feat: add 'missing' flag to platforms and roms, update related handlers and schemas

This commit is contained in:
zurdi
2025-06-12 01:20:20 +00:00
parent 76f613dd0c
commit b52ea89115
16 changed files with 131 additions and 46 deletions

View File

@@ -0,0 +1,36 @@
"""empty message
Revision ID: 0042_add_missing_fields
Revises: 0041_assets_t_thumb_cleanup
Create Date: 2025-06-11
"""
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision = "0042_add_missing_fields"
down_revision = "0041_assets_t_thumb_cleanup"
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table("platforms", schema=None) as batch_op:
batch_op.add_column(
sa.Column("missing", sa.Boolean(), nullable=False, server_default="0")
)
with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.add_column(
sa.Column("missing", sa.Boolean(), nullable=False, server_default="0")
)
def downgrade():
with op.batch_alter_table("platforms", schema=None) as batch_op:
batch_op.drop_column("missing")
with op.batch_alter_table("roms", schema=None) as batch_op:
batch_op.drop_column("missing")

View File

@@ -32,6 +32,8 @@ class PlatformSchema(BaseModel):
updated_at: datetime
fs_size_bytes: int
missing: bool
class Config:
from_attributes = True

View File

@@ -225,6 +225,8 @@ class RomSchema(BaseModel):
created_at: datetime
updated_at: datetime
missing: bool
class Config:
from_attributes = True

View File

