Files
psp-preservation-tool/app/config.py
T
quoije c19717402c Polish settings, add matching defaults, drop pkg2zip, externalize infra config
- Rework Settings tab into grouped sections
- Add preferred-region matching and full-hash-by-default setting
- Replace combo-box square arrow with a proper chevron
- Fix UMD Refresh button (clicked bool was clearing the view)
- Remove pkg2zip.exe fallback (pure-Python decryptor is the primary path)
- Move LAN IP/share paths to .env; use example defaults in config
2026-09-13 20:03:44 -04:00

97 lines
4.5 KiB
Python

"""Central configuration, driven by environment variables."""
import os
import sys
from pathlib import Path
# PyInstaller sets ``sys.frozen`` and unpacks bundled files to ``sys._MEIPASS``.
# Read-only resources (DATs, loader, icons) then live in the bundle; otherwise
# they live in the project checkout.
IS_FROZEN = getattr(sys, "frozen", False)
_BUNDLE_DIR = getattr(sys, "_MEIPASS", None)
PROJECT_ROOT = Path(_BUNDLE_DIR).resolve() if _BUNDLE_DIR else Path(__file__).resolve().parent.parent
def resource_path(*parts):
"""Absolute path to a read-only bundled resource (works when frozen)."""
return PROJECT_ROOT.joinpath(*parts)
DATA_DIR = Path(os.environ.get("PSP_DATA_DIR", str(PROJECT_ROOT / "data")))
DATS_DIR = Path(os.environ.get("PSP_DATS_DIR", str(DATA_DIR / "dats")))
def _default_state_dir():
"""Local state directory (fast, reliable local disk).
SQLite locking is unreliable over network filesystems (SMB/FUSE), so the
library DB and the cover cache live on local storage while read-only DATs
stay bundled with the app.
"""
if os.name == "nt":
base = os.environ.get("LOCALAPPDATA") or os.environ.get("APPDATA") or str(Path.home())
return Path(base) / "PSPPreserve"
xdg = os.environ.get("XDG_STATE_HOME")
base = Path(xdg) if xdg else Path.home() / ".local" / "state"
return base / "psp-preserve"
STATE_DIR = Path(os.environ.get("PSP_STATE_DIR", str(_default_state_dir())))
DB_PATH = Path(os.environ.get("PSP_DB_PATH", str(STATE_DIR / "library.sqlite")))
COVERS_DIR = Path(os.environ.get("PSP_COVERS_DIR", str(STATE_DIR / "covers")))
# User-writable data (credentials, manual covers, PS1 templates, NPS TSV cache).
# In dev this is the checkout's ``data/``; when frozen the bundle is read-only,
# so it points at local state that survives app upgrades.
USER_DATA_DIR = Path(os.environ.get(
"PSP_USER_DATA_DIR", str(DATA_DIR if not IS_FROZEN else STATE_DIR / "data")))
LEGACY_DB_PATH = DATA_DIR / "library.sqlite"
LEGACY_COVERS_DIR = DATA_DIR / "covers"
def user_data_path(*parts):
"""Absolute path to a user-writable data file/folder (works when frozen)."""
return USER_DATA_DIR.joinpath(*parts)
# Redump 7z store (browsable in the Store tab). Env override; GUI setting wins.
STORE_ROOT = Path(os.environ.get("PSP_STORE_DIR", "/data/store"))
# Remote store server (HTTPS). Used by the Store tab as the primary source.
STORE_SERVER_URL = os.environ.get("PSP_STORE_SERVER", "https://psp.example.com")
STORE_TOKEN = os.environ.get("PSP_STORE_TOKEN", "")
# Optional direct URLs for the offline cover bundle zips. When unset, the GUI
# derives them from the store server URL (/api/covers/<name>.zip).
LIBRETRO_COVERS_URL = os.environ.get("PSP_LIBRETRO_COVERS_URL", "")
OVGDB_COVERS_URL = os.environ.get("PSP_OVGDB_COVERS_URL", "")
# archive.org item hosting the same Redump 7z set (identifier or full URL).
ARCHIVE_ITEM = os.environ.get("PSP_ARCHIVE_ITEM", "psp-redump-roms321.com")
# Optional archive.org login cookies (for items whose files are "private" /
# login-required). Grab these from a logged-in browser session on archive.org.
ARCHIVE_LOGGED_IN_SIG = os.environ.get("PSP_ARCHIVE_LOGGED_IN_SIG", "")
ARCHIVE_LOGGED_IN_USER = os.environ.get("PSP_ARCHIVE_LOGGED_IN_USER", "")
# NoPayStation: where extracted .pkg content lands, and the TSV source base.
# PSP games go to <NPS_OUTPUT_DIR>/PSP/<TITLE_ID>/; PS1 Classics to .../PSX/.
NPS_OUTPUT_DIR = Path(os.environ.get("PSP_NPS_DIR", str(STATE_DIR / "nps")))
NPS_TSV_BASE = os.environ.get("PSP_NPS_TSV_BASE", "https://nopaystation.com/tsv/")
# Scan roots (used by the scanner phase). Comma-separated in env.
GAMES_ROOTS = [Path(p) for p in os.environ.get("PSP_GAMES_ROOTS", "/data/umd_games").split(",") if p.strip()]
VIDEO_ROOTS = [Path(p) for p in os.environ.get("PSP_VIDEO_ROOTS", "/data/umd_videos").split(",") if p.strip()]
# PS1: source folder (raw ISO/BIN/CUE) and converted EBOOT.PBP output folder.
PS1_ROOTS = [Path(p) for p in os.environ.get("PSP_PS1_ROOTS", "/data/ps1").split(",") if p.strip()]
PS1_OUTPUT_DIR = Path(os.environ.get("PSP_PS1_OUTPUT_DIR", "/data/ps1_converted"))
# Only .iso discs are tracked; everything below is explicitly ignored.
DISC_EXTENSIONS = {".iso"}
IGNORED_EXTENSIONS = {".pkg", ".pbp", ".cso"}
# PS1 source images (scanned separately from PSP .iso discs).
PS1_EXTENSIONS = {".cue", ".iso", ".img", ".bin"}
# Parallel hashing workers for the scanner. Keep at 1 on spinning disks
# (concurrent reads cause seek thrash); raise it for SSD / fast arrays.
SCAN_WORKERS = int(os.environ.get("PSP_SCAN_WORKERS", "1"))