mirror of
https://github.com/rommapp/romm.git
synced 2026-02-18 00:27:41 +01:00
feat: add 'missing' flag to platforms and roms, update related handlers and schemas
This commit is contained in:
36
backend/alembic/versions/0042_add_missing_rom_flag.py
Normal file
36
backend/alembic/versions/0042_add_missing_rom_flag.py
Normal 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")
|
||||
@@ -32,6 +32,8 @@ class PlatformSchema(BaseModel):
|
||||
updated_at: datetime
|
||||
fs_size_bytes: int
|
||||
|
||||
missing: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@@ -225,6 +225,8 @@ class RomSchema(BaseModel):
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
missing: bool
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -27,6 +27,7 @@ export type PlatformSchema = {
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
fs_size_bytes: number;
|
||||
missing: boolean;
|
||||
readonly display_name: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ export type SimpleRomSchema = {
|
||||
full_path: string;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
missing: boolean;
|
||||
siblings: Array<SiblingRomSchema>;
|
||||
rom_user: RomUserSchema;
|
||||
};
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user