Merge Library and Status into a unified Games tab

This commit is contained in:
2026-10-13 17:16:23 +02:00
parent 5d917dd4b9
commit 1643da4bb6
26 changed files with 1777 additions and 1786 deletions
+30 -21
View File
@@ -1,7 +1,7 @@
"""Installer file downloader with progress reporting and resume support."""
import time
from datetime import datetime, timezone
from datetime import UTC, datetime
from typing import Protocol
import requests
@@ -51,7 +51,7 @@ class InstallerDownloader:
If single_language is True, the language subfolder is skipped
(game only available in one language).
"""
game_name = sanitize_folder_name(game_name)
folder = self._folder_for(installer.game_id, game_name)
# Resolve actual download URL and real filename
resolved = self.api.resolve_download_url(installer.download_url)
if not resolved:
@@ -64,10 +64,10 @@ class InstallerDownloader:
version = installer.version if installer.version else "unknown"
if single_language:
target_dir = self.metadata.base_path / game_name / version
target_dir = self.metadata.base_path / folder / version
else:
lang_folder = language_folder_name(installer.language)
target_dir = self.metadata.base_path / game_name / version / lang_folder
target_dir = self.metadata.base_path / folder / version / lang_folder
target_dir.mkdir(parents=True, exist_ok=True)
target_file = target_dir / real_filename
@@ -88,7 +88,7 @@ class InstallerDownloader:
if response.status_code == 416:
logger.info(f"Already fully downloaded: {real_filename}")
self._record_download(installer, game_name, real_filename)
self._record_download(installer, folder, real_filename)
if callback:
callback.on_finished(True, "Already downloaded")
return True
@@ -141,7 +141,7 @@ class InstallerDownloader:
callback.on_finished(False, f"Write error: {e}")
return False
self._record_download(installer, game_name, real_filename)
self._record_download(installer, folder, real_filename)
logger.info(f"Downloaded {real_filename} ({downloaded} bytes)")
if callback:
callback.on_finished(True, "Download complete")
@@ -154,7 +154,7 @@ class InstallerDownloader:
callback: DownloadProgressCallback | None = None,
) -> bool:
"""Download a bonus content file to GameName/Bonus/ directory."""
game_name = sanitize_folder_name(game_name)
folder = self._folder_for(bonus.game_id, game_name)
resolved = self.api.resolve_download_url(bonus.download_url)
if not resolved:
logger.error(f"Could not resolve download URL for bonus '{bonus.name}'")
@@ -164,14 +164,14 @@ class InstallerDownloader:
actual_url, real_filename = resolved
target_dir = self.metadata.base_path / game_name / "Bonus"
target_dir = self.metadata.base_path / folder / "Bonus"
target_dir.mkdir(parents=True, exist_ok=True)
target_file = target_dir / real_filename
# Skip if already downloaded with same size
if target_file.exists() and target_file.stat().st_size == bonus.size:
logger.info(f"Bonus already downloaded: {real_filename}")
self._record_bonus(bonus, game_name, real_filename)
self._record_bonus(bonus, folder, real_filename)
if callback:
callback.on_finished(True, "Already downloaded")
return True
@@ -192,7 +192,7 @@ class InstallerDownloader:
if response.status_code == 416:
logger.info(f"Bonus already fully downloaded: {real_filename}")
self._record_bonus(bonus, game_name, real_filename)
self._record_bonus(bonus, folder, real_filename)
if callback:
callback.on_finished(True, "Already downloaded")
return True
@@ -244,45 +244,54 @@ class InstallerDownloader:
callback.on_finished(False, f"Write error: {e}")
return False
self._record_bonus(bonus, game_name, real_filename)
self._record_bonus(bonus, folder, real_filename)
logger.info(f"Downloaded bonus {real_filename} ({downloaded} bytes)")
if callback:
callback.on_finished(True, "Download complete")
return True
def _record_bonus(self, bonus: BonusContent, game_name: str, real_filename: str) -> None:
def _folder_for(self, game_id: str, game_name: str) -> str:
"""Pinned download folder for a game: its recorded folder, else the sanitized title."""
record = self.metadata.get_game(game_id)
if record and record.folder:
return record.folder
return sanitize_folder_name(game_name)
def _record_bonus(self, bonus: BonusContent, folder: str, real_filename: str) -> None:
"""Record the bonus download in metadata store."""
game_name = sanitize_folder_name(game_name)
record = self.metadata.get_game(bonus.game_id)
if not record:
record = GameRecord(
game_id=bonus.game_id,
name=game_name,
name=folder,
)
if not record.folder:
record.folder = folder
downloaded = DownloadedBonus(
filename=real_filename,
name=bonus.name,
bonus_type=bonus.bonus_type,
size=bonus.size,
downloaded_at=datetime.now(timezone.utc).isoformat(),
downloaded_at=datetime.now(UTC).isoformat(),
)
record.bonuses = [b for b in record.bonuses if b.filename != real_filename]
record.bonuses.append(downloaded)
record.last_checked = datetime.now(timezone.utc).isoformat()
record.last_checked = datetime.now(UTC).isoformat()
self.metadata.update_game(record)
def _record_download(self, installer: InstallerInfo, game_name: str, real_filename: str) -> None:
def _record_download(self, installer: InstallerInfo, folder: str, real_filename: str) -> None:
"""Record the download in metadata store."""
game_name = sanitize_folder_name(game_name)
record = self.metadata.get_game(installer.game_id)
if not record:
record = GameRecord(
game_id=installer.game_id,
name=game_name,
name=folder,
latest_version=installer.version,
)
if not record.folder:
record.folder = folder
downloaded = DownloadedInstaller(
filename=real_filename,
@@ -290,12 +299,12 @@ class InstallerDownloader:
version=installer.version,
language=installer.language,
installer_type=installer.installer_type,
downloaded_at=datetime.now(timezone.utc).isoformat(),
downloaded_at=datetime.now(UTC).isoformat(),
)
# Replace existing entry for same file, or add new
record.installers = [i for i in record.installers if i.filename != real_filename]
record.installers.append(downloaded)
record.latest_version = installer.version
record.last_checked = datetime.now(timezone.utc).isoformat()
record.last_checked = datetime.now(UTC).isoformat()
self.metadata.update_game(record)