diff --git a/backend/models/platform.py b/backend/models/platform.py index 189330fec..3204623fa 100644 --- a/backend/models/platform.py +++ b/backend/models/platform.py @@ -4,7 +4,7 @@ from functools import cached_property from typing import TYPE_CHECKING from models.base import BaseModel -from models.rom import Rom +from models.rom import Rom, RomFile from sqlalchemy import String, func, select from sqlalchemy.orm import Mapped, column_property, mapped_column, relationship @@ -55,6 +55,14 @@ class Platform(BaseModel): select(func.count(Rom.id)).where(Rom.platform_id == id).scalar_subquery() ) + fs_size_bytes: Mapped[int] = column_property( + select(func.coalesce(func.sum(RomFile.file_size_bytes), 0)) + .select_from(RomFile.__table__.join(Rom.__table__, RomFile.rom_id == Rom.id)) + .where(Rom.platform_id == id) + .correlate_except(RomFile, Rom) + .scalar_subquery() + ) + missing_from_fs: Mapped[bool] = mapped_column(default=False, nullable=False) @property @@ -72,12 +80,6 @@ class Platform(BaseModel): def is_identified(self) -> bool: return not self.is_unidentified - @cached_property - def fs_size_bytes(self) -> int: - from handler.database import db_stats_handler - - return db_stats_handler.get_platform_filesize(self.id) - @cached_property def igdb_slug(self) -> str | None: from handler.metadata import meta_igdb_handler diff --git a/backend/models/rom.py b/backend/models/rom.py index 59dbd9376..025a466fb 100644 --- a/backend/models/rom.py +++ b/backend/models/rom.py @@ -22,8 +22,10 @@ from sqlalchemy import ( String, Text, UniqueConstraint, + func, + select, ) -from sqlalchemy.orm import Mapped, mapped_column, relationship +from sqlalchemy.orm import Mapped, column_property, mapped_column, relationship from utils.database import CustomJSON if TYPE_CHECKING: @@ -193,6 +195,8 @@ class Rom(BaseModel): sha1_hash: Mapped[str | None] = mapped_column(String(length=100)) ra_hash: Mapped[str | None] = mapped_column(String(length=100)) + missing_from_fs: Mapped[bool] = mapped_column(default=False, nullable=False) + platform_id: Mapped[int] = mapped_column( ForeignKey("platforms.id", ondelete="CASCADE") ) @@ -222,7 +226,12 @@ class Rom(BaseModel): back_populates="roms", ) - missing_from_fs: Mapped[bool] = mapped_column(default=False, nullable=False) + fs_size_bytes: Mapped[int] = column_property( + select(func.coalesce(func.sum(RomFile.file_size_bytes), 0)) + .where(RomFile.rom_id == id) + .correlate_except(RomFile) + .scalar_subquery() + ) @property def platform_slug(self) -> str: @@ -261,20 +270,9 @@ class Rom(BaseModel): @cached_property def multi(self) -> bool: - # TODO: Improve multi game detection as this is a very basic check - if len(self.files) > 1: - return True - if ( - self.files - and len(self.files) > 0 - and len(self.files[0].full_path.split("/")) > 3 - ): - return True - return False - - @cached_property - def fs_size_bytes(self) -> int: - return sum(f.file_size_bytes for f in self.files) + return len(self.files) > 1 or ( + len(self.files) > 0 and len(self.files[0].full_path.split("/")) > 3 + ) @property def fs_resources_path(self) -> str: @@ -296,7 +294,29 @@ class Rom(BaseModel): else "" ) - # # Metadata fields + @property + def is_unidentified(self) -> bool: + return ( + not self.igdb_id + and not self.moby_id + and not self.ss_id + and not self.ra_id + and not self.launchbox_id + and not self.hasheous_id + ) + + @property + def is_identified(self) -> bool: + return not self.is_unidentified + + def has_m3u_file(self) -> bool: + """ + Check if the ROM has an M3U file associated with it. + This is used for multi-disc games. + """ + return any(file.file_extension.lower() == "m3u" for file in self.files) + + # Metadata fields @property def youtube_video_id(self) -> str | None: igdb_video_id = ( @@ -333,28 +353,6 @@ class Rom(BaseModel): ) return self.ra_metadata - @property - def is_unidentified(self) -> bool: - return ( - not self.igdb_id - and not self.moby_id - and not self.ss_id - and not self.ra_id - and not self.launchbox_id - and not self.hasheous_id - ) - - @property - def is_identified(self) -> bool: - return not self.is_unidentified - - def has_m3u_file(self) -> bool: - """ - Check if the ROM has an M3U file associated with it. - This is used for multi-disc games. - """ - return any(file.file_extension.lower() == "m3u" for file in self.files) - def __repr__(self) -> str: return self.fs_name