@@ -210,9 +210,9 @@ async def scan_platforms(
# This protects against accidental deletion of entries when
# the folder structure is not correct or the drive is not mounted
if len(fs_platforms) > 0:
purged_platforms = db_platform_handler.purge_platforms(fs_platforms)
purged_platforms = db_platform_handler.mark_missing_platforms(fs_platforms)
if len(purged_platforms) > 0:
log.warning("Purging platforms not found in the filesystem:")
log.warning("Missing platforms not found in the filesystem:")
for p in purged_platforms:
log.warning(f" - {p.slug}")
@@ -330,12 +330,12 @@ async def _identify_platform(
# This protects against accidental deletion of entries when
# the folder structure is not correct or the drive is not mounted
if len(fs_roms) > 0:
purged_roms = db_rom_handler.purge_roms(
missing_roms = db_rom_handler.mark_missing_roms(
platform.id, [rom["fs_name"] for rom in fs_roms]
)
if len(purged_roms) > 0:
log.warning("Purging roms not found in the filesystem:")
for r in purged_roms:
if len(missing_roms) > 0:
log.warning("Missing roms not found in the filesystem:")
for r in missing_roms:
log.warning(f" - {r.fs_name}")
# Same protection for firmware

View File

@@ -4,7 +4,7 @@ from typing import Sequence
from decorators.database import begin_session
from models.platform import Platform
from models.rom import Rom
from sqlalchemy import delete, or_, select
from sqlalchemy import delete, or_, select, update
from sqlalchemy.orm import Query, Session, selectinload
from .base_handler import DBBaseHandler
@@ -72,13 +72,13 @@ class DBPlatformsHandler(DBBaseHandler):
)
@begin_session
def purge_platforms(
def mark_missing_platforms(
self,
fs_platforms_to_keep: list[str],
query: Query = None,
session: Session = None,
) -> Sequence[Platform]:
purged_platforms = (
missing_platforms = (
session.scalars(
select(Platform)
.order_by(Platform.name.asc())
@@ -93,8 +93,9 @@ class DBPlatformsHandler(DBBaseHandler):
.all()
)
session.execute(
delete(Platform)
update(Platform)
.where(or_(Platform.fs_slug.not_in(fs_platforms_to_keep), Platform.slug.is_(None))) # type: ignore[attr-defined]
.values(**{"missing": True})
.execution_options(synchronize_session="fetch")
)
return purged_platforms
return missing_platforms

View File

@@ -674,10 +674,10 @@ class DBRomsHandler(DBBaseHandler):
)
@begin_session
def purge_roms(
def mark_missing_roms(
self, platform_id: int, fs_roms_to_keep: list[str], session: Session = None
) -> Sequence[Rom]:
purged_roms = (
missing_roms = (
session.scalars(
select(Rom)
.order_by(Rom.fs_name.asc())
@@ -692,15 +692,16 @@ class DBRomsHandler(DBBaseHandler):
.all()
)
session.execute(
delete(Rom)
update(Rom)
.where(
and_(
Rom.platform_id == platform_id, Rom.fs_name.not_in(fs_roms_to_keep)
)
)
.values(**{"missing": True})
.execution_options(synchronize_session="evaluate")
)
return purged_roms
return missing_roms
@begin_session
def add_rom_user(

View File

@@ -175,6 +175,7 @@ async def scan_platform(
extra=LOGGER_MODULE_NAME,
)
platform_attrs["missing"] = False
return Platform(**platform_attrs)
@@ -397,6 +398,7 @@ async def scan_rom(
extra=LOGGER_MODULE_NAME,
)
romm_attrs["missing"] = False
return Rom(**rom_attrs)

View File

@@ -27,7 +27,7 @@ def test_platforms():
assert platform is not None
assert platform.name == "test_platform"
db_platform_handler.purge_platforms([])
db_platform_handler.mark_missing_platforms([])
platforms = db_platform_handler.get_platforms()
assert len(platforms) == 0
@@ -63,7 +63,7 @@ def test_roms(rom: Rom, platform: Platform):
roms = db_rom_handler.get_roms_scalar(platform_id=platform.id)
assert len(roms) == 1
db_rom_handler.purge_roms(rom_2.platform_id, [])
db_rom_handler.mark_missing_roms(rom_2.platform_id, [])
roms = db_rom_handler.get_roms_scalar(platform_id=platform.id)
assert len(roms) == 0

View File

@@ -50,11 +50,13 @@ class Platform(BaseModel):
select(func.count(Rom.id)).where(Rom.platform_id == id).scalar_subquery()
)
def __repr__(self) -> str:
return self.name
missing: Mapped[bool] = mapped_column(default=False, nullable=False)
@cached_property
def fs_size_bytes(self) -> int:
from handler.database import db_stats_handler
return db_stats_handler.get_platform_filesize(self.id)
def __repr__(self) -> str:
return self.name

View File

@@ -203,6 +203,8 @@ class Rom(BaseModel):
back_populates="roms",
)
missing: Mapped[bool] = mapped_column(default=False, nullable=False)
@property
def platform_slug(self) -> str:
return self.platform.slug

View File

@@ -62,6 +62,7 @@ export type DetailedRomSchema = {
full_path: string;
created_at: string;
updated_at: string;
missing: boolean;
merged_ra_metadata: (RomRAMetadata | null);
merged_screenshots: Array<string>;
siblings: Array<SiblingRomSchema>;

View File

@@ -27,6 +27,7 @@ export type PlatformSchema = {
created_at: string;
updated_at: string;
fs_size_bytes: number;
missing: boolean;
readonly display_name: string;
};

View File

@@ -56,6 +56,7 @@ export type SimpleRomSchema = {
full_path: string;
created_at: string;
updated_at: string;
missing: boolean;
siblings: Array<SiblingRomSchema>;
rom_user: RomUserSchema;
};

View File

@@ -238,6 +238,26 @@ onBeforeUnmount(() => {
</template>
<sources v-if="!romsStore.isSimpleRom(rom)" :rom="rom" />
<v-row no-gutters class="text-white px-1">
<v-tooltip
v-if="romsStore.isSimpleRom(rom) && rom.missing"
location="top"
class="tooltip"
transition="fade-transition"
:text="`Missing game from filesystem: ${rom.fs_path}/${rom.fs_name}`"
open-delay="500"
><template #activator="{ props }">
<v-chip
v-bind="props"
class="translucent-dark mr-1 mt-1 px-1"
color="accent"
density="compact"
>
<span class="text-caption"
><v-icon>mdi-alert-circle</v-icon></span
>
</v-chip>
</template>
</v-tooltip>
<game-card-flags
v-if="romsStore.isSimpleRom(rom) && showFlags"
:rom="rom"

View File

@@ -207,9 +207,29 @@ function updateOptions({ sortBy }: { sortBy: SortBy }) {
</v-col>
</v-row>
<template #append>
<v-tooltip
v-if="item.missing"
location="top"
class="tooltip"
transition="fade-transition"
:text="`Missing game from filesystem: ${item.fs_path}/${item.fs_name}`"
open-delay="500"
><template #activator="{ props }">
<v-chip
v-bind="props"
class="translucent-dark ml-4"
color="accent"
size="x-small"
>
<span class="text-caption"
><v-icon>mdi-alert-circle</v-icon></span
>
</v-chip>
</template>
</v-tooltip>
<v-chip
v-if="item.siblings.length > 0 && showSiblings"
class="translucent-dark ml-4"
class="translucent-dark ml-2"
size="x-small"
>
<span class="text-caption">+{{ item.siblings.length }}</span>

View File

@@ -37,24 +37,7 @@ withDefaults(
:name="platform.name"
:fs-slug="platform.fs_slug"
:size="40"
>
<v-tooltip
location="bottom"
class="tooltip"
transition="fade-transition"
text="Not found"
open-delay="500"
><template #activator="{ props }">
<div
v-if="!platform.igdb_id && !platform.moby_id && !platform.ss_id"
v-bind="props"
class="not-found-icon"
>
⚠️
</div>
</template>
</v-tooltip></platform-icon
>
/>
</template>
<v-row no-gutters
><v-col
@@ -67,17 +50,28 @@ withDefaults(
</v-col>
</v-row>
<template v-if="showRomCount" #append>
<v-tooltip
v-if="platform.missing"
location="top"
class="tooltip"
transition="fade-transition"
text="Missing platform from filesystem"
open-delay="500"
><template #activator="{ props }">
<v-chip
v-bind="props"
class="ml-2"
size="x-small"
color="accent"
label
>
<v-icon>mdi-alert-circle</v-icon>
</v-chip>
</template>
</v-tooltip>
<v-chip class="ml-2" size="x-small" label>
{{ platform.rom_count }}
</v-chip>
</template>
</v-list-item>
</template>
<style scoped>
.not-found-icon {
position: absolute;
bottom: 0;
right: 0;
}
</style>