diff --git a/backend/alembic/versions/0012_rom_assets.py b/backend/alembic/versions/0012_asset_files.py similarity index 53% rename from backend/alembic/versions/0012_rom_assets.py rename to backend/alembic/versions/0012_asset_files.py index d8e957fba..a250b8cf6 100644 --- a/backend/alembic/versions/0012_rom_assets.py +++ b/backend/alembic/versions/0012_asset_files.py @@ -1,8 +1,8 @@ """empty message -Revision ID: 0012_rom_assets +Revision ID: 0012_asset_files Revises: 0011_drop_has_cover -Create Date: 2023-11-12 23:51:15.578857 +Create Date: 2023-11-13 12:25:47.355434 """ from alembic import op @@ -10,7 +10,7 @@ import sqlalchemy as sa # revision identifiers, used by Alembic. -revision = "0012_rom_assets" +revision = "0012_asset_files" down_revision = "0011_drop_has_cover" branch_labels = None depends_on = None @@ -19,56 +19,82 @@ depends_on = None def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table( - "saves", + "bios", + sa.Column("platform_slug", sa.String(length=50), nullable=False), sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("rom_id", sa.Integer(), nullable=True), sa.Column("file_name", sa.String(length=450), nullable=False), sa.Column("file_name_no_tags", sa.String(length=450), nullable=False), sa.Column("file_extension", sa.String(length=10), nullable=False), sa.Column("file_path", sa.String(length=1000), nullable=False), sa.Column("file_size_bytes", sa.Integer(), nullable=False), sa.ForeignKeyConstraint( - ["rom_id"], - ["roms.id"], + ["platform_slug"], ["platforms.slug"], ondelete="CASCADE" ), sa.PrimaryKeyConstraint("id"), ) + op.create_table( + "saves", + sa.Column("rom_id", sa.Integer(), nullable=False), + sa.Column("platform_slug", sa.String(length=50), nullable=False), + sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), + sa.Column("file_name", sa.String(length=450), nullable=False), + sa.Column("file_name_no_tags", sa.String(length=450), nullable=False), + sa.Column("file_extension", sa.String(length=10), nullable=False), + sa.Column("file_path", sa.String(length=1000), nullable=False), + sa.Column("file_size_bytes", sa.Integer(), nullable=False), + sa.ForeignKeyConstraint( + ["platform_slug"], ["platforms.slug"], ondelete="CASCADE" + ), + sa.ForeignKeyConstraint(["rom_id"], ["roms.id"], ondelete="CASCADE"), + sa.PrimaryKeyConstraint("id"), + ) op.create_table( "screenshots", + sa.Column("rom_id", sa.Integer(), nullable=False), sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("rom_id", sa.Integer(), nullable=True), sa.Column("file_name", sa.String(length=450), nullable=False), sa.Column("file_name_no_tags", sa.String(length=450), nullable=False), sa.Column("file_extension", sa.String(length=10), nullable=False), sa.Column("file_path", sa.String(length=1000), nullable=False), sa.Column("file_size_bytes", sa.Integer(), nullable=False), - sa.ForeignKeyConstraint( - ["rom_id"], - ["roms.id"], - ), + sa.ForeignKeyConstraint(["rom_id"], ["roms.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) op.create_table( "states", + sa.Column("rom_id", sa.Integer(), nullable=False), + sa.Column("platform_slug", sa.String(length=50), nullable=False), sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), - sa.Column("rom_id", sa.Integer(), nullable=True), sa.Column("file_name", sa.String(length=450), nullable=False), sa.Column("file_name_no_tags", sa.String(length=450), nullable=False), sa.Column("file_extension", sa.String(length=10), nullable=False), sa.Column("file_path", sa.String(length=1000), nullable=False), sa.Column("file_size_bytes", sa.Integer(), nullable=False), sa.ForeignKeyConstraint( - ["rom_id"], - ["roms.id"], + ["platform_slug"], ["platforms.slug"], ondelete="CASCADE" ), + sa.ForeignKeyConstraint(["rom_id"], ["roms.id"], ondelete="CASCADE"), sa.PrimaryKeyConstraint("id"), ) + with op.batch_alter_table("roms", schema=None) as batch_op: + batch_op.drop_constraint("fk_platform_roms", type_="foreignkey") + batch_op.create_foreign_key( + None, "platforms", ["platform_slug"], ["slug"], ondelete="CASCADE" + ) + # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("roms", schema=None) as batch_op: + batch_op.drop_constraint(None, type_="foreignkey") + batch_op.create_foreign_key( + "fk_platform_roms", "platforms", ["platform_slug"], ["slug"] + ) + op.drop_table("states") op.drop_table("screenshots") op.drop_table("saves") + op.drop_table("bios") # ### end Alembic commands ### diff --git a/backend/config/__init__.py b/backend/config/__init__.py index 9403a5244..15e5dfe8e 100644 --- a/backend/config/__init__.py +++ b/backend/config/__init__.py @@ -10,10 +10,15 @@ DEV_PORT: Final = int(os.environ.get("VITE_BACKEND_DEV_PORT", "5000")) DEV_HOST: Final = "0.0.0.0" # PATHS -ROMM_BASE_PATH: Final = os.environ.get("ROMM_BASE_PATH", "/romm") ROMS_FOLDER_NAME: Final = os.environ.get("ROMS_FOLDER_NAME", "roms") SAVES_FOLDER_NAME: Final = os.environ.get("SAVES_FOLDER_NAME", "saves") STATES_FOLDER_NAME: Final = os.environ.get("STATES_FOLDER_NAME", "states") +SCREENSHOTS_FOLDER_NAME: Final = os.environ.get( + "SCREENSHOTS_FOLDER_NAME", "screenshots" +) +BIOS_FOLDER_NAME: Final = os.environ.get("BIOS_FOLDER_NAME", "bios") + +ROMM_BASE_PATH: Final = os.environ.get("ROMM_BASE_PATH", "/romm") LIBRARY_BASE_PATH: Final = f"{ROMM_BASE_PATH}/library" HIGH_PRIO_STRUCTURE_PATH: Final = f"{LIBRARY_BASE_PATH}/{ROMS_FOLDER_NAME}" diff --git a/backend/endpoints/rom.py b/backend/endpoints/rom.py index d705d8b82..4f10e3537 100644 --- a/backend/endpoints/rom.py +++ b/backend/endpoints/rom.py @@ -30,8 +30,8 @@ from utils.fs import ( build_artwork_path, build_upload_roms_path, rename_rom, - get_cover, - get_screenshots, + get_rom_cover, + get_rom_screenshots, remove_rom, ) @@ -240,7 +240,7 @@ async def update_rom( cleaned_data["file_name"] = fs_safe_file_name cleaned_data["file_name_no_tags"] = get_file_name_with_no_tags(fs_safe_file_name) cleaned_data.update( - get_cover( + get_rom_cover( overwrite=True, fs_slug=db_rom.platform_slug, rom_name=cleaned_data["name"], @@ -249,7 +249,7 @@ async def update_rom( ) cleaned_data.update( - get_screenshots( + get_rom_screenshots( fs_slug=db_rom.platform_slug, rom_name=cleaned_data["name"], url_screenshots=cleaned_data.get("url_screenshots", []), diff --git a/backend/endpoints/scan.py b/backend/endpoints/scan.py index 5b0f8066a..1937d8f73 100644 --- a/backend/endpoints/scan.py +++ b/backend/endpoints/scan.py @@ -4,10 +4,22 @@ import socketio # type: ignore from logger.logger import log from exceptions.fs_exceptions import PlatformsNotFoundException, RomsNotFoundException from handler import dbh -from utils import get_file_name_with_no_tags -from utils.fastapi import scan_platform, scan_rom, scan_save, scan_state +from utils.fastapi import ( + scan_platform, + scan_rom, + scan_save, + scan_state, + scan_bios, + scan_screenshot, +) from utils.socket import socket_server -from utils.fs import get_platforms, get_roms, store_default_resources, get_assets +from utils.fs import ( + get_platforms, + get_roms, + store_default_resources, + get_assets, + get_screenshots, +) from utils.redis import high_prio_queue, redis_url from endpoints.platform import PlatformSchema from endpoints.rom import RomSchema @@ -76,39 +88,74 @@ async def scan_platforms( }, ) - # Scanning assets fs_assets = get_assets(scanned_platform.fs_slug) - for fs_save in fs_assets["saves"]: - scanned_save = await scan_save( + + # Scanning saves + log.info("\t · Saves") + for fs_save_filename in fs_assets["saves"]: + save = dbh.get_save_by_filename(scanned_platform.slug, fs_save_filename) + if save: + continue + + scanned_save = scan_save( scanned_platform, - fs_save, - ) - - file_name_no_tags = get_file_name_with_no_tags(scanned_save.file_name) - rom = dbh.get_rom_by_filename_no_tags( - scanned_platform.slug, file_name_no_tags + fs_save_filename, ) + scanned_save.platform_slug = scanned_platform.slug + rom = dbh.get_rom_by_filename_no_tags(scanned_save.file_name_no_tags) if rom: scanned_save.rom_id = rom.id + dbh.add_save(scanned_save) - dbh.add_save(scanned_save) + # Scanning states + log.info("\t · States") + for fs_state_filename in fs_assets["states"]: + state = dbh.get_state_by_filename(scanned_platform.slug, fs_state_filename) + if state: + continue - for state in fs_assets["states"]: - scanned_state = await scan_state(scanned_platform, state) - - file_name_no_tags = get_file_name_with_no_tags(scanned_state.file_name) - rom = dbh.get_rom_by_filename_no_tags( - scanned_platform.slug, file_name_no_tags - ) + scanned_state = scan_state(scanned_platform, fs_state_filename) + scanned_state.platform_slug = scanned_platform.slug + rom = dbh.get_rom_by_filename_no_tags(scanned_state.file_name_no_tags) if rom: scanned_state.rom_id = rom.id + dbh.add_state(scanned_state) - dbh.add_state(scanned_state) + # Scanning bios + log.info("\t · Firmware") + for fs_bios_filename in fs_assets["bios"]: + bios = dbh.get_bios_by_filename(scanned_platform.slug, fs_bios_filename) + if bios: + continue + scanned_bios = scan_bios(scanned_platform, fs_bios_filename) + scanned_bios.platform_slug = scanned_platform.slug + dbh.add_bios(scanned_bios) + + dbh.purge_saves(scanned_platform.slug, fs_assets["saves"]) + dbh.purge_states(scanned_platform.slug, fs_assets["states"]) + dbh.purge_bios(scanned_platform.slug, fs_assets["bios"]) dbh.purge_roms(scanned_platform.slug, [rom["file_name"] for rom in fs_roms]) + + # Scanning screenshots + log.info("\t · Screenshots") + fs_screenshots = get_screenshots() + for fs_screenshot_filename in fs_screenshots: + screenshot = dbh.get_screenshot_by_filename(fs_screenshot_filename) + if screenshot: + continue + + scanned_screenshot = scan_screenshot(fs_screenshot_filename) + + rom = dbh.get_rom_by_filename_no_tags(scanned_screenshot.file_name_no_tags) + if rom: + scanned_screenshot.rom_id = rom.id + dbh.add_screenshot(scanned_screenshot) + dbh.purge_platforms(fs_platforms) + dbh.purge_screenshots(fs_screenshots) await sm.emit("scan:done", {}) diff --git a/backend/handler/db_handler.py b/backend/handler/db_handler.py index de6be1c41..5b7855329 100644 --- a/backend/handler/db_handler.py +++ b/backend/handler/db_handler.py @@ -7,7 +7,7 @@ from sqlalchemy.exc import ProgrammingError from logger.logger import log from config.config_loader import ConfigLoader -from models import Platform, Rom, User, Role, Save, State +from models import Platform, Rom, User, Role, Save, State, Screenshot, Bios class DBHandler: @@ -48,6 +48,21 @@ class DBHandler: @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") + ) + session.execute( + delete(Bios) + .where(Bios.platform_slug.not_in(platforms)) + .execution_options(synchronize_session="evaluate") + ) return session.execute( delete(Platform) .where(or_(Platform.slug.not_in(platforms), Platform.slug.is_(None))) @@ -118,32 +133,128 @@ class DBHandler: @begin_session def get_rom_by_filename_no_tags( - self, platform_slug: str, file_name_no_tags: str, session: Session = None + self, file_name_no_tags: str, session: Session = None ): return session.scalars( - select(Rom) - .filter_by(platform_slug=platform_slug, file_name_no_tags=file_name_no_tags) - .limit(1) + select(Rom).filter_by(file_name_no_tags=file_name_no_tags).limit(1) ).first() - + # ========= Saves ========= @begin_session def add_save(self, save: Rom, session: Session = None): return session.merge(save) - + @begin_session def get_save(self, id, session: Session = None): return session.get(Save, id) - + + @begin_session + def get_save_by_filename( + self, platform_slug: str, file_name: str, session: Session = None + ): + return session.scalars( + select(Save) + .filter_by(platform_slug=platform_slug, file_name=file_name) + .limit(1) + ).first() + + @begin_session + def purge_saves( + self, platform_slug: str, saves: list[str], session: Session = None + ): + return session.execute( + delete(Save) + .where( + and_(Save.platform_slug == platform_slug, Save.file_name.not_in(saves)) + ) + .execution_options(synchronize_session="evaluate") + ) + # ========= States ========= @begin_session def add_state(self, state: Rom, session: Session = None): return session.merge(state) - + @begin_session def get_state(self, id, session: Session = None): return session.get(State, id) + @begin_session + def get_state_by_filename( + self, platform_slug: str, file_name: str, session: Session = None + ): + return session.scalars( + select(State) + .filter_by(platform_slug=platform_slug, file_name=file_name) + .limit(1) + ).first() + + @begin_session + def purge_states( + self, platform_slug: str, states: list[str], session: Session = None + ): + return session.execute( + delete(State) + .where( + and_( + State.platform_slug == platform_slug, State.file_name.not_in(states) + ) + ) + .execution_options(synchronize_session="evaluate") + ) + + # ========= Bios ========= + @begin_session + def add_bios(self, bios: Rom, session: Session = None): + return session.merge(bios) + + @begin_session + def get_bios(self, id, session: Session = None): + return session.get(Bios, id) + + @begin_session + def get_bios_by_filename( + self, platform_slug: str, file_name: str, session: Session = None + ): + return session.scalars( + select(Bios) + .filter_by(platform_slug=platform_slug, file_name=file_name) + .limit(1) + ).first() + + @begin_session + def purge_bios(self, platform_slug: str, bios: list[str], session: Session = None): + return session.execute( + delete(Bios) + .where( + and_(Bios.platform_slug == platform_slug, Bios.file_name.not_in(bios)) + ) + .execution_options(synchronize_session="evaluate") + ) + + # ========= Screenshots ========= + @begin_session + def add_screenshot(self, screenshot: Rom, session: Session = None): + return session.merge(screenshot) + + @begin_session + def get_screenshot(self, id, session: Session = None): + return session.get(Screenshot, id) + + @begin_session + def get_screenshot_by_filename(self, file_name: str, session: Session = None): + return session.scalars( + select(Screenshot).filter_by(file_name=file_name).limit(1) + ).first() + + @begin_session + def purge_screenshots(self, screenshots: list[str], session: Session = None): + return session.execute( + delete(Screenshot) + .where(Screenshot.file_name.not_in(screenshots)) + .execution_options(synchronize_session="evaluate") + ) + # ========= Users ========= @begin_session def add_user(self, user: User, session: Session = None): diff --git a/backend/handler/igdb_handler.py b/backend/handler/igdb_handler.py index c278bed85..629f6eecc 100644 --- a/backend/handler/igdb_handler.py +++ b/backend/handler/igdb_handler.py @@ -13,7 +13,7 @@ from typing import Final from typing_extensions import TypedDict from config import IGDB_CLIENT_ID, IGDB_CLIENT_SECRET, DEFAULT_URL_COVER_L -from utils import get_file_name_with_no_tags as get_search_term +from utils import get_file_name_with_no_tags from logger.logger import log from utils.cache import cache from tasks.update_switch_titledb import update_switch_titledb_task @@ -180,7 +180,7 @@ class IGDBHandler: @check_twitch_token async def get_rom(self, file_name: str, platform_idgb_id: int) -> IGDBRomType: - search_term = get_search_term(file_name) + search_term = get_file_name_with_no_tags(file_name) # Patch support for PS2 OPL flename format match = re.match(PS2_OPL_REGEX, file_name) diff --git a/backend/handler/tests/test_db_handler.py b/backend/handler/tests/test_db_handler.py index 669db6886..ec9184e85 100644 --- a/backend/handler/tests/test_db_handler.py +++ b/backend/handler/tests/test_db_handler.py @@ -57,7 +57,7 @@ def test_roms(rom): roms = session.scalars(dbh.get_roms(rom.platform_slug)).all() assert len(roms) == 1 - dbh.purge_roms(rom_2.platform_slug, [rom_2.slug]) + dbh.purge_roms(rom_2.platform_slug, [rom_2.id]) with dbh.session.begin() as session: roms = session.scalars(dbh.get_roms(rom.platform_slug)).all() diff --git a/backend/models/__init__.py b/backend/models/__init__.py index 92efaca62..f45bd0e8a 100644 --- a/backend/models/__init__.py +++ b/backend/models/__init__.py @@ -1,4 +1,4 @@ from .platform import Platform # noqa[401] from .rom import Rom # noqa[401] from .user import User, Role # noqa[401] -from .asset import Save, State, Screenshot # noqa[401] +from .asset import Save, State, Screenshot, Bios # noqa[401] diff --git a/backend/models/asset.py b/backend/models/asset.py index 2fee115cf..b4057b24d 100644 --- a/backend/models/asset.py +++ b/backend/models/asset.py @@ -1,5 +1,5 @@ from sqlalchemy import Integer, Column, ForeignKey, String -from sqlalchemy.orm import relationship, Mapped +from sqlalchemy.orm import relationship, Mapped, backref from functools import cached_property from config import FRONT_LIBRARY_PATH @@ -7,12 +7,9 @@ from .base import BaseModel class BaseAsset(BaseModel): - from .rom import Rom - __abstract__ = True id = Column(Integer(), primary_key=True, autoincrement=True) - rom_id = Column(Integer(), ForeignKey("roms.id"), nullable=True) file_name = Column(String(length=450), nullable=False) file_name_no_tags = Column(String(length=450), nullable=False) @@ -34,15 +31,22 @@ class Save(BaseAsset): __tablename__ = "saves" - rom: Mapped[Rom] = relationship("Rom", lazy="joined", innerjoin=True) + rom_id = Column(Integer(), ForeignKey("roms.id", ondelete='CASCADE'), nullable=False) + rom: Mapped[Rom] = relationship("Rom", lazy="joined", innerjoin=True, backref=backref('saves', passive_deletes=True)) + platform_slug = Column(String(length=50), ForeignKey("platforms.slug", ondelete='CASCADE'), nullable=False) + platform = relationship("Platform", lazy="joined", innerjoin=True, backref=backref('saves', passive_deletes=True)) class State(BaseAsset): from .rom import Rom __tablename__ = "states" - rom: Mapped[Rom] = relationship("Rom", lazy="joined", innerjoin=True) + rom_id = Column(Integer(), ForeignKey("roms.id", ondelete='CASCADE'), nullable=False) + rom: Mapped[Rom] = relationship("Rom", lazy="joined", innerjoin=True, backref=backref('states', passive_deletes=True)) + + platform_slug = Column(String(length=50), ForeignKey("platforms.slug", ondelete='CASCADE'), nullable=False) + platform = relationship("Platform", lazy="joined", innerjoin=True, backref=backref('states', passive_deletes=True)) class Screenshot(BaseAsset): @@ -50,4 +54,14 @@ class Screenshot(BaseAsset): __tablename__ = "screenshots" - rom: Mapped[Rom] = relationship("Rom", lazy="joined", innerjoin=True) + rom_id = Column(Integer(), ForeignKey("roms.id", ondelete='CASCADE'), nullable=False) + rom: Mapped[Rom] = relationship("Rom", lazy="joined", innerjoin=True, backref=backref('screenshots', passive_deletes=True)) + + +class Bios(BaseAsset): + from .platform import Platform + + __tablename__ = "bios" + + platform_slug = Column(String(length=50), ForeignKey("platforms.slug", ondelete='CASCADE'), nullable=False) + platform: Mapped[Platform] = relationship("Platform", lazy="joined", innerjoin=True, backref=backref('bios', passive_deletes=True)) diff --git a/backend/models/rom.py b/backend/models/rom.py index ac8622e75..c7c513ce1 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -1,6 +1,6 @@ import re from sqlalchemy import Integer, Column, String, Text, Boolean, Float, JSON, ForeignKey -from sqlalchemy.orm import relationship, Mapped +from sqlalchemy.orm import relationship, Mapped, backref from functools import cached_property from config import DEFAULT_PATH_COVER_S, DEFAULT_PATH_COVER_L, FRONT_LIBRARY_PATH @@ -29,10 +29,15 @@ class Rom(BaseModel): # Foreign key to platform platform_slug = Column( - String(length=50), ForeignKey("platforms.slug"), nullable=False + String(length=50), + ForeignKey("platforms.slug", ondelete="CASCADE"), + nullable=False, ) platform: Mapped[Platform] = relationship( # noqa - "Platform", lazy="joined", innerjoin=True + "Platform", + lazy="joined", + innerjoin=True, + backref=backref("roms", passive_deletes=True), ) ### DEPRECATED ### diff --git a/backend/utils/__init__.py b/backend/utils/__init__.py index e721eb5f5..fdc969ead 100644 --- a/backend/utils/__init__.py +++ b/backend/utils/__init__.py @@ -105,8 +105,7 @@ def get_file_name_with_no_extension(file_name: str) -> str: def get_file_name_with_no_tags(file_name: str) -> str: file_name_no_extension = get_file_name_with_no_extension(file_name) - return re.sub(TAG_REGEX, "", file_name_no_extension).strip() - + return re.split(TAG_REGEX, file_name_no_extension)[0].strip() def get_file_extension(file_name: str) -> str: return re.search(EXTENSION_REGEX, file_name).group(1) diff --git a/backend/utils/fastapi.py b/backend/utils/fastapi.py index f56d51d75..f3e7b517d 100644 --- a/backend/utils/fastapi.py +++ b/backend/utils/fastapi.py @@ -3,9 +3,14 @@ from typing import Any from handler import igdbh from utils import fs, parse_tags, get_file_extension, get_file_name_with_no_tags -from config import SAVES_FOLDER_NAME, STATES_FOLDER_NAME +from config import ( + SAVES_FOLDER_NAME, + STATES_FOLDER_NAME, + BIOS_FOLDER_NAME, + SCREENSHOTS_FOLDER_NAME, +) from config.config_loader import config -from models import Platform, Rom, Save, State +from models import Platform, Rom, Save, State, Bios, Screenshot from logger.logger import log @@ -100,7 +105,7 @@ async def scan_rom( # Update properties from IGDB rom_attrs.update( - fs.get_cover( + fs.get_rom_cover( overwrite=overwrite, fs_slug=platform.slug, rom_name=rom_attrs["name"], @@ -108,7 +113,7 @@ async def scan_rom( ) ) rom_attrs.update( - fs.get_screenshots( + fs.get_rom_screenshots( fs_slug=platform.slug, rom_name=rom_attrs["name"], url_screenshots=rom_attrs["url_screenshots"], @@ -118,33 +123,34 @@ async def scan_rom( return Rom(**rom_attrs) -async def scan_save(platform: Platform, file_name: str) -> Save: +def _scan_asset(file_name: str, path: str): + log.info(f"\t\t · {file_name}") + + file_size = fs.get_fs_file_size(file_name=file_name, asset_path=path) + + return { + "file_path": path, + "file_name": file_name, + "file_name_no_tags": get_file_name_with_no_tags(file_name), + "file_extension": get_file_extension(file_name), + "file_size_bytes": file_size, + } + + +def scan_save(platform: Platform, file_name: str) -> Save: saves_path = fs.get_fs_structure(platform.fs_slug, folder=SAVES_FOLDER_NAME) - - log.info(f"\t · {file_name}") - - file_size = fs.get_fs_file_size(file_name=file_name, asset_path=saves_path) - - return Save( - file_path=saves_path, - file_name=file_name, - file_name_no_tags=get_file_name_with_no_tags(file_name), - file_extension=get_file_extension(file_name), - file_size_bytes=file_size, - ) + return Save(**_scan_asset(file_name, saves_path)) -async def scan_state(platform: Platform, file_name: str) -> State: +def scan_state(platform: Platform, file_name: str) -> State: states_path = fs.get_fs_structure(platform.fs_slug, folder=STATES_FOLDER_NAME) + return State(**_scan_asset(file_name, states_path)) - log.info(f"\t · {file_name}") - file_size = fs.get_fs_file_size(file_name=file_name, asset_path=states_path) +def scan_bios(platform: Platform, file_name: str) -> State: + bios_path = fs.get_fs_structure(platform.fs_slug, folder=BIOS_FOLDER_NAME) + return Bios(**_scan_asset(file_name, bios_path)) - return State( - file_path=states_path, - file_name=file_name, - file_name_no_tags=get_file_name_with_no_tags(file_name), - file_extension=get_file_extension(file_name), - file_size_bytes=file_size, - ) + +def scan_screenshot(file_name: str) -> State: + return Screenshot(**_scan_asset(file_name, SCREENSHOTS_FOLDER_NAME)) diff --git a/backend/utils/fs.py b/backend/utils/fs.py index 9ffd7ee12..db0dd42f9 100644 --- a/backend/utils/fs.py +++ b/backend/utils/fs.py @@ -22,6 +22,8 @@ from config import ( DEFAULT_HEIGHT_COVER_S, SAVES_FOLDER_NAME, STATES_FOLDER_NAME, + SCREENSHOTS_FOLDER_NAME, + BIOS_FOLDER_NAME, ) from config.config_loader import config from exceptions.fs_exceptions import ( @@ -113,7 +115,7 @@ def _get_cover_path(fs_slug: str, rom_name: str, size: CoverSize): return f"{fs_slug}/{rom_name}/cover/{size.value}.png?timestamp={strtime}" -def get_cover( +def get_rom_cover( overwrite: bool, fs_slug: str, rom_name: str, url_cover: str = "" ) -> dict: q_rom_name = quote(rom_name) @@ -171,13 +173,14 @@ def _get_screenshot_path(fs_slug: str, rom_name: str, idx: str): return f"{fs_slug}/{rom_name}/screenshots/{idx}.jpg" -def get_screenshots(fs_slug: str, rom_name: str, url_screenshots: list) -> dict: +def get_rom_screenshots(fs_slug: str, rom_name: str, url_screenshots: list) -> dict: q_rom_name = quote(rom_name) path_screenshots: list[str] = [] for idx, url in enumerate(url_screenshots): _store_screenshot(fs_slug, rom_name, url, idx) path_screenshots.append(_get_screenshot_path(fs_slug, q_rom_name, str(idx))) + return {"path_screenshots": path_screenshots} @@ -303,6 +306,7 @@ def get_assets(fs_slug: str): fs_saves: list[str] = [] fs_states: list[str] = [] + fs_bios: list[str] = [] try: fs_saves = list(os.walk(saves_file_path))[0][2] @@ -317,12 +321,32 @@ def get_assets(fs_slug: str): except IndexError: pass + bios_path = get_fs_structure(fs_slug, folder=BIOS_FOLDER_NAME) + bios_file_path = f"{LIBRARY_BASE_PATH}/{bios_path}" + + try: + fs_bios = list(os.walk(bios_file_path))[0][2] + except IndexError: + pass + return { "saves": fs_saves, "states": fs_states, + "bios": fs_bios, } +def get_screenshots(): + screenshots_path = f"{LIBRARY_BASE_PATH}/{SCREENSHOTS_FOLDER_NAME}" + + try: + fs_screenshots = list(os.walk(screenshots_path))[0][2] + except IndexError: + pass + + return fs_screenshots + + def get_rom_file_size( roms_path: str, file_name: str, multi: bool, multi_files: list = [] ): diff --git a/backend/utils/tests/test_fs.py b/backend/utils/tests/test_fs.py index c3122abb4..c2bc7f560 100644 --- a/backend/utils/tests/test_fs.py +++ b/backend/utils/tests/test_fs.py @@ -1,12 +1,12 @@ import pytest from ..fs import ( - get_cover, + get_rom_cover, get_platforms, - get_roms_structure, + get_fs_structure, get_roms, get_rom_file_size, - # get_screenshots # TODO: write test + # get_rom_screenshots # TODO: write test # store_default_resources # TODO: write test # get_rom_files, # TODO: write test # rename_rom, # TODO: write test @@ -23,9 +23,9 @@ from config import ( @pytest.mark.vcr -def test_get_cover(): +def test_get_rom_cover(): # Game: Paper Mario (USA).z64 - cover = get_cover( + cover = get_rom_cover( overwrite=False, fs_slug="n64", rom_name="Paper Mario", @@ -35,7 +35,7 @@ def test_get_cover(): assert DEFAULT_PATH_COVER_L in cover["path_cover_l"] # Game: Paper Mario (USA).z64 - cover = get_cover( + cover = get_rom_cover( overwrite=True, fs_slug="n64", rom_name="Paper Mario", @@ -46,7 +46,7 @@ def test_get_cover(): assert "n64/Paper%20Mario/cover/big.png" in cover["path_cover_l"] # Game: Super Mario 64 (J) (Rev A) - cover = get_cover( + cover = get_rom_cover( overwrite=False, fs_slug="n64", rom_name="Super Mario 64", @@ -57,7 +57,7 @@ def test_get_cover(): assert "n64/Super%20Mario%2064/cover/big.png" in cover["path_cover_l"] # Game: Disney's Kim Possible: What's the Switch?.zip - cover = get_cover( + cover = get_rom_cover( overwrite=False, fs_slug="ps2", rom_name="Disney's Kim Possible: What's the Switch?", @@ -74,7 +74,7 @@ def test_get_cover(): ) # Game: Fake Game.xyz - cover = get_cover( + cover = get_rom_cover( overwrite=False, fs_slug="n64", rom_name="Fake Game", @@ -91,8 +91,8 @@ def test_get_platforms(): assert "psx" in platforms -def test_get_roms_structure(): - roms_structure = get_roms_structure(fs_slug="n64") +def test_get_fs_structure(): + roms_structure = get_fs_structure(fs_slug="n64") assert roms_structure == "n64/roms" @@ -110,7 +110,7 @@ def test_get_roms(): def test_rom_size(): rom_size = get_rom_file_size( - roms_path=get_roms_structure(fs_slug="n64"), + roms_path=get_fs_structure(fs_slug="n64"), file_name="Paper Mario (USA).z64", multi=False, ) @@ -118,7 +118,7 @@ def test_rom_size(): assert rom_size == (1.0, "KB") rom_size = get_rom_file_size( - roms_path=get_roms_structure(fs_slug="n64"), + roms_path=get_fs_structure(fs_slug="n64"), file_name="Super Mario 64 (J) (Rev A)", multi=True, multi_files=[ diff --git a/frontend/src/views/Details/Base.vue b/frontend/src/views/Details/Base.vue index f62d229cc..f339543e4 100644 --- a/frontend/src/views/Details/Base.vue +++ b/frontend/src/views/Details/Base.vue @@ -102,17 +102,19 @@ onBeforeMount(async () => { > Details - Saves[coming soon] + + Saves + + + States + Screenshots + Screenshots +