diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml index e7fc3317e..f51080313 100644 --- a/.trunk/trunk.yaml +++ b/.trunk/trunk.yaml @@ -18,7 +18,7 @@ runtimes: # This is the section where you manage your linters. (https://docs.trunk.io/check/configuration) lint: enabled: - - markdownlint@0.40.0 + # - markdownlint@0.40.0 - actionlint@1.7.0 - bandit@1.7.8 - black@24.4.2 diff --git a/backend/endpoints/responses/rom.py b/backend/endpoints/responses/rom.py index 0632bb973..445f28472 100644 --- a/backend/endpoints/responses/rom.py +++ b/backend/endpoints/responses/rom.py @@ -120,7 +120,9 @@ class DetailedRomSchema(RomSchema): user_notes: list[RomNoteSchema] = Field(default_factory=list) @classmethod - def from_orm_with_request(cls, db_rom: Rom, request: Request) -> "DetailedRomSchema": + def from_orm_with_request( + cls, db_rom: Rom, request: Request + ) -> "DetailedRomSchema": rom = cls.model_validate(db_rom) user_id = request.user.id diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py index 157dec9cd..8dea6c884 100644 --- a/backend/endpoints/rom.py +++ b/backend/endpoints/rom.py @@ -1,7 +1,7 @@ import os from datetime import datetime from stat import S_IFREG -from typing import Annotated, Optional +from typing import Annotated from urllib.parse import quote from config import DISABLE_DOWNLOAD_ENDPOINT_AUTH, LIBRARY_BASE_PATH @@ -15,7 +15,7 @@ from endpoints.responses.rom import ( RomSchema, ) from exceptions.fs_exceptions import RomAlreadyExistsException -from fastapi import APIRouter, HTTPException, Query, Request, UploadFile, File, status +from fastapi import APIRouter, HTTPException, Query, Request, UploadFile, status from fastapi.responses import FileResponse from handler.database import db_platform_handler, db_rom_handler from handler.filesystem import fs_resource_handler, fs_rom_handler @@ -86,9 +86,9 @@ def add_roms( @protected_route(router.get, "/roms", ["roms.read"]) def get_roms( request: Request, - platform_id: int = None, + platform_id: int | None = None, search_term: str = "", - limit: int = None, + limit: int | None = None, order_by: str = "name", order_dir: str = "asc", ) -> list[RomSchema]: @@ -248,7 +248,7 @@ async def update_rom( id: int, rename_as_igdb: bool = False, remove_cover: bool = False, - artwork: Optional[UploadFile] = File(None), + artwork: UploadFile | None = None, ) -> DetailedRomSchema: """Update rom endpoint diff --git a/backend/endpoints/tests/test_rom.py b/backend/endpoints/tests/test_rom.py index 66314f13e..020990ae5 100644 --- a/backend/endpoints/tests/test_rom.py +++ b/backend/endpoints/tests/test_rom.py @@ -1,6 +1,6 @@ -from fastapi.testclient import TestClient from unittest.mock import patch +from fastapi.testclient import TestClient from main import app client = TestClient(app) diff --git a/backend/handler/scan_handler.py b/backend/handler/scan_handler.py index 831057c89..3e67901c0 100644 --- a/backend/handler/scan_handler.py +++ b/backend/handler/scan_handler.py @@ -11,8 +11,8 @@ from handler.filesystem import ( fs_rom_handler, ) from handler.metadata import meta_igdb_handler, meta_moby_handler -from handler.metadata.igdb_handler import IGDBRom, IGDBPlatform -from handler.metadata.moby_handler import MobyGamesRom, MobyGamesPlatform +from handler.metadata.igdb_handler import IGDBPlatform, IGDBRom +from handler.metadata.moby_handler import MobyGamesPlatform, MobyGamesRom from logger.logger import log from models.assets import Save, Screenshot, State from models.firmware import Firmware @@ -51,7 +51,7 @@ def _get_main_platform_igdb_id(platform: Platform): def scan_platform( fs_slug: str, fs_platforms: list[str], - metadata_sources: list[str] = ["igdb", "moby"], + metadata_sources: list[str] = None, ) -> Platform: """Get platform details @@ -63,6 +63,9 @@ def scan_platform( log.info(f"· {fs_slug}") + if metadata_sources is None: + metadata_sources = ["igdb", "moby"] + platform_attrs: dict[str, Any] = {} platform_attrs["fs_slug"] = fs_slug diff --git a/backend/models/rom.py b/backend/models/rom.py index af041f849..225879ef7 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -15,10 +15,10 @@ from sqlalchemy import ( String, Text, UniqueConstraint, - func, - select, and_, + func, or_, + select, ) from sqlalchemy.dialects.mysql.json import JSON as MySQLJSON from sqlalchemy.orm import Mapped, relationship @@ -118,8 +118,8 @@ class Rom(BaseModel): Rom.platform_id == self.platform_id, Rom.id != self.id, or_( - and_(Rom.igdb_id == self.igdb_id, Rom.igdb_id != None), # noqa - and_(Rom.moby_id == self.moby_id, Rom.moby_id != None), # noqa + and_(Rom.igdb_id == self.igdb_id, Rom.igdb_id is not None), + and_(Rom.moby_id == self.moby_id, Rom.moby_id is not None), ), ) ) diff --git a/docker/init_scripts/init b/docker/init_scripts/init index 977a03115..3c0a19ead 100755 --- a/docker/init_scripts/init +++ b/docker/init_scripts/init @@ -34,55 +34,55 @@ error_log() { exit 1 } -wait_for_gunicorn_socket () { - info_log "waiting for gunicorn socket file..." - while [ ! -S /tmp/gunicorn.sock ]; do - sleep 1 - done - info_log "gunicorn socket file found" +wait_for_gunicorn_socket() { + info_log "waiting for gunicorn socket file..." + while [[ ! -S /tmp/gunicorn.sock ]]; do + sleep 1 + done + info_log "gunicorn socket file found" } # function that runs or main process and creates a corresponding PID file, -start_bin_gunicorn () { - # cleanup potentially leftover socket - rm /tmp/gunicorn.sock -f +start_bin_gunicorn() { + # cleanup potentially leftover socket + rm /tmp/gunicorn.sock -f - # commands to start our main application and store its PID to check for crashes - info_log "starting gunicorn" - gunicorn \ - --access-logfile - \ - --error-logfile - \ - --worker-class uvicorn.workers.UvicornWorker \ - --bind=0.0.0.0:5000 \ - --bind=unix:/tmp/gunicorn.sock \ - --pid=/tmp/gunicorn.pid \ - --workers ${GUNICORN_WORKERS:=2} \ - main:app & + # commands to start our main application and store its PID to check for crashes + info_log "starting gunicorn" + gunicorn \ + --access-logfile - \ + --error-logfile - \ + --worker-class uvicorn.workers.UvicornWorker \ + --bind=0.0.0.0:5000 \ + --bind=unix:/tmp/gunicorn.sock \ + --pid=/tmp/gunicorn.pid \ + --workers "${GUNICORN_WORKERS:=2}" \ + main:app & } # Commands to start nginx (handling PID creation internally) -start_bin_nginx () { - wait_for_gunicorn_socket +start_bin_nginx() { + wait_for_gunicorn_socket - info_log "starting nginx" - if [ "$EUID" -ne 0 ]; then - nginx - else - # if container runs as root, drop permissions - nginx -g 'user romm;' - fi + info_log "starting nginx" + if [[ ${EUID} -ne 0 ]]; then + nginx + else + # if container runs as root, drop permissions + nginx -g 'user romm;' + fi } -start_bin_redis-server () { - info_log "starting redis-server" - # Check if /usr/local/etc/redis/redis.conf exists and use it if so - if [ -f /usr/local/etc/redis/redis.conf ]; then - redis-server /usr/local/etc/redis/redis.conf & - else - redis-server --dir /redis-data & - fi - REDIS_PID=$! - echo $REDIS_PID > /tmp/redis-server.pid +start_bin_redis-server() { + info_log "starting redis-server" + # Check if /usr/local/etc/redis/redis.conf exists and use it if so + if [[ -f /usr/local/etc/redis/redis.conf ]]; then + redis-server /usr/local/etc/redis/redis.conf & + else + redis-server --dir /redis-data & + fi + REDIS_PID=$! + echo "${REDIS_PID}" >/tmp/redis-server.pid } # function that runs our independent python scripts and creates corresponding PID files, @@ -150,17 +150,17 @@ while true; do debug_log "database schema already upgraded during current container lifecycle" fi - # Start gunicorn if we dont have a corresponding PID file - watchdog_process_pid bin gunicorn - - # Start nginx if we dont have a corresponding PID file - watchdog_process_pid bin nginx + # Start gunicorn if we dont have a corresponding PID file + watchdog_process_pid bin gunicorn - # only start the watcher.py if we actually want to use the rescan on fs change feature - if [[ ${ENABLE_RESCAN_ON_FILESYSTEM_CHANGE} == "true" ]]; then - # Start watcher if we dont have a corresponding PID file - watchdog_process_pid python watcher - fi + # Start nginx if we dont have a corresponding PID file + watchdog_process_pid bin nginx + + # only start the watcher.py if we actually want to use the rescan on fs change feature + if [[ ${ENABLE_RESCAN_ON_FILESYSTEM_CHANGE} == "true" ]]; then + # Start watcher if we dont have a corresponding PID file + watchdog_process_pid python watcher + fi # Start background worker processes debug_log "Starting worker and scheduler" diff --git a/examples/config.es-de.example.yml b/examples/config.es-de.example.yml index 06a9e4bc1..8669980f7 100644 --- a/examples/config.es-de.example.yml +++ b/examples/config.es-de.example.yml @@ -6,126 +6,125 @@ exclude: roms: single_file: extensions: - - 'xml' - - 'txt' + - "xml" + - "txt" names: - - 'info.txt' - - 'metadata.txt' - - 'systeminfo.txt' - - 'psy.ggjud.iso' - - 'psy.ggjud.nfo' + - "info.txt" + - "metadata.txt" + - "systeminfo.txt" + - "psy.ggjud.iso" + - "psy.ggjud.nfo" multi_file: names: - - 'roms' - - 'pfx' - - 'mlc01' - - '.Trash*' + - "roms" + - "pfx" + - "mlc01" + - ".Trash*" system: platforms: - 3do: '3do' # 3DO Interactive Multiplayer - n3ds: '3ds' # Nintendo 3DS - amstradcpc: 'acpc' # Amstrad CPC - amiga: 'amiga' # Amiga - amigacd32: 'amiga-cd32' # Amiga CD32 - android: 'android' # Android - apple2gs: 'apple2gs' # Apple IIGD - apple2: 'appleii' # Apple II - arcade: 'arcade' # Arcade - arcadia: 'arcadia-2001' # Arcadia 2001 - arduboy: 'arduboy' # Arduboy - astrocde: 'astrocade' # Bally Astrocade - apfm1000: 'apf' # APF-M1000/Imagination Machine - atarijaguarcd: 'atari-jaguar-cd' # Atari Jaguar CD - atarist: 'atari-st' # Atari ST/STE - atari2600: 'atari2600' # Atari 2600 - atari5200: 'atari5200' # Atari 5200 - atari7800: 'atari7800' # Atari 7800 - atari800: 'atari8bit' # Atari 8-bit - c16: 'c16' # Commodore 16 - c64: 'c64' # Commodore C64/128/MAX - pv1000: 'casio-pv-1000' # Casio PV-1000 - colecovision: 'colecovision' # ColecoVision - cdtv: 'commodore-cdtv' # CDTV - crvision: 'creativision' # CreatiVision - dreamcast: 'dc' # Dreamcast - dos: 'dos' # DOS - dragon32: 'dragon-32-slash-64' # Dragon 32/64 - channelf: 'fairchild-channel-f' # Channel F - famicom: 'famicom' # Family Computer - fds: 'fds' # Family Computer Disk System - fmtowns: 'fm-towns' # FM Towns - gameandwatch: 'g-and-w' # Game & Watch - gamecom: 'game-dot-com' # Game.Com - gb: 'gb' # Game Boy - gba: 'gba' # Game Boy Advance - gbc: 'gbc' # Game Boy Color - megadrive: 'genesis-slash-megadrive' # Genesis/Mega Drive - lcdgames: 'handheld-electronic-lcd' # Handheld Electronic LCD - intellivision: 'intellivision' # Intellivision - j2me: 'j2me' # J2ME - atarijaguar: 'jaguar' # Atari Jaguar - atarilynx: 'lynx' # Atari Lynx - macintosh: 'mac' # Mac - megaduck: 'mega-duck-slash-cougar-boy' # Mega Duck/Cougar Boy - msx: 'msx' # MSX - msx2: 'msx2' # MSX2 - n64: 'n64' # Nintendo 64 - nds: 'nds' # Nintendo DS - neogeocd: 'neo-geo-cd' # Neo Geo CD - ngp: 'neo-geo-pocket' # Neo Geo Pocket - ngpc: 'neo-geo-pocket-color' # Neo Geo Pocket Color - neogeo: 'neogeoaes' # Neo Geo - nes: 'nes' # Nintendo Entertainment System - gc: 'ngc' # GameCube - n64dd: 'nintendo-64dd' # Nintendo 64DD - odyssey2: 'odyssey-2-slash-videopac-g7000' # Odyssey 2/Videopac G7000 - oric: 'oric' # Oric - palm: 'palm-os' # Palm OS - pc88: 'pc-8800-series' # PC-8800 Series - pc98: 'pc-9800-series' # PC-9800 Series - pcfx: 'pc-fx' # PC-FX - pokemini: 'pokemon-mini' # Pokémon mini - psx: 'ps' # PlayStation - ps2: 'ps2' # PlayStation 2 - ps3: 'ps3' # PlayStation 3 - ps4: 'ps4--1' # PlayStation 4 - psp: 'psp' # PlayStation Portable - samcoupe: 'sam-coupe' # SAM Coupé - satellaview: 'satellaview' # Satellaview - sega32x: 'sega-32x' # SEGA 32X - mastersystem: 'sega-master-system' # SEGA Master System - sega32x: 'sega32' # Sega 32X - megacd: 'segacd' # SEGA CD - sfc: 'sfam' # Super Famicom - sg-1000: 'sg1000' # SG-1000 - x68000: 'sharp-x68000' # Sharp X68000 - snes: 'snes' # Super Nintendo Entertainment System - spectravideo: 'spectravideo' # Spectravideo - supergrafx: 'supergrafx' # PC Engine SuperGrafx - supervision: 'supervision' # Supervision - switch: 'switch' # Nintendo Switch - symbian: 'symbian' # Symbian - ti99: 'ti-99' # Texas Instruments TI-99 - trs-80: 'trs-80' # TRS-80 - tg-cd: 'turbografx-16-slash-pc-engine-cd' # TurboGrafx CD - tg16: 'turbografx16--1' # TurboGrafx-16 - vectrex: 'vectrex' # Vectrex - vic20: 'vic-20' # Commodore VIC-20 - videopac: 'videopac-g7400' # Videopac+ G7400 - virtualboy: 'virtualboy' # Virtual Boy - vsmile: 'vsmile' # V.Smile - wii: 'wii' # Wii - wiiu: 'wiiu' # Wii U - pc: 'win' # PC (Microsoft Windows) - wonderswan: 'wonderswan' # WonderSwan - wonderswancolor: 'wonderswan-color' # WonderSwan Color - x1: 'x1' # Sharp X1 - xbox: 'xbox' # Xbox - xbox360: 'xbox360' # Xbox 360 - zmachine: 'z-machine' # Z-machine - zx81: 'zx80' # ZX80 - zxspectrum: 'zxs' # ZX Spectrum - naomi: 'arcade' - naomi2: 'arcade' - naomigd: 'arcade' + 3do: "3do" # 3DO Interactive Multiplayer + n3ds: "3ds" # Nintendo 3DS + amstradcpc: "acpc" # Amstrad CPC + amiga: "amiga" # Amiga + amigacd32: "amiga-cd32" # Amiga CD32 + android: "android" # Android + apple2gs: "apple2gs" # Apple IIGD + apple2: "appleii" # Apple II + arcade: "arcade" # Arcade + arcadia: "arcadia-2001" # Arcadia 2001 + arduboy: "arduboy" # Arduboy + astrocde: "astrocade" # Bally Astrocade + apfm1000: "apf" # APF-M1000/Imagination Machine + atarijaguarcd: "atari-jaguar-cd" # Atari Jaguar CD + atarist: "atari-st" # Atari ST/STE + atari2600: "atari2600" # Atari 2600 + atari5200: "atari5200" # Atari 5200 + atari7800: "atari7800" # Atari 7800 + atari800: "atari8bit" # Atari 8-bit + c16: "c16" # Commodore 16 + c64: "c64" # Commodore C64/128/MAX + pv1000: "casio-pv-1000" # Casio PV-1000 + colecovision: "colecovision" # ColecoVision + cdtv: "commodore-cdtv" # CDTV + crvision: "creativision" # CreatiVision + dreamcast: "dc" # Dreamcast + dos: "dos" # DOS + dragon32: "dragon-32-slash-64" # Dragon 32/64 + channelf: "fairchild-channel-f" # Channel F + famicom: "famicom" # Family Computer + fds: "fds" # Family Computer Disk System + fmtowns: "fm-towns" # FM Towns + gameandwatch: "g-and-w" # Game & Watch + gamecom: "game-dot-com" # Game.Com + gb: "gb" # Game Boy + gba: "gba" # Game Boy Advance + gbc: "gbc" # Game Boy Color + megadrive: "genesis-slash-megadrive" # Genesis/Mega Drive + lcdgames: "handheld-electronic-lcd" # Handheld Electronic LCD + intellivision: "intellivision" # Intellivision + j2me: "j2me" # J2ME + atarijaguar: "jaguar" # Atari Jaguar + atarilynx: "lynx" # Atari Lynx + macintosh: "mac" # Mac + megaduck: "mega-duck-slash-cougar-boy" # Mega Duck/Cougar Boy + msx: "msx" # MSX + msx2: "msx2" # MSX2 + n64: "n64" # Nintendo 64 + nds: "nds" # Nintendo DS + neogeocd: "neo-geo-cd" # Neo Geo CD + ngp: "neo-geo-pocket" # Neo Geo Pocket + ngpc: "neo-geo-pocket-color" # Neo Geo Pocket Color + neogeo: "neogeoaes" # Neo Geo + nes: "nes" # Nintendo Entertainment System + gc: "ngc" # GameCube + n64dd: "nintendo-64dd" # Nintendo 64DD + odyssey2: "odyssey-2-slash-videopac-g7000" # Odyssey 2/Videopac G7000 + oric: "oric" # Oric + palm: "palm-os" # Palm OS + pc88: "pc-8800-series" # PC-8800 Series + pc98: "pc-9800-series" # PC-9800 Series + pcfx: "pc-fx" # PC-FX + pokemini: "pokemon-mini" # Pokémon mini + psx: "ps" # PlayStation + ps2: "ps2" # PlayStation 2 + ps3: "ps3" # PlayStation 3 + ps4: "ps4--1" # PlayStation 4 + psp: "psp" # PlayStation Portable + samcoupe: "sam-coupe" # SAM Coupé + satellaview: "satellaview" # Satellaview + mastersystem: "sega-master-system" # SEGA Master System + sega32x: "sega32" # Sega 32X + megacd: "segacd" # SEGA CD + sfc: "sfam" # Super Famicom + sg-1000: "sg1000" # SG-1000 + x68000: "sharp-x68000" # Sharp X68000 + snes: "snes" # Super Nintendo Entertainment System + spectravideo: "spectravideo" # Spectravideo + supergrafx: "supergrafx" # PC Engine SuperGrafx + supervision: "supervision" # Supervision + switch: "switch" # Nintendo Switch + symbian: "symbian" # Symbian + ti99: "ti-99" # Texas Instruments TI-99 + trs-80: "trs-80" # TRS-80 + tg-cd: "turbografx-16-slash-pc-engine-cd" # TurboGrafx CD + tg16: "turbografx16--1" # TurboGrafx-16 + vectrex: "vectrex" # Vectrex + vic20: "vic-20" # Commodore VIC-20 + videopac: "videopac-g7400" # Videopac+ G7400 + virtualboy: "virtualboy" # Virtual Boy + vsmile: "vsmile" # V.Smile + wii: "wii" # Wii + wiiu: "wiiu" # Wii U + pc: "win" # PC (Microsoft Windows) + wonderswan: "wonderswan" # WonderSwan + wonderswancolor: "wonderswan-color" # WonderSwan Color + x1: "x1" # Sharp X1 + xbox: "xbox" # Xbox + xbox360: "xbox360" # Xbox 360 + zmachine: "z-machine" # Z-machine + zx81: "zx80" # ZX80 + zxspectrum: "zxs" # ZX Spectrum + naomi: "arcade" + naomi2: "arcade" + naomigd: "arcade" diff --git a/examples/config.example.yml b/examples/config.example.yml index 3de1c141a..5118fced2 100644 --- a/examples/config.example.yml +++ b/examples/config.example.yml @@ -46,7 +46,7 @@ system: platforms: # gc: 'ngc' # In this example if you have a 'gc' folder, RomM will treat it like the 'ngc' folder # psx: 'ps' # In this example if you have a 'psx' folder, RomM will treat it like the 'ps' folder - + # Asociate one platform to it's main version versions: # naomi: 'arcade' diff --git a/frontend/src/services/api/rom.ts b/frontend/src/services/api/rom.ts index 426729ae3..ddc27e308 100644 --- a/frontend/src/services/api/rom.ts +++ b/frontend/src/services/api/rom.ts @@ -57,7 +57,11 @@ async function getRecentRoms(): Promise<{ data: SimpleRom[] }> { }); } -async function getRom({ romId }: { romId: number }): Promise<{ data: DetailedRom }> { +async function getRom({ + romId, +}: { + romId: number; +}): Promise<{ data: DetailedRom }> { return api.get(`/roms/${romId}`); } diff --git a/frontend/src/stores/galleryFilter.ts b/frontend/src/stores/galleryFilter.ts index bfb6e91c5..20a4f2605 100644 --- a/frontend/src/stores/galleryFilter.ts +++ b/frontend/src/stores/galleryFilter.ts @@ -50,11 +50,11 @@ export default defineStore("galleryFilter", { isFiltered() { return Boolean( normalizeString(this.filterSearch).trim() != "" || - this.filterUnmatched || - this.selectedGenre || - this.selectedFranchise || - this.selectedCollection || - this.selectedCompany + this.filterUnmatched || + this.selectedGenre || + this.selectedFranchise || + this.selectedCollection || + this.selectedCompany, ); }, reset() {