standarized GET endpoints for roms and platforms

This commit is contained in:
Zurdi
2024-01-14 01:12:07 +01:00
parent 93f6cb7651
commit 1d2c9e7d05
25 changed files with 368 additions and 248 deletions

View File

@@ -0,0 +1,23 @@
import functools
from fastapi import HTTPException, status
from logger.logger import log
from sqlalchemy.exc import ProgrammingError
def begin_session(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if hasattr(kwargs, "session"):
return func(*args, **kwargs)
try:
with args[0].session.begin() as s:
return func(*args, **kwargs, session=s)
except ProgrammingError as e:
log.critical(str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)
)
return wrapper

View File

@@ -1,7 +1,7 @@
from pathlib import Path
from config.config_manager import config_manager as cm
from decorators.oauth import protected_route
from decorators.auth import protected_route
from endpoints.responses.assets import (
SaveSchema,
StateSchema,
@@ -9,7 +9,7 @@ from endpoints.responses.assets import (
UploadedStatesResponse,
)
from fastapi import APIRouter, File, HTTPException, Request, UploadFile, status
from handler import dbh, romh
from handler import dbh, fsromh
from handler.scan_handler import scan_save, scan_state
from logger.logger import log
@@ -42,7 +42,7 @@ def upload_saves(
detail="No saves were uploaded",
)
saves_path = romh.build_upload_file_path(
saves_path = fsromh.build_upload_file_path(
rom.platform.fs_slug, folder=cm.config.SAVES_FOLDER_NAME
)
@@ -90,7 +90,7 @@ async def delete_saves(request: Request) -> list[SaveSchema]:
log.info(f"Deleting {save.file_name} from filesystem")
try:
romh.remove_file(file_name=save.file_name, file_path=save.file_path)
fsromh.remove_file(file_name=save.file_name, file_path=save.file_path)
except FileNotFoundError:
error = f"Save file {save.file_name} not found for platform {save.platform_slug}"
log.error(error)
@@ -113,7 +113,7 @@ def upload_states(
detail="No states were uploaded",
)
states_path = romh.build_upload_file_path(
states_path = fsromh.build_upload_file_path(
rom.platform.fs_slug, folder=cm.config.STATES_FOLDER_NAME
)
@@ -160,7 +160,7 @@ async def delete_states(request: Request) -> list[StateSchema]:
if delete_from_fs:
log.info(f"Deleting {state.file_name} from filesystem")
try:
romh.remove_file(file_name=state.file_name, file_path=state.file_path)
fsromh.remove_file(file_name=state.file_name, file_path=state.file_path)
except FileNotFoundError:
error = f"Save file {state.file_name} not found for platform {state.platform_slug}"
log.error(error)

View File

@@ -0,0 +1,33 @@
from config.config_manager import config_manager as cm
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from fastapi import APIRouter, Request
router = APIRouter()
@protected_route(router.put, "/config/system/platforms", ["platforms.write"])
async def add_platform_binding(request: Request) -> MessageResponse:
"""Add platform binding to the configuration"""
data = await request.form()
fs_slug = data.get("fs_slug")
slug = data.get("slug")
cm.add_binding(fs_slug, slug)
return {"msg": f"{fs_slug} binded to: {slug} successfully!"}
@protected_route(router.patch, "/config/system/platforms", ["platforms.write"])
async def delete_platform_binding(request: Request) -> MessageResponse:
"""Delete platform binding from the configuration"""
data = await request.form()
fs_slug = data.get("fs_slug")
cm.remove_binding(fs_slug)
return {"msg": f"{fs_slug} bind removed successfully!"}

View File

@@ -2,14 +2,14 @@ import secrets
from typing import Annotated
from config import ROMM_AUTH_ENABLED
from decorators.oauth import protected_route
from decorators.auth import protected_route
from endpoints.forms.identity import UserForm
from endpoints.responses import MessageResponse
from endpoints.responses.identity import UserSchema
from exceptions.auth_exceptions import AuthCredentialsException, DisabledException
from fastapi import APIRouter, Depends, HTTPException, Request, status
from fastapi.security.http import HTTPBasic
from handler import authh, dbh, resourceh
from handler import authh, dbh, fsresourceh
from handler.redis_handler import cache
from models.user import Role, User
@@ -207,7 +207,7 @@ def update_user(
cleaned_data["enabled"] = form_data.enabled # type: ignore[assignment]
if form_data.avatar is not None:
cleaned_data["avatar_path"], avatar_user_path = resourceh.build_avatar_path(
cleaned_data["avatar_path"], avatar_user_path = fsresourceh.build_avatar_path(
form_data.avatar.filename, form_data.username
)
file_location = f"{avatar_user_path}/{form_data.avatar.filename}"

View File

@@ -1,80 +1,82 @@
from config.config_manager import config_manager as cm
from decorators.oauth import protected_route
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from endpoints.responses.platform import PlatformSchema
from fastapi import APIRouter, HTTPException, Request, status
from handler import dbh
from handler import dbplatformh
from logger.logger import log
router = APIRouter()
@protected_route(router.post, "/platforms", ["platforms.write"])
def add_platform(request: Request) -> MessageResponse:
"""Create platform endpoint
Args:
request (Request): Fastapi Request object
Returns:
MessageResponse: Standard message response
"""
pass
@protected_route(router.get, "/platforms", ["platforms.read"])
def get_platforms(request: Request) -> list[PlatformSchema]:
"""Get platforms endpoint
Args:
request (Request): Fastapi Request object
id (int, optional): Platform id. Defaults to None.
Returns:
list[PlatformSchema]: All platforms in the database
list[PlatformSchema]: List of platforms
"""
return dbh.get_platform()
return dbplatformh.get_platforms()
@protected_route(router.get, "/platforms/{id}", ["platforms.read"])
def get_platforms(request: Request, id: int = None) -> PlatformSchema:
"""Get platform endpoint
def get_platforms(request: Request, id: int) -> PlatformSchema:
"""Get platforms endpoint
Args:
request (Request): Fastapi Request object
id (int, optional): Platform id. Defaults to None.
Returns:
PlatformSchema: Platform
"""
return dbplatformh.get_platforms(id)
@protected_route(router.put, "/platforms/{id}", ["platforms.write"])
def update_platform(request: Request, form) -> MessageResponse:
"""Update platform endpoint
Args:
request (Request): Fastapi Request object
Returns:
PlatformSchema: All platforms in the database
MessageResponse: Standard message response
"""
return dbh.get_platform(id)
pass
@protected_route(router.delete, "/platforms/{id}", ["platforms.write"])
def delete_platforms(request: Request, id: int) -> MessageResponse:
"""Detele platform from database [and filesystem]"""
platform = dbh.get_platform(id)
platform = dbplatformh.get_platforms(id)
if not platform:
error = f"Platform {platform.name} - [{platform.fs_slug}] not found"
log.error(error)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
log.info(f"Deleting {platform.name} [{platform.fs_slug}] from database")
dbh.delete_platform(platform.id)
dbplatformh.delete_platform(id)
return {"msg": f"{platform.name} - [{platform.fs_slug}] deleted successfully!"}
@protected_route(router.put, "/config/system/platforms", ["platforms.write"])
async def add_platform_binding(request: Request) -> MessageResponse:
"""Add platform binding to the configuration"""
data = await request.form()
fs_slug = data.get("fs_slug")
slug = data.get("slug")
cm.add_binding(fs_slug, slug)
return {"msg": f"{fs_slug} binded to: {slug} successfully!"}
@protected_route(router.patch, "/config/system/platforms", ["platforms.write"])
async def delete_platform_binding(request: Request) -> MessageResponse:
"""Delete platform binding from the configuration"""
data = await request.form()
fs_slug = data.get("fs_slug")
cm.remove_binding(fs_slug)
return {"msg": f"{fs_slug} bind removed successfully!"}

View File

@@ -4,7 +4,7 @@ from stat import S_IFREG
from typing import Annotated, Optional
from config import LIBRARY_BASE_PATH
from decorators.oauth import protected_route
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from endpoints.responses.rom import (
CustomStreamingResponse,
@@ -17,7 +17,7 @@ from fastapi import APIRouter, File, HTTPException, Query, Request, UploadFile,
from fastapi.responses import FileResponse
from fastapi_pagination.cursor import CursorPage, CursorParams
from fastapi_pagination.ext.sqlalchemy import paginate
from handler import asseth, dbh, romh
from handler import dbplatformh, dbromh, fsasseth, fsresourceh, fsromh
from logger.logger import log
from models import Rom
from stream_zip import ZIP_64, stream_zip # type: ignore[import]
@@ -25,8 +25,49 @@ from stream_zip import ZIP_64, stream_zip # type: ignore[import]
router = APIRouter()
@protected_route(router.post, "/roms", ["roms.write"])
def add_rom(request: Request) -> MessageResponse:
"""Create rom endpoint
Args
request (Request): Fastapi Request object
Returns:
MessageResponse: Standard message response
"""
pass
@protected_route(router.get, "/roms", ["roms.read"])
def get_roms(
request: Request,
platform_id: int = None,
size: int = 60,
cursor: str = "",
search_term: str = "",
order_by: str = "name",
order_dir: str = "asc",
) -> CursorPage[RomSchema]:
"""Get roms endpoint
Args:
request (Request): Fastapi Request object
id (int, optional): Rom internal id
Returns:
EnhancedRomSchema: Rom stored in RomM's database
"""
with dbromh.session.begin() as session:
cursor_params = CursorParams(size=size, cursor=cursor)
qq = dbromh.get_roms(platform_id, search_term, order_by, order_dir)
return paginate(session, qq, cursor_params)
@protected_route(router.get, "/roms/{id}", ["roms.read"])
def rom(request: Request, id: int) -> EnhancedRomSchema:
def get_rom(request: Request, id: int) -> EnhancedRomSchema:
"""Get rom endpoint
Args:
@@ -37,61 +78,12 @@ def rom(request: Request, id: int) -> EnhancedRomSchema:
EnhancedRomSchema: Rom stored in RomM's database
"""
return dbh.get_rom(id)
@protected_route(router.get, "/roms-recent", ["roms.read"])
def recent_roms(request: Request) -> list[RomSchema]:
"""Get recent roms endpoint
Args:
request (Request): Fastapi Request object
Returns:
list[RomSchema]: List of the last 15 stored roms in RomM's database
"""
return dbh.get_recent_roms()
@protected_route(router.get, "/platforms/{platform_slug}/roms", ["roms.read"])
def roms(
request: Request,
platform_slug: str,
size: int = 60,
cursor: str = "",
search_term: str = "",
) -> CursorPage[RomSchema]:
"""Get all roms for a specific platform endpoint (paginated)
Args:
request (Request): Fastapi Request object
platform_slug (str): Platform slug
size (int, optional): Size of each page. Defaults to 60.
cursor (str, optional): Cursor string. Defaults to "".
search_term (str, optional): Filter to search roms. Defaults to "".
Returns:
CursorPage[RomSchema]: Paged list of roms
"""
with dbh.session.begin() as session:
cursor_params = CursorParams(size=size, cursor=cursor)
qq = dbh.get_roms(platform_slug)
if search_term:
return paginate(
session,
qq.filter(Rom.file_name.ilike(f"%{search_term}%")),
cursor_params,
)
return paginate(session, qq, cursor_params)
return dbromh.get_roms(id)
@protected_route(router.put, "/roms/upload", ["roms.write"])
def upload_roms(
request: Request, platform_slug: str, roms: list[UploadFile] = File(...)
request: Request, platform_id: str, roms: list[UploadFile] = File(...)
) -> UploadRomResponse:
"""Upload roms endpoint (one or more at the same time)
@@ -107,7 +99,7 @@ def upload_roms(
UploadRomResponse: Standard message response
"""
platform_fs_slug = dbh.get_platform(platform_slug).fs_slug
platform_fs_slug = dbromh.get_platforms(platform_id).fs_slug
log.info(f"Uploading roms to {platform_fs_slug}")
if roms is None:
log.error("No roms were uploaded")
@@ -116,13 +108,13 @@ def upload_roms(
detail="No roms were uploaded",
)
roms_path = romh.build_upload_file_path(platform_fs_slug)
roms_path = fsromh.build_upload_file_path(platform_fs_slug)
uploaded_roms = []
skipped_roms = []
for rom in roms:
if romh.file_exists(roms_path, rom.filename):
if fsromh.file_exists(roms_path, rom.filename):
log.warning(f" - Skipping {rom.filename} since the file already exists")
skipped_roms.append(rom.filename)
continue
@@ -163,7 +155,7 @@ def download_rom(
CustomStreamingResponse: Streams a file for multi-part roms
"""
rom = dbh.get_rom(id)
rom = dbromh.get_roms(id)
rom_path = f"{LIBRARY_BASE_PATH}/{rom.full_path}"
if not rom.multi:
@@ -219,8 +211,8 @@ async def update_rom(
data = await request.form()
db_rom = dbh.get_rom(id)
platform_fs_slug = dbh.get_platform(db_rom.platform_slug).fs_slug
db_rom = dbromh.get_roms(id)
platform_fs_slug = dbplatformh.get_platforms(db_rom.platform_id).fs_slug
cleaned_data = {}
cleaned_data["igdb_id"] = data.get("igdb_id", db_rom.igdb_id) or None
@@ -242,7 +234,7 @@ async def update_rom(
try:
if db_rom.file_name != fs_safe_file_name:
romh.rename_file(
fsromh.rename_file(
old_name=db_rom.file_name,
new_name=fs_safe_file_name,
file_path=db_rom.file_path,
@@ -254,11 +246,11 @@ async def update_rom(
)
cleaned_data["file_name"] = fs_safe_file_name
cleaned_data["file_name_no_tags"] = romh.get_file_name_with_no_tags(
cleaned_data["file_name_no_tags"] = fsromh.get_file_name_with_no_tags(
fs_safe_file_name
)
cleaned_data.update(
asseth.get_rom_cover(
fsresourceh.get_rom_cover(
overwrite=True,
fs_slug=platform_fs_slug,
rom_name=cleaned_data["name"],
@@ -267,7 +259,7 @@ async def update_rom(
)
cleaned_data.update(
asseth.get_rom_screenshots(
fsasseth.get_rom_screenshots(
fs_slug=platform_fs_slug,
rom_name=cleaned_data["name"],
url_screenshots=cleaned_data.get("url_screenshots", []),
@@ -276,7 +268,7 @@ async def update_rom(
if artwork is not None:
file_ext = artwork.filename.split(".")[-1]
path_cover_l, path_cover_s, artwork_path = asseth.build_artwork_path(
path_cover_l, path_cover_s, artwork_path = fsresourceh.build_artwork_path(
cleaned_data["name"], platform_fs_slug, file_ext
)
@@ -292,12 +284,12 @@ async def update_rom(
with open(file_location_l, "wb+") as artwork_l:
artwork_l.write(artwork_file)
dbh.update_rom(id, cleaned_data)
dbromh.update_rom(id, cleaned_data)
return dbh.get_rom(id)
return dbromh.get_roms(id)
def _delete_single_rom(rom_id: int, delete_from_fs: bool = False) -> Rom:
def _delete_single_rom(id: int, delete_from_fs: bool = False) -> Rom:
"""Auxiliar function to delete one single rom at once
Args:
@@ -312,19 +304,19 @@ def _delete_single_rom(rom_id: int, delete_from_fs: bool = False) -> Rom:
Rom: Rom object
"""
rom = dbh.get_rom(rom_id)
rom = dbromh.get_rosm(id)
if not rom:
error = f"Rom with id {rom_id} not found"
error = f"Rom with id {id} not found"
log.error(error)
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=error)
log.info(f"Deleting {rom.file_name} from database")
dbh.delete_rom(rom_id)
dbromh.delete_rom(id)
if delete_from_fs:
log.info(f"Deleting {rom.file_name} from filesystem")
try:
romh.remove_file(file_name=rom.file_name, file_path=rom.file_path)
fsromh.remove_file(file_name=rom.file_name, file_path=rom.file_path)
except FileNotFoundError:
error = (
f"Rom file {rom.file_name} not found for platform {rom.platform_slug}"

View File

@@ -1,5 +1,5 @@
import emoji
from decorators.oauth import protected_route
from decorators.auth import protected_route
from endpoints.responses.search import RomSearchResponse
from fastapi import APIRouter, Request
from handler import dbh, igdbh

View File

@@ -7,7 +7,7 @@ from exceptions.fs_exceptions import (
FolderStructureNotMatchException,
RomsNotFoundException,
)
from handler import asseth, dbh, platformh, resourceh, romh, socketh
from handler import fsasseth, dbh, fsplatformh, fsresourceh, fsromh, socketh
from handler.redis_handler import high_prio_queue, redis_url
from handler.scan_handler import (
scan_platform,
@@ -43,7 +43,7 @@ async def scan_platforms(
# Scanning file system
try:
fs_platforms: list[str] = platformh.get_platforms()
fs_platforms: list[str] = fsplatformh.get_platforms()
except FolderStructureNotMatchException as e:
log.error(e)
await sm.emit("scan:done_ko", e.message)
@@ -71,7 +71,7 @@ async def scan_platforms(
# Scanning roms
try:
fs_roms = romh.get_roms(platform.fs_slug)
fs_roms = fsromh.get_roms(platform.fs_slug)
except RomsNotFoundException as e:
log.error(e)
continue
@@ -106,7 +106,7 @@ async def scan_platforms(
},
)
fs_assets = asseth.get_assets(platform.fs_slug)
fs_assets = fsasseth.get_assets(platform.fs_slug)
# Scanning saves
log.info(f"\t · {len(fs_assets['saves'])} saves found")
@@ -190,7 +190,7 @@ async def scan_platforms(
dbh.purge_roms(platform.id, [rom["file_name"] for rom in fs_roms])
# Scanning screenshots outside platform folders
fs_screenshots = asseth.get_screenshots()
fs_screenshots = fsasseth.get_screenshots()
log.info("Screenshots")
log.info(f" · {len(fs_screenshots)} screenshots found")
for fs_platform, fs_screenshot_filename in fs_screenshots:
@@ -231,7 +231,7 @@ async def scan_handler(_sid: str, options: dict):
"""
log.info(emoji.emojize(":magnifying_glass_tilted_right: Scanning "))
resourceh.store_default_resources()
fsresourceh.store_default_resources()
platform_slugs = options.get("platforms", [])
complete_rescan = options.get("completeRescan", False)

View File

@@ -1,4 +1,4 @@
from decorators.oauth import protected_route
from decorators.auth import protected_route
from endpoints.responses import MessageResponse
from fastapi import APIRouter, Request
from tasks.update_mame_xml import update_mame_xml_task

View File

@@ -1,5 +1,5 @@
from config import ROMM_HOST
from decorators.oauth import protected_route
from decorators.auth import protected_route
from endpoints.responses.webrcade import (
WEBRCADE_SLUG_TO_TYPE_MAP,
WEBRCADE_SUPPORTED_PLATFORM_SLUGS,

View File

@@ -1,9 +1,10 @@
from handler.auth_handler.auth_handler import AuthHandler, OAuthHandler
from handler.db_handler import DBHandler
from handler.fs_handler.assets_handler import AssetsHandler
from handler.fs_handler.platforms_handler import PlatformsHandler
from handler.fs_handler.resources_handler import ResourceHandler
from handler.fs_handler.roms_handler import RomsHandler
from handler.db_handler.db_platforms_handler import DBPlatformsHandler
from handler.db_handler.db_roms_handler import DBRomsHandler
from handler.fs_handler.fs_assets_handler import FSAssetsHandler
from handler.fs_handler.fs_platforms_handler import FSPlatformsHandler
from handler.fs_handler.fs_resources_handler import FSResourceHandler
from handler.fs_handler.fs_roms_handler import FSRomsHandler
from handler.gh_handler import GHHandler
from handler.igdb_handler import IGDBHandler
from handler.sgdb_handler import SGDBHandler
@@ -11,12 +12,17 @@ from handler.socket_handler import SocketHandler
igdbh = IGDBHandler()
sgdbh = SGDBHandler()
dbh = DBHandler()
ghh = GHHandler()
authh = AuthHandler()
oauthh = OAuthHandler()
socketh = SocketHandler()
platformh = PlatformsHandler()
romh = RomsHandler()
asseth = AssetsHandler()
resourceh = ResourceHandler()
dbplatformh = DBPlatformsHandler()
dbromh = DBRomsHandler()
fsplatformh = FSPlatformsHandler()
fsromh = FSRomsHandler()
fsasseth = FSAssetsHandler()
fsresourceh = FSResourceHandler()
from handler.db_handler.db_handler import DBHandler
dbh = DBHandler()

View File

View File

@@ -1,11 +1,7 @@
import functools
from config.config_manager import ConfigManager
from fastapi import HTTPException, status
from logger.logger import log
from models import Platform, Role, Rom, Save, Screenshot, State, User
from sqlalchemy import and_, create_engine, delete, func, or_, select, update
from sqlalchemy.exc import ProgrammingError
from decorators.database import begin_session
from models import Role, Rom, Save, Screenshot, State, User
from sqlalchemy import and_, create_engine, delete, func, select, update
from sqlalchemy.orm import Session, sessionmaker
@@ -14,84 +10,6 @@ class DBHandler:
self.engine = create_engine(ConfigManager.get_db_engine(), pool_pre_ping=True)
self.session = sessionmaker(bind=self.engine, expire_on_commit=False)
@staticmethod
def begin_session(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if hasattr(kwargs, "session"):
return func(*args, session=kwargs.get("session"))
try:
with args[0].session.begin() as s:
return func(*args, session=s)
except ProgrammingError as e:
log.critical(str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)
)
return wrapper
# ========= Platforms =========
@begin_session
def add_platform(self, platform: Platform, session: Session = None):
return session.merge(platform)
@begin_session
def get_platform(self, id: int = None, session: Session = None):
return (
session.scalars(select(Platform).order_by(Platform.slug.asc()))
.unique()
.all()
if not id
else session.get(Platform, id)
)
@begin_session
def delete_platform(self, slug: str, session: Session = None):
# Remove all roms from that platforms first
session.execute(
delete(Rom)
.where(Rom.platform_slug == slug)
.execution_options(synchronize_session="evaluate")
)
return session.execute(
delete(Platform)
.where(Platform.slug == slug)
.execution_options(synchronize_session="evaluate")
)
@begin_session
def get_rom_count(self, platform_id: int, session: Session = None):
return session.scalar(
select(func.count()).select_from(Rom).filter_by(platform_id=platform_id)
)
@begin_session
def purge_platforms(self, platforms: list[str], session: Session = None):
session.execute(
delete(Save)
.where(Save.platform_slug.not_in(platforms))
.execution_options(synchronize_session="evaluate")
)
session.execute(
delete(State)
.where(State.platform_slug.not_in(platforms))
.execution_options(synchronize_session="evaluate")
)
return session.execute(
delete(Platform)
.where(or_(Platform.fs_slug.not_in(platforms), Platform.slug.is_(None)))
.where(
select(func.count())
.select_from(Rom)
.filter_by(platform_slug=Platform.slug)
.as_scalar()
== 0
)
.execution_options(synchronize_session="fetch")
)
# ========= Roms =========
@begin_session
def add_rom(self, rom: Rom, session: Session = None):

View File

@@ -0,0 +1,69 @@
from models import Platform, Rom, Save, State
from sqlalchemy import delete, func, or_, select
from sqlalchemy.orm import Session
from decorators.database import begin_session
from handler.db_handler.db_handler import DBHandler
class DBPlatformsHandler(DBHandler):
@begin_session
def add_platform(self, platform: Platform, session: Session = None):
return session.merge(platform)
@begin_session
def get_platforms(self, id: int = None, session: Session = None):
return (
session.get(Platform, id)
if id
else (
session.scalars(select(Platform).order_by(Platform.name.asc()))
.unique()
.all()
)
)
@begin_session
def delete_platform(self, slug: str, session: Session = None):
# Remove all roms from that platforms first
session.execute(
delete(Rom)
.where(Rom.platform_slug == slug)
.execution_options(synchronize_session="evaluate")
)
return session.execute(
delete(Platform)
.where(Platform.slug == slug)
.execution_options(synchronize_session="evaluate")
)
@begin_session
def get_rom_count(self, platform_id: int, session: Session = None):
return session.scalar(
select(func.count()).select_from(Rom).filter_by(platform_id=platform_id)
)
@begin_session
def purge_platforms(self, platforms: list[str], session: Session = None):
session.execute(
delete(Save)
.where(Save.platform_slug.not_in(platforms))
.execution_options(synchronize_session="evaluate")
)
session.execute(
delete(State)
.where(State.platform_slug.not_in(platforms))
.execution_options(synchronize_session="evaluate")
)
return session.execute(
delete(Platform)
.where(or_(Platform.fs_slug.not_in(platforms), Platform.slug.is_(None)))
.where(
select(func.count())
.select_from(Rom)
.filter_by(platform_slug=Platform.slug)
.as_scalar()
== 0
)
.execution_options(synchronize_session="fetch")
)

View File

@@ -0,0 +1,76 @@
from decorators.database import begin_session
from handler.db_handler.db_handler import DBHandler
from models import Rom
from sqlalchemy import and_, delete, func, select, update
from sqlalchemy.orm import Session
class DBRomsHandler(DBHandler):
@staticmethod
def _filter(data, platform_id, search_term):
if platform_id:
data = data.filter_by(platform_id=platform_id)
if search_term:
data = data.filter(Rom.file_name.ilike(f"%{search_term}%"))
return data
@staticmethod
def _order(data, order_by, order_dir):
if order_by == "name":
_column = func.lower(Rom.name)
elif order_by == "id":
_column = Rom.id
else:
_column = func.lower(Rom.name)
if order_dir == "asc":
return data.order_by(_column.asc())
elif order_dir == "desc":
return data.order_by(_column.desc())
else:
return data.order_by(_column.asc())
@begin_session
def get_roms(
self,
id: int = None,
platform_id: int = None,
search_term: str = "",
order_by: str = "name",
order_dir: str = "asc",
session: Session = None,
):
return (
session.get(Rom, id)
if id
else self._order(
self._filter(select(Rom), platform_id, search_term),
order_by,
order_dir,
)
)
@begin_session
def update_rom(self, id: int, data: dict, session: Session = None):
return session.execute(
update(Rom)
.where(Rom.id == id)
.values(**data)
.execution_options(synchronize_session="evaluate")
)
@begin_session
def delete_rom(self, id: int, session: Session = None):
return session.execute(
delete(Rom)
.where(Rom.id == id)
.execution_options(synchronize_session="evaluate")
)
@begin_session
def purge_roms(self, platform_id: int, roms: list[str], session: Session = None):
return session.execute(
delete(Rom)
.where(and_(Rom.platform_id == platform_id, Rom.file_name.not_in(roms)))
.execution_options(synchronize_session="evaluate")
)

View File

@@ -10,7 +10,7 @@ from handler.fs_handler import RESOURCES_BASE_PATH
from handler.fs_handler.fs_handler import FSHandler
class AssetsHandler(FSHandler):
class FSAssetsHandler(FSHandler):
def __init__(self) -> None:
pass

View File

@@ -6,7 +6,7 @@ from exceptions.fs_exceptions import FolderStructureNotMatchException
from handler.fs_handler.fs_handler import FSHandler
class PlatformsHandler(FSHandler):
class FSPlatformsHandler(FSHandler):
def __init__(self) -> None:
pass

View File

@@ -23,7 +23,7 @@ from handler.fs_handler.fs_handler import FSHandler
from PIL import Image
class ResourceHandler(FSHandler):
class FSResourceHandler(FSHandler):
def __init__(self) -> None:
pass

View File

@@ -18,7 +18,7 @@ from handler.fs_handler import (
from handler.fs_handler.fs_handler import FSHandler
class RomsHandler(FSHandler):
class FSRomsHandler(FSHandler):
def __init__(self) -> None:
pass

View File

@@ -3,7 +3,7 @@ from typing import Any
import emoji
from config.config_manager import config_manager as cm
from handler import asseth, dbh, igdbh, resourceh, romh
from handler import fsasseth, dbh, igdbh, fsresourceh, fsromh
from logger.logger import log
from models import Platform, Rom, Save, Screenshot, State
@@ -62,7 +62,7 @@ async def scan_rom(
r_igbd_id_search: str = "",
overwrite: bool = False,
) -> Rom:
roms_path = romh.get_fs_structure(platform.fs_slug)
roms_path = fsromh.get_fs_structure(platform.fs_slug)
log.info(f"\t · {r_igbd_id_search or rom_attrs['file_name']}")
@@ -71,21 +71,21 @@ async def scan_rom(
log.info(f"\t\t · {file}")
# Update properties that don't require IGDB
file_size, file_size_units = romh.get_rom_file_size(
file_size, file_size_units = fsromh.get_rom_file_size(
multi=rom_attrs["multi"],
file_name=rom_attrs["file_name"],
multi_files=rom_attrs["files"],
roms_path=roms_path,
)
regs, rev, langs, other_tags = romh.parse_tags(rom_attrs["file_name"])
regs, rev, langs, other_tags = fsromh.parse_tags(rom_attrs["file_name"])
rom_attrs.update(
{
"file_path": roms_path,
"file_name": rom_attrs["file_name"],
"file_name_no_tags": romh.get_file_name_with_no_tags(
"file_name_no_tags": fsromh.get_file_name_with_no_tags(
rom_attrs["file_name"]
),
"file_extension": romh.parse_file_extension(rom_attrs["file_name"]),
"file_extension": fsromh.parse_file_extension(rom_attrs["file_name"]),
"file_size": file_size,
"file_size_units": file_size_units,
"multi": rom_attrs["multi"],
@@ -117,7 +117,7 @@ async def scan_rom(
# Update properties from IGDB
rom_attrs.update(
resourceh.get_rom_cover(
fsresourceh.get_rom_cover(
overwrite=overwrite,
platform_fs_slug=platform.slug,
rom_name=rom_attrs["name"],
@@ -125,7 +125,7 @@ async def scan_rom(
)
)
rom_attrs.update(
asseth.get_rom_screenshots(
fsasseth.get_rom_screenshots(
platform_fs_slug=platform.slug,
rom_name=rom_attrs["name"],
url_screenshots=rom_attrs["url_screenshots"],
@@ -138,19 +138,19 @@ async def scan_rom(
def _scan_asset(file_name: str, path: str):
log.info(f"\t\t · {file_name}")
file_size = asseth.get_asset_size(file_name=file_name, asset_path=path)
file_size = fsasseth.get_asset_size(file_name=file_name, asset_path=path)
return {
"file_path": path,
"file_name": file_name,
"file_name_no_tags": asseth.get_file_name_with_no_tags(file_name),
"file_extension": asseth.parse_file_extension(file_name),
"file_name_no_tags": fsasseth.get_file_name_with_no_tags(file_name),
"file_extension": fsasseth.parse_file_extension(file_name),
"file_size_bytes": file_size,
}
def scan_save(platform: Platform, file_name: str, emulator: str = None) -> Save:
saves_path = asseth.get_fs_structure(
saves_path = fsasseth.get_fs_structure(
platform.fs_slug, folder=cm.config.SAVES_FOLDER_NAME
)
@@ -162,7 +162,7 @@ def scan_save(platform: Platform, file_name: str, emulator: str = None) -> Save:
def scan_state(platform: Platform, file_name: str, emulator: str = None) -> State:
states_path = asseth.get_fs_structure(
states_path = fsasseth.get_fs_structure(
platform.fs_slug, folder=cm.config.STATES_FOLDER_NAME
)
@@ -174,7 +174,7 @@ def scan_state(platform: Platform, file_name: str, emulator: str = None) -> Stat
def scan_screenshot(file_name: str, platform: Platform = None) -> Screenshot:
screenshots_path = asseth.get_fs_structure(
screenshots_path = fsasseth.get_fs_structure(
platform.fs_slug, folder=cm.config.SCREENSHOTS_FOLDER_NAME
)

View File

@@ -5,7 +5,7 @@ import alembic.config
import uvicorn
from config import DEV_HOST, DEV_PORT, ROMM_AUTH_ENABLED, ROMM_AUTH_SECRET_KEY
from endpoints import (assets, heartbeat, identity, oauth, platform, rom,
search, tasks, webrcade)
search, tasks, webrcade, config)
from endpoints.sockets import scan
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
@@ -57,6 +57,7 @@ app.include_router(search.router)
app.include_router(assets.router)
app.include_router(tasks.router)
app.include_router(webrcade.router)
app.include_router(webrcade.router)
add_pagination(app)
app.mount("/ws", socketh.socket_app)

View File

@@ -22,9 +22,9 @@ class Platform(BaseModel):
@property
def rom_count(self) -> int:
from handler import dbh
from handler import dbplatformh
return dbh.get_rom_count(self.id)
return dbplatformh.get_rom_count(self.id)
def __repr__(self) -> str:
return self.name

View File

@@ -130,14 +130,14 @@ class Rom(BaseModel):
# This is an expensive operation so don't call it on a list of roms
@cached_property
def sibling_roms(self) -> list["Rom"]:
from handler import dbh
from handler import dbromh
if not self.igdb_id:
return []
with dbh.session.begin() as session:
with dbromh.session.begin() as session:
return session.scalars(
dbh.get_roms(self.platform_slug).filter(
dbromh.get_roms(platform_id=self.platform_id).filter(
Rom.id != self.id,
Rom.igdb_id == self.igdb_id,
)

View File

@@ -1,6 +1,6 @@
from unittest.mock import patch
from ...handler.fs_handler.roms_handler import (
from ...handler.fs_handler.fs_roms_handler import (
get_rom_cover,
get_platforms,
get_fs_structure,