mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 23:42:07 +01:00
bunch of fixes for trunk
This commit is contained in:
3
.trunk/configs/.bandit
Normal file
3
.trunk/configs/.bandit
Normal file
@@ -0,0 +1,3 @@
|
||||
[bandit]
|
||||
exclude = tests
|
||||
skips=B101
|
||||
@@ -1,7 +1,7 @@
|
||||
Thanks for helping make RomM safer for everyone.
|
||||
|
||||
## Reporting Security Issues
|
||||
|
||||
Thanks for helping make RomM safer for everyone.
|
||||
|
||||
If you believe you have found a security vulnerability in RomM, please report it to us through coordinated disclosure.
|
||||
|
||||
**Do not report security vulnerabilities through public GitHub issues, discussions, pull requests, or on our public Discord server.**
|
||||
|
||||
@@ -64,14 +64,16 @@ def migrate_to_mysql() -> None:
|
||||
continue
|
||||
|
||||
table_data = sqlite_conn.execute(
|
||||
text(f"SELECT * FROM {table_name}")
|
||||
text(f"SELECT * FROM {table_name}") # nosec B608
|
||||
).fetchall()
|
||||
|
||||
# Insert data into MariaDB table
|
||||
for row in table_data:
|
||||
mapped_row = {f"{i}": value for i, value in enumerate(row, start=1)}
|
||||
columns = ",".join([f":{i}" for i in range(1, len(row) + 1)])
|
||||
insert_query = f"INSERT INTO {table_name} VALUES ({columns})"
|
||||
insert_query = (
|
||||
f"INSERT INTO {table_name} VALUES ({columns})" # nosec B608
|
||||
)
|
||||
maria_conn.execute(text(insert_query), mapped_row)
|
||||
|
||||
maria_conn.execute(text("SET FOREIGN_KEY_CHECKS=1"))
|
||||
|
||||
@@ -8,7 +8,7 @@ load_dotenv()
|
||||
|
||||
# UVICORN
|
||||
DEV_PORT: Final = int(os.environ.get("VITE_BACKEND_DEV_PORT", "5000"))
|
||||
DEV_HOST: Final = "0.0.0.0"
|
||||
DEV_HOST: Final = "127.0.0.1"
|
||||
ROMM_HOST: Final = os.environ.get("ROMM_HOST", DEV_HOST)
|
||||
|
||||
# PATHS
|
||||
|
||||
@@ -220,9 +220,9 @@ class ConfigManager:
|
||||
self._raw_config = yaml.load(config_file, Loader=SafeLoader) or {}
|
||||
except FileNotFoundError:
|
||||
self._raw_config = {}
|
||||
except PermissionError:
|
||||
except PermissionError as exc:
|
||||
self._raw_config = {}
|
||||
raise ConfigNotReadableException
|
||||
raise ConfigNotReadableException from exc
|
||||
|
||||
self._parse_config()
|
||||
self._validate_config()
|
||||
@@ -259,9 +259,9 @@ class ConfigManager:
|
||||
yaml.dump(self._raw_config, config_file)
|
||||
except FileNotFoundError:
|
||||
self._raw_config = {}
|
||||
except PermissionError:
|
||||
except PermissionError as exc:
|
||||
self._raw_config = {}
|
||||
raise ConfigNotWritableException
|
||||
raise ConfigNotWritableException from exc
|
||||
|
||||
def add_platform_binding(self, fs_slug: str, slug: str) -> None:
|
||||
platform_bindings = self.config.PLATFORMS_BINDING
|
||||
|
||||
22
backend/config/tests/fixtures/config/config.yml
vendored
22
backend/config/tests/fixtures/config/config.yml
vendored
@@ -3,29 +3,29 @@
|
||||
|
||||
exclude:
|
||||
platforms:
|
||||
- "romm"
|
||||
- romm
|
||||
roms:
|
||||
single_file:
|
||||
names:
|
||||
- "info.txt"
|
||||
- info.txt
|
||||
extensions:
|
||||
- "xml"
|
||||
- xml
|
||||
|
||||
multi_file:
|
||||
names:
|
||||
- "my_multi_file_game"
|
||||
- "DLC"
|
||||
- my_multi_file_game
|
||||
- DLC
|
||||
parts:
|
||||
names:
|
||||
- "data.xml"
|
||||
- data.xml
|
||||
extensions:
|
||||
- "txt"
|
||||
- txt
|
||||
system:
|
||||
platforms:
|
||||
gc: "ngc"
|
||||
gc: ngc
|
||||
versions:
|
||||
naomi: "arcade"
|
||||
naomi: arcade
|
||||
|
||||
filesystem:
|
||||
roms_folder: "ROMS"
|
||||
firmware_folder: "BIOS"
|
||||
roms_folder: ROMS
|
||||
firmware_folder: BIOS
|
||||
|
||||
@@ -25,17 +25,17 @@ oauth2_password_bearer = OAuth2PasswordBearer(
|
||||
def protected_route(
|
||||
method: Any,
|
||||
path: str,
|
||||
scopes: list[str] = [],
|
||||
scopes: list[str] | None = None,
|
||||
**kwargs,
|
||||
):
|
||||
def decorator(func: DecoratedCallable):
|
||||
fn = requires(scopes)(func)
|
||||
fn = requires(scopes or [])(func)
|
||||
return method(
|
||||
path,
|
||||
dependencies=[
|
||||
Security(
|
||||
dependency=oauth2_password_bearer,
|
||||
scopes=scopes,
|
||||
scopes=scopes or [],
|
||||
),
|
||||
Security(dependency=HTTPBasic(auto_error=False)),
|
||||
],
|
||||
|
||||
@@ -15,10 +15,10 @@ def begin_session(func):
|
||||
with args[0].session.begin() as s:
|
||||
kwargs["session"] = s
|
||||
return func(*args, **kwargs)
|
||||
except ProgrammingError as e:
|
||||
log.critical(str(e))
|
||||
except ProgrammingError as exc:
|
||||
log.critical(str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)
|
||||
)
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
return wrapper
|
||||
|
||||
@@ -133,7 +133,9 @@ async def token(form_data: Annotated[OAuth2RequestForm, Depends()]) -> TokenResp
|
||||
|
||||
|
||||
@router.post("/login")
|
||||
def login(request: Request, credentials=Depends(HTTPBasic())) -> MessageResponse:
|
||||
def login(
|
||||
request: Request, credentials=Depends(HTTPBasic())
|
||||
) -> MessageResponse: # nosec B008
|
||||
"""Session login endpoint
|
||||
|
||||
Args:
|
||||
|
||||
@@ -22,11 +22,11 @@ def get_config() -> ConfigResponse:
|
||||
|
||||
try:
|
||||
return cm.get_config().__dict__
|
||||
except ConfigNotReadableException as e:
|
||||
log.critical(e.message)
|
||||
except ConfigNotReadableException as exc:
|
||||
log.critical(exc.message)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.message
|
||||
)
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message
|
||||
) from exc
|
||||
|
||||
|
||||
@protected_route(router.post, "/config/system/platforms", ["platforms.write"])
|
||||
@@ -39,11 +39,11 @@ async def add_platform_binding(request: Request) -> MessageResponse:
|
||||
|
||||
try:
|
||||
cm.add_platform_binding(fs_slug, slug)
|
||||
except ConfigNotWritableException as e:
|
||||
log.critical(e.message)
|
||||
except ConfigNotWritableException as exc:
|
||||
log.critical(exc.message)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.message
|
||||
)
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message
|
||||
) from exc
|
||||
|
||||
return {"msg": f"{fs_slug} binded to: {slug} successfully!"}
|
||||
|
||||
@@ -56,11 +56,11 @@ async def delete_platform_binding(request: Request, fs_slug: str) -> MessageResp
|
||||
|
||||
try:
|
||||
cm.remove_platform_binding(fs_slug)
|
||||
except ConfigNotWritableException as e:
|
||||
log.critical(e.message)
|
||||
except ConfigNotWritableException as exc:
|
||||
log.critical(exc.message)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.message
|
||||
)
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message
|
||||
) from exc
|
||||
|
||||
return {"msg": f"{fs_slug} bind removed successfully!"}
|
||||
|
||||
@@ -75,11 +75,11 @@ async def add_platform_version(request: Request) -> MessageResponse:
|
||||
|
||||
try:
|
||||
cm.add_platform_version(fs_slug, slug)
|
||||
except ConfigNotWritableException as e:
|
||||
log.critical(e.message)
|
||||
except ConfigNotWritableException as exc:
|
||||
log.critical(exc.message)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.message
|
||||
)
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message
|
||||
) from exc
|
||||
|
||||
return {"msg": f"Added {fs_slug} as version of: {slug} successfully!"}
|
||||
|
||||
@@ -92,11 +92,11 @@ async def delete_platform_version(request: Request, fs_slug: str) -> MessageResp
|
||||
|
||||
try:
|
||||
cm.remove_platform_version(fs_slug)
|
||||
except ConfigNotWritableException as e:
|
||||
log.critical(e.message)
|
||||
except ConfigNotWritableException as exc:
|
||||
log.critical(exc.message)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=e.message
|
||||
)
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=exc.message
|
||||
) from exc
|
||||
|
||||
return {"msg": f"{fs_slug} version removed successfully!"}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ from config import DISABLE_DOWNLOAD_ENDPOINT_AUTH, LIBRARY_BASE_PATH
|
||||
from decorators.auth import protected_route
|
||||
from endpoints.responses import MessageResponse
|
||||
from endpoints.responses.firmware import AddFirmwareResponse, FirmwareSchema
|
||||
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
|
||||
from fastapi import APIRouter, HTTPException, Request, UploadFile, status
|
||||
from fastapi.responses import FileResponse
|
||||
from handler.database import db_firmware_handler, db_platform_handler
|
||||
from handler.filesystem import fs_firmware_handler
|
||||
@@ -14,7 +14,7 @@ router = APIRouter()
|
||||
|
||||
@protected_route(router.post, "/firmware", ["firmware.write"])
|
||||
def add_firmware(
|
||||
request: Request, platform_id: int, files: list[UploadFile] = File(...)
|
||||
request: Request, platform_id: int, files: list[UploadFile] | None = None
|
||||
) -> AddFirmwareResponse:
|
||||
"""Upload firmware files endpoint
|
||||
|
||||
@@ -196,9 +196,11 @@ async def delete_firmware(
|
||||
fs_firmware_handler.remove_file(
|
||||
file_name=firmware.file_name, file_path=firmware.file_path
|
||||
)
|
||||
except FileNotFoundError:
|
||||
except FileNotFoundError as exc:
|
||||
error = f"Firmware file {firmware.file_name} not found for platform {firmware.platform_slug}"
|
||||
log.error(error)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=error
|
||||
) from exc
|
||||
|
||||
return {"msg": f"{len(firmare_ids)} firmware files deleted successfully!"}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Optional
|
||||
|
||||
from fastapi import File, UploadFile
|
||||
from fastapi import UploadFile
|
||||
from fastapi.param_functions import Form
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ class UserForm:
|
||||
password: Optional[str] = None,
|
||||
role: Optional[str] = None,
|
||||
enabled: Optional[bool] = None,
|
||||
avatar: Optional[UploadFile] = File(None),
|
||||
avatar: Optional[UploadFile] = None,
|
||||
):
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
@@ -13,7 +13,7 @@ from endpoints.responses.rom import (
|
||||
RomSchema,
|
||||
)
|
||||
from exceptions.fs_exceptions import RomAlreadyExistsException
|
||||
from fastapi import APIRouter, File, HTTPException, Query, Request, UploadFile, status
|
||||
from fastapi import APIRouter, HTTPException, Query, Request, UploadFile, status
|
||||
from fastapi.responses import FileResponse
|
||||
from fastapi_pagination.cursor import CursorPage, CursorParams
|
||||
from fastapi_pagination.ext.sqlalchemy import paginate
|
||||
@@ -29,7 +29,7 @@ router = APIRouter()
|
||||
|
||||
@protected_route(router.post, "/roms", ["roms.write"])
|
||||
def add_roms(
|
||||
request: Request, platform_id: int, roms: list[UploadFile] = File(...)
|
||||
request: Request, platform_id: int, roms: list[UploadFile] | None = None
|
||||
) -> AddRomsResponse:
|
||||
"""Upload roms endpoint (one or more at the same time)
|
||||
|
||||
@@ -247,7 +247,7 @@ async def update_rom(
|
||||
id: int,
|
||||
rename_as_igdb: bool = False,
|
||||
remove_cover: bool = False,
|
||||
artwork: Optional[UploadFile] = File(None),
|
||||
artwork: Optional[UploadFile] = None,
|
||||
) -> RomSchema:
|
||||
"""Update rom endpoint
|
||||
|
||||
@@ -305,11 +305,11 @@ async def update_rom(
|
||||
new_name=fs_safe_file_name,
|
||||
file_path=db_rom.file_path,
|
||||
)
|
||||
except RomAlreadyExistsException as e:
|
||||
log.error(str(e))
|
||||
except RomAlreadyExistsException as exc:
|
||||
log.error(str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)
|
||||
)
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(exc)
|
||||
) from exc
|
||||
|
||||
cleaned_data["file_name"] = fs_safe_file_name
|
||||
cleaned_data["file_name_no_tags"] = fs_rom_handler.get_file_name_with_no_tags(
|
||||
@@ -415,10 +415,12 @@ async def delete_roms(
|
||||
fs_rom_handler.remove_file(
|
||||
file_name=rom.file_name, file_path=rom.file_path
|
||||
)
|
||||
except FileNotFoundError:
|
||||
except FileNotFoundError as exc:
|
||||
error = f"Rom file {rom.file_name} not found for platform {rom.platform_slug}"
|
||||
log.error(error)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=error
|
||||
) from exc
|
||||
|
||||
return {"msg": f"{len(roms_ids)} roms deleted successfully!"}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from decorators.auth import protected_route
|
||||
from endpoints.responses import MessageResponse
|
||||
from endpoints.responses.assets import SaveSchema, UploadedSavesResponse
|
||||
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
|
||||
from fastapi import APIRouter, HTTPException, Request, UploadFile, status
|
||||
from handler.database import db_rom_handler, db_save_handler, db_screenshot_handler
|
||||
from handler.filesystem import fs_asset_handler
|
||||
from handler.scan_handler import scan_save
|
||||
@@ -14,7 +14,7 @@ router = APIRouter()
|
||||
def add_saves(
|
||||
request: Request,
|
||||
rom_id: int,
|
||||
saves: list[UploadFile] = File(...),
|
||||
saves: list[UploadFile] | None = None,
|
||||
emulator: str = None,
|
||||
) -> UploadedSavesResponse:
|
||||
rom = db_rom_handler.get_roms(rom_id)
|
||||
@@ -129,10 +129,12 @@ async def delete_saves(request: Request) -> MessageResponse:
|
||||
fs_asset_handler.remove_file(
|
||||
file_name=save.file_name, file_path=save.file_path
|
||||
)
|
||||
except FileNotFoundError:
|
||||
except FileNotFoundError as exc:
|
||||
error = f"Save file {save.file_name} not found for platform {save.rom.platform_slug}"
|
||||
log.error(error)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=error
|
||||
) from exc
|
||||
|
||||
if save.screenshot:
|
||||
db_screenshot_handler.delete_screenshot(save.screenshot.id)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from decorators.auth import protected_route
|
||||
from endpoints.responses.assets import UploadedScreenshotsResponse
|
||||
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
|
||||
from fastapi import APIRouter, HTTPException, Request, UploadFile, status
|
||||
from handler.database import db_rom_handler, db_screenshot_handler
|
||||
from handler.filesystem import fs_asset_handler
|
||||
from handler.scan_handler import scan_screenshot
|
||||
@@ -11,7 +11,7 @@ router = APIRouter()
|
||||
|
||||
@protected_route(router.post, "/screenshots", ["assets.write"])
|
||||
def add_screenshots(
|
||||
request: Request, rom_id: int, screenshots: list[UploadFile] = File(...)
|
||||
request: Request, rom_id: int, screenshots: list[UploadFile] | None = None
|
||||
) -> UploadedScreenshotsResponse:
|
||||
rom = db_rom_handler.get_roms(rom_id)
|
||||
current_user = request.user
|
||||
|
||||
@@ -64,12 +64,12 @@ async def search_rom(
|
||||
moby_matched_roms = meta_moby_handler.get_matched_roms_by_id(
|
||||
int(search_term)
|
||||
)
|
||||
except ValueError:
|
||||
except ValueError as exc:
|
||||
log.error(f"Search error: invalid ID '{search_term}'")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Tried searching by ID, but '{search_term}' is not a valid ID",
|
||||
)
|
||||
) from exc
|
||||
elif search_by.lower() == "name":
|
||||
igdb_matched_roms = meta_igdb_handler.get_matched_roms_by_name(
|
||||
search_term, _get_main_platform_igdb_id(rom.platform), search_extended
|
||||
|
||||
@@ -41,8 +41,8 @@ def _get_socket_manager():
|
||||
async def scan_platforms(
|
||||
platform_ids: list[int],
|
||||
scan_type: ScanType = ScanType.QUICK,
|
||||
selected_roms: list[str] = (),
|
||||
metadata_sources: list[str] = ["igdb", "moby"],
|
||||
selected_roms: list[str] | None = None,
|
||||
metadata_sources: list[str] | None = None,
|
||||
):
|
||||
"""Scan all the listed platforms and fetch metadata from different sources
|
||||
|
||||
@@ -53,6 +53,12 @@ async def scan_platforms(
|
||||
metadata_sources (list[str], optional): List of metadata sources to be used. Defaults to all sources.
|
||||
"""
|
||||
|
||||
if not selected_roms:
|
||||
selected_roms = []
|
||||
|
||||
if not metadata_sources:
|
||||
metadata_sources = ["igdb", "moby"]
|
||||
|
||||
sm = _get_socket_manager()
|
||||
|
||||
if not IGDB_API_ENABLED and not MOBY_API_ENABLED:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from decorators.auth import protected_route
|
||||
from endpoints.responses import MessageResponse
|
||||
from endpoints.responses.assets import StateSchema, UploadedStatesResponse
|
||||
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
|
||||
from fastapi import APIRouter, HTTPException, Request, UploadFile, status
|
||||
from handler.database import db_rom_handler, db_screenshot_handler, db_state_handler
|
||||
from handler.filesystem import fs_asset_handler
|
||||
from handler.scan_handler import scan_state
|
||||
@@ -14,7 +14,7 @@ router = APIRouter()
|
||||
def add_states(
|
||||
request: Request,
|
||||
rom_id: int,
|
||||
states: list[UploadFile] = File(...),
|
||||
states: list[UploadFile] | None = None,
|
||||
emulator: str = None,
|
||||
) -> UploadedStatesResponse:
|
||||
rom = db_rom_handler.get_roms(rom_id)
|
||||
@@ -128,10 +128,12 @@ async def delete_states(request: Request) -> MessageResponse:
|
||||
fs_asset_handler.remove_file(
|
||||
file_name=state.file_name, file_path=state.file_path
|
||||
)
|
||||
except FileNotFoundError:
|
||||
except FileNotFoundError as exc:
|
||||
error = f"Save file {state.file_name} not found for platform {state.rom.platform_slug}"
|
||||
log.error(error)
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND, detail=error
|
||||
) from exc
|
||||
|
||||
if state.screenshot:
|
||||
db_screenshot_handler.delete_screenshot(state.screenshot.id)
|
||||
|
||||
@@ -131,8 +131,8 @@ class OAuthHandler:
|
||||
|
||||
try:
|
||||
payload = jwt.decode(token, ROMM_AUTH_SECRET_KEY)
|
||||
except (BadSignatureError, ValueError):
|
||||
raise OAuthCredentialsException
|
||||
except (BadSignatureError, ValueError) as exc:
|
||||
raise OAuthCredentialsException from exc
|
||||
|
||||
issuer = payload.claims.get("iss")
|
||||
if not issuer or issuer != "romm:oauth":
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import fnmatch
|
||||
import os
|
||||
import re
|
||||
from abc import ABC
|
||||
from enum import Enum
|
||||
from typing import Final
|
||||
|
||||
@@ -84,10 +83,7 @@ class Asset(Enum):
|
||||
SCREENSHOTS = "screenshots"
|
||||
|
||||
|
||||
class FSHandler(ABC):
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
|
||||
class FSHandler:
|
||||
def get_roms_fs_structure(self, fs_slug: str) -> str:
|
||||
cnfg = cm.get_config()
|
||||
return (
|
||||
|
||||
@@ -55,8 +55,8 @@ class FSFirmwareHandler(FSHandler):
|
||||
"crc_hash": (binascii.crc32(data) & 0xFFFFFFFF)
|
||||
.to_bytes(4, byteorder="big")
|
||||
.hex(),
|
||||
"md5_hash": hashlib.md5(data).hexdigest(),
|
||||
"sha1_hash": hashlib.sha1(data).hexdigest(),
|
||||
"md5_hash": hashlib.md5(data, usedforsecurity=False).hexdigest(),
|
||||
"sha1_hash": hashlib.sha1(data, usedforsecurity=False).hexdigest(),
|
||||
}
|
||||
|
||||
def file_exists(self, path: str, file_name: str):
|
||||
|
||||
@@ -33,8 +33,8 @@ class FSPlatformsHandler(FSHandler):
|
||||
parents=True
|
||||
)
|
||||
)
|
||||
except FileExistsError:
|
||||
raise PlatformAlreadyExistsException(fs_slug)
|
||||
except FileExistsError as exc:
|
||||
raise PlatformAlreadyExistsException(fs_slug) from exc
|
||||
|
||||
def get_platforms(self) -> list[str]:
|
||||
"""Gets all filesystem platforms
|
||||
|
||||
@@ -95,12 +95,12 @@ class FSResourcesHandler(FSHandler):
|
||||
stream=True,
|
||||
timeout=120,
|
||||
)
|
||||
except requests.exceptions.ConnectionError:
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
log.critical("Connection error: can't connect to IGDB")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Can't connect to IGDB, check your internet connection.",
|
||||
)
|
||||
) from exc
|
||||
|
||||
if res.status_code == 200:
|
||||
Path(cover_path).mkdir(parents=True, exist_ok=True)
|
||||
@@ -191,12 +191,12 @@ class FSResourcesHandler(FSHandler):
|
||||
|
||||
try:
|
||||
res = requests.get(url, stream=True, timeout=120)
|
||||
except requests.exceptions.ConnectionError:
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
log.critical("Connection error: can't connect to IGDB")
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Can't connect to IGDB, check your internet connection.",
|
||||
)
|
||||
) from exc
|
||||
|
||||
if res.status_code == 200:
|
||||
Path(screenshot_path).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
@@ -130,8 +130,15 @@ class FSRomsHandler(FSHandler):
|
||||
]
|
||||
|
||||
def get_rom_file_size(
|
||||
self, roms_path: str, file_name: str, multi: bool, multi_files: list = []
|
||||
self,
|
||||
roms_path: str,
|
||||
file_name: str,
|
||||
multi: bool,
|
||||
multi_files: list[str] | None = None,
|
||||
):
|
||||
if multi_files is None:
|
||||
multi_files = []
|
||||
|
||||
files = (
|
||||
[f"{LIBRARY_BASE_PATH}/{roms_path}/{file_name}"]
|
||||
if not multi
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import subprocess as sp
|
||||
import subprocess as sp # nosec B404
|
||||
|
||||
import requests
|
||||
from __version__ import __version__
|
||||
@@ -18,7 +18,9 @@ class GithubHandler:
|
||||
else:
|
||||
try:
|
||||
output = str(
|
||||
sp.check_output(["git", "branch"], universal_newlines=True)
|
||||
sp.check_output(
|
||||
["git", "branch"], universal_newlines=True
|
||||
) # nosec B603, B607
|
||||
)
|
||||
except (sp.CalledProcessError, FileNotFoundError):
|
||||
return "1.0.0"
|
||||
|
||||
@@ -168,12 +168,12 @@ class IGDBBaseHandler(MetadataHandler):
|
||||
|
||||
res.raise_for_status()
|
||||
return res.json()
|
||||
except requests.exceptions.ConnectionError:
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
log.critical("Connection error: can't connect to IGDB", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Can't connect to IGDB, check your internet connection",
|
||||
)
|
||||
) from exc
|
||||
except HTTPError as err:
|
||||
# Retry once if the auth token is invalid
|
||||
if err.response.status_code != 401:
|
||||
@@ -509,11 +509,11 @@ class IGDBBaseHandler(MetadataHandler):
|
||||
|
||||
class TwitchAuth:
|
||||
def _update_twitch_token(self) -> str:
|
||||
token = ""
|
||||
token = None
|
||||
expires_in = 0
|
||||
|
||||
if not IGDB_API_ENABLED:
|
||||
return token
|
||||
return ""
|
||||
|
||||
try:
|
||||
res = requests.post(
|
||||
@@ -528,16 +528,16 @@ class TwitchAuth:
|
||||
|
||||
if res.status_code == 400:
|
||||
log.critical("IGDB Error: Invalid IGDB_CLIENT_ID or IGDB_CLIENT_SECRET")
|
||||
return token
|
||||
return ""
|
||||
else:
|
||||
token = res.json().get("access_token", "")
|
||||
expires_in = res.json().get("expires_in", 0)
|
||||
except requests.exceptions.ConnectionError:
|
||||
log.critical("Can't connect to IGDB, check your internet connection.")
|
||||
return token
|
||||
return ""
|
||||
|
||||
if not token or expires_in == 0:
|
||||
return token
|
||||
return ""
|
||||
|
||||
# Set token in redis to expire in <expires_in> seconds
|
||||
cache.set("romm:twitch_token", token, ex=expires_in - 10) # type: ignore[attr-defined]
|
||||
|
||||
@@ -81,12 +81,12 @@ class MobyGamesHandler(MetadataHandler):
|
||||
res = requests.get(authorized_url, timeout=timeout)
|
||||
res.raise_for_status()
|
||||
return res.json()
|
||||
except requests.exceptions.ConnectionError:
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
log.critical("Connection error: can't connect to Mobygames", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
|
||||
detail="Can't connect to Mobygames, check your internet connection",
|
||||
)
|
||||
) from exc
|
||||
except HTTPError as err:
|
||||
if err.response.status_code == 401:
|
||||
# Sometimes Mobygames returns 401 even with a valid API key
|
||||
|
||||
@@ -14,7 +14,9 @@ class SGDBBaseHandler:
|
||||
|
||||
def get_details(self, term):
|
||||
search_response = requests.get(
|
||||
f"{self.BASE_URL}/search/autocomplete/{term}", headers=self.headers
|
||||
f"{self.BASE_URL}/search/autocomplete/{term}",
|
||||
headers=self.headers,
|
||||
timeout=120,
|
||||
).json()
|
||||
|
||||
if len(search_response["data"]) == 0:
|
||||
@@ -25,7 +27,7 @@ class SGDBBaseHandler:
|
||||
game_name = search_response["data"][0]["name"]
|
||||
|
||||
game_response = requests.get(
|
||||
f"{self.BASE_URL}/grid/game/{game_id}", headers=self.headers
|
||||
f"{self.BASE_URL}/grid/game/{game_id}", headers=self.headers, timeout=120
|
||||
).json()
|
||||
|
||||
if len(game_response["data"]) == 0:
|
||||
|
||||
@@ -150,8 +150,11 @@ async def scan_rom(
|
||||
rom_attrs: dict,
|
||||
scan_type: ScanType,
|
||||
rom: Rom | None = None,
|
||||
metadata_sources: list[str] = ["igdb", "moby"],
|
||||
metadata_sources: list[str] | None = None,
|
||||
) -> Rom:
|
||||
if not metadata_sources:
|
||||
metadata_sources = ["igdb", "moby"]
|
||||
|
||||
roms_path = fs_rom_handler.get_roms_fs_structure(platform.fs_slug)
|
||||
|
||||
log.info(f"\t · {rom_attrs['file_name']}")
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
from handler.auth.base_handler import DEFAULT_SCOPES, FULL_SCOPES, WRITE_SCOPES
|
||||
from models.user import User
|
||||
|
||||
|
||||
def test_admin(admin_user):
|
||||
admin_user.oauth_scopes == FULL_SCOPES
|
||||
def test_admin(admin_user: User):
|
||||
assert admin_user.oauth_scopes == FULL_SCOPES
|
||||
|
||||
|
||||
def test_editor(editor_user):
|
||||
editor_user.oauth_scopes == WRITE_SCOPES
|
||||
def test_editor(editor_user: User):
|
||||
assert editor_user.oauth_scopes == WRITE_SCOPES
|
||||
|
||||
|
||||
def test_user(viewer_user):
|
||||
viewer_user.oauth_scopes == DEFAULT_SCOPES
|
||||
def test_user(viewer_user: User):
|
||||
assert viewer_user.oauth_scopes == DEFAULT_SCOPES
|
||||
|
||||
@@ -84,7 +84,7 @@ class RemoteFilePullTask(PeriodicTask):
|
||||
log.info(f"Scheduled {self.description} started...")
|
||||
|
||||
try:
|
||||
response = requests.get(self.url)
|
||||
response = requests.get(self.url, timeout=120)
|
||||
response.raise_for_status()
|
||||
return response.content
|
||||
except requests.exceptions.RequestException as e:
|
||||
|
||||
@@ -17,7 +17,7 @@ RUN mkdir -p ${WEBSERVER_FOLDER}/assets/romm && \
|
||||
ln -s /romm/assets ${WEBSERVER_FOLDER}/assets/romm/assets
|
||||
|
||||
# install generall required packages
|
||||
RUN apk add --upgrade \
|
||||
RUN apk add --no-cache --upgrade \
|
||||
bash \
|
||||
curl \
|
||||
libffi \
|
||||
@@ -27,7 +27,7 @@ RUN apk add --upgrade \
|
||||
tzdata
|
||||
|
||||
# Install additional build dependencies
|
||||
RUN apk add --upgrade \
|
||||
RUN apk add --no-cache --upgrade \
|
||||
gcc \
|
||||
libffi-dev \
|
||||
mariadb-connector-c-dev \
|
||||
@@ -78,7 +78,7 @@ ENV REDIS_VERSION 7.2.4
|
||||
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-7.2.4.tar.gz
|
||||
ENV REDIS_DOWNLOAD_SHA 8d104c26a154b29fd67d6568b4f375212212ad41e0c2caa3d66480e78dbd3b59
|
||||
|
||||
RUN wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \
|
||||
RUN wget --no-cache -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \
|
||||
echo "$REDIS_DOWNLOAD_SHA *redis.tar.gz" | sha256sum -c -; \
|
||||
mkdir -p /usr/src/redis; \
|
||||
tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
branch_name="$(git symbolic-ref HEAD 2>/dev/null)"
|
||||
branch_name=${branch_name##refs/heads/}
|
||||
docker build -t rommapp/romm:local-${branch_name} . --file ./docker/Dockerfile
|
||||
docker build -t "rommapp/romm:local-${branch_name}" . --file ./docker/Dockerfile
|
||||
|
||||
@@ -6,6 +6,7 @@ set -o pipefail # treat errors in pipes as fatal
|
||||
shopt -s inherit_errexit # inherit errexit
|
||||
|
||||
# use virtualenvs
|
||||
# shellcheck disable=SC1091
|
||||
source /backend/bin/activate
|
||||
|
||||
# make it possible to disable the inotify watcher process
|
||||
@@ -17,19 +18,19 @@ INIT_DEBUG="${INIT_DEBUG:="false"}"
|
||||
|
||||
# print debug log output if enabled
|
||||
debug_log() {
|
||||
if [ "${INIT_DEBUG}" == "true" ]; then
|
||||
echo "DEBUG: [init][$(date +"%Y-%m-%d %T")]" "${@}"
|
||||
if [[ ${INIT_DEBUG} == "true" ]]; then
|
||||
echo "DEBUG: [init][$(date +"%Y-%m-%d %T")]" "${@}" || true
|
||||
fi
|
||||
}
|
||||
|
||||
# print debug log output if enabled
|
||||
info_log() {
|
||||
echo "INFO: [init][$(date +"%Y-%m-%d %T")]" "${@}"
|
||||
echo "INFO: [init][$(date +"%Y-%m-%d %T")]" "${@}" || true
|
||||
}
|
||||
|
||||
# print error log output if enabled
|
||||
error_log() {
|
||||
echo "ERROR: [init][$(date +"%Y-%m-%d %T")]" "${@}"
|
||||
echo "ERROR: [init][$(date +"%Y-%m-%d %T")]" "${@}" || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
@@ -53,7 +54,7 @@ start_bin_gunicorn() {
|
||||
# Commands to start nginx (handling PID creation internally)
|
||||
start_bin_nginx() {
|
||||
info_log "starting nginx"
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
if [[ ${EUID} -ne 0 ]]; then
|
||||
nginx
|
||||
else
|
||||
# if container runs as root, drop permissions
|
||||
@@ -65,13 +66,13 @@ 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
|
||||
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
|
||||
echo "${REDIS_PID}" >/tmp/redis-server.pid
|
||||
}
|
||||
|
||||
# function that runs our independent python scripts and creates corresponding PID files,
|
||||
@@ -80,27 +81,28 @@ start_python() {
|
||||
info_log "starting ${SCRIPT}.py"
|
||||
python3 "${SCRIPT}.py" &
|
||||
WATCHER_PID=$!
|
||||
echo $WATCHER_PID >"/tmp/${SCRIPT}.pid"
|
||||
echo "${WATCHER_PID}" >"/tmp/${SCRIPT}.pid"
|
||||
}
|
||||
|
||||
watchdog_process_pid() {
|
||||
TYPE=$1
|
||||
PROCESS=$2
|
||||
if [ -f "/tmp/${PROCESS}.pid" ]; then
|
||||
if [[ -f "/tmp/${PROCESS}.pid" ]]; then
|
||||
# check if the pid we last wrote to our state file is actually active
|
||||
if [ -d "/proc/$(cat "/tmp/${PROCESS}.pid")" ]; then
|
||||
PID=$(cat "/tmp/${PROCESS}.pid") || true
|
||||
if [[ -d "/proc/${PID}" ]]; then
|
||||
debug_log "${PROCESS} still running, no need to start"
|
||||
else
|
||||
if [ "${TYPE}" == "bin" ]; then
|
||||
if [[ ${TYPE} == "bin" ]]; then
|
||||
start_bin_"${PROCESS}"
|
||||
elif [ "${TYPE}" == "python" ]; then
|
||||
elif [[ ${TYPE} == "python" ]]; then
|
||||
start_python "${PROCESS}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if [ "${TYPE}" == "bin" ]; then
|
||||
if [[ ${TYPE} == "bin" ]]; then
|
||||
start_bin_"${PROCESS}"
|
||||
elif [ "${TYPE}" == "python" ]; then
|
||||
elif [[ ${TYPE} == "python" ]]; then
|
||||
start_python "${PROCESS}"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
exclude:
|
||||
# Exclude platforms to be scanned
|
||||
platforms:
|
||||
- "romm"
|
||||
- romm
|
||||
|
||||
# Exclude roms or parts of roms to be scanned
|
||||
roms:
|
||||
@@ -10,11 +10,11 @@ exclude:
|
||||
single_file:
|
||||
# Exclude all files with certain extensions to be scanned
|
||||
extensions:
|
||||
- "xml"
|
||||
- xml
|
||||
# Exclude matched file names to be scanned
|
||||
names:
|
||||
- "info.txt"
|
||||
- "._*" # Supports unix filename pattern matching
|
||||
- info.txt
|
||||
- ._* # Supports unix filename pattern matching
|
||||
- "*.nfo" # Can also exclude files by extension
|
||||
|
||||
## Multi files games section
|
||||
@@ -22,30 +22,30 @@ exclude:
|
||||
multi_file:
|
||||
# Exclude matched 'folder' names to be scanned (RomM identifies folders as multi file games)
|
||||
names:
|
||||
- "my_multi_file_game"
|
||||
- "DLC"
|
||||
- my_multi_file_game
|
||||
- DLC
|
||||
# Exclude files within sub-folders.
|
||||
parts:
|
||||
# Exclude matched file names to be scanned from multi file roms
|
||||
# Keep in mind that RomM doesn't scan folders inside multi files games,
|
||||
# so there is no need to exclude folders from inside of multi files games.
|
||||
names:
|
||||
- "data.xml"
|
||||
- "._*" # Supports unix filename pattern matching
|
||||
- data.xml
|
||||
- ._* # Supports unix filename pattern matching
|
||||
# Exclude all files with certain extensions to be scanned from multi file roms
|
||||
extensions:
|
||||
- "txt"
|
||||
- txt
|
||||
|
||||
system:
|
||||
# Asociate different platform names to your current file system platform names
|
||||
platforms:
|
||||
# [your custom platform folder name]: [RomM platform name]
|
||||
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
|
||||
gc: ngc # In this example if you have a 'gc' folder, RomM will treat it like the 'ngc' folder
|
||||
psx: ps
|
||||
# Asociate one platform to it's main version
|
||||
versions:
|
||||
naomi: "arcade"
|
||||
naomi: arcade
|
||||
|
||||
filesystem:
|
||||
# The folder name where your roms are located
|
||||
roms_folder: "roms" # For example if your folder structure is /home/user/library/roms_folder
|
||||
roms_folder: roms # For example if your folder structure is /home/user/library/roms_folder
|
||||
|
||||
Reference in New Issue
Block a user