PSP Sync: list themes and DLC in the device inventory
This commit is contained in:
@@ -379,8 +379,8 @@ token**, then pick **Server** in the Store tab's source selector. Use
|
||||
|
||||
Connect a PSP over USB (or mount a UMD) and the **PSP Sync** tab detects it:
|
||||
|
||||
- **Games / UMD Videos / Homebrew-PSN / PKG** — tables of what's installed, with
|
||||
delete-from-PSP (checkboxes and right-click).
|
||||
- **Games / UMD Videos / Homebrew-PSN / Themes / DLC / PKG** — tables of what's
|
||||
installed, with delete-from-PSP (checkboxes and right-click).
|
||||
- **UMD** — reads `PARAM.SFO` from a mounted UMD, shows the title/serial, and
|
||||
can dump the disc image into your library.
|
||||
- **Sync from Library** — copy discs from your library onto the PSP, optionally
|
||||
|
||||
+34
-9
@@ -2599,7 +2599,7 @@ class PspSyncPage(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._root = ""
|
||||
self._inventory = {"games": [], "videos": [], "homebrew": [], "pkg": []}
|
||||
self._inventory = {"games": [], "videos": [], "homebrew": [], "pkg": [], "themes": [], "dlc": []}
|
||||
self._library_items = []
|
||||
self._lib_raw = {}
|
||||
self._lib_norm = {}
|
||||
@@ -2638,16 +2638,21 @@ class PspSyncPage(QWidget):
|
||||
self.games_table = self._make_table(["", "Name", "Category", "Added", "Release", "Region", "Size"], stretch_col=1)
|
||||
self.videos_table = self._make_table(["", "Name", "Added", "Size"], stretch_col=1)
|
||||
self.homebrew_table = self._make_table(["", "ID", "Name", "Added", "Size"], stretch_col=2)
|
||||
self.themes_table = self._make_table(["", "Name", "Added", "Size"], stretch_col=1)
|
||||
self.dlc_table = self._make_table(["", "Game", "Name", "Added", "Size"], stretch_col=2)
|
||||
self.pkg_table = self._make_table(["", "Name", "Added", "Size"], stretch_col=1)
|
||||
self.tabs.addTab(self.games_table, "Games")
|
||||
self.tabs.addTab(self.videos_table, "UMD Videos")
|
||||
self.tabs.addTab(self.homebrew_table, "Homebrew / PSN")
|
||||
self.tabs.addTab(self.themes_table, "Themes")
|
||||
self.tabs.addTab(self.dlc_table, "DLC")
|
||||
self.tabs.addTab(self.pkg_table, "PKG")
|
||||
self.tabs.addTab(self._build_umd_tab(), "UMD")
|
||||
self.tabs.addTab(self._build_sync_tab(), "Sync from Library")
|
||||
layout.addWidget(self.tabs, 1)
|
||||
|
||||
for tbl in (self.games_table, self.videos_table, self.homebrew_table, self.pkg_table):
|
||||
for tbl in (self.games_table, self.videos_table, self.homebrew_table,
|
||||
self.themes_table, self.dlc_table, self.pkg_table):
|
||||
tbl.itemSelectionChanged.connect(self._update_delete_enabled)
|
||||
tbl.itemChanged.connect(self._update_delete_enabled)
|
||||
tbl.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
@@ -2711,7 +2716,7 @@ class PspSyncPage(QWidget):
|
||||
self.sync_search.textChanged.connect(self._filter_sync)
|
||||
bar.addWidget(self.sync_search, 1)
|
||||
self.sync_type = QComboBox()
|
||||
self.sync_type.addItems(["All", "Games", "Videos"])
|
||||
self.sync_type.addItems(["All", "Games", "Videos", "PS1", "Themes", "DLC"])
|
||||
self.sync_type.currentTextChanged.connect(self._filter_sync)
|
||||
bar.addWidget(self.sync_type)
|
||||
self.sync_installed = QComboBox()
|
||||
@@ -2883,6 +2888,12 @@ class PspSyncPage(QWidget):
|
||||
continue
|
||||
if typ == "Videos" and it.get("system") != "videos":
|
||||
continue
|
||||
if typ == "PS1" and it.get("system") != "ps1":
|
||||
continue
|
||||
if typ == "Themes" and it.get("system") != "theme":
|
||||
continue
|
||||
if typ == "DLC" and it.get("system") != "dlc":
|
||||
continue
|
||||
on_psp = bool(it.get("on_psp"))
|
||||
if inst == "Not installed" and on_psp:
|
||||
continue
|
||||
@@ -2906,7 +2917,8 @@ class PspSyncPage(QWidget):
|
||||
title = QTableWidgetItem(label)
|
||||
title.setData(Qt.UserRole, it)
|
||||
self.sync_table.setItem(row, 1, title)
|
||||
kind_label = {"games": "Game", "videos": "Video", "ps1": "PS1"}.get(it.get("system"), "Game")
|
||||
kind_label = {"games": "Game", "videos": "Video", "ps1": "PS1",
|
||||
"theme": "Theme", "dlc": "DLC"}.get(it.get("system"), "Game")
|
||||
self.sync_table.setItem(row, 2, QTableWidgetItem(kind_label))
|
||||
size = it.get("size")
|
||||
self.sync_table.setItem(row, 3, NumericItem(f"{size / (1024 * 1024):.1f} MB" if size else "", size or 0))
|
||||
@@ -2959,7 +2971,7 @@ class PspSyncPage(QWidget):
|
||||
self._gen += 1
|
||||
gen = self._gen
|
||||
if not self._root:
|
||||
self._inventory = {"games": [], "videos": [], "homebrew": [], "pkg": []}
|
||||
self._inventory = {"games": [], "videos": [], "homebrew": [], "pkg": [], "themes": [], "dlc": []}
|
||||
self.status.setText("No PSP connected")
|
||||
self._populate(self._inventory)
|
||||
return
|
||||
@@ -2979,21 +2991,26 @@ class PspSyncPage(QWidget):
|
||||
if gen != self._gen:
|
||||
return
|
||||
if "error" in res:
|
||||
self._inventory = {"games": [], "videos": [], "homebrew": [], "pkg": []}
|
||||
self._inventory = {"games": [], "videos": [], "homebrew": [], "pkg": [], "themes": [], "dlc": []}
|
||||
self.status.setText(f"Error reading PSP: {res['error']}")
|
||||
log.warning("psp inventory error: %s", res["error"])
|
||||
else:
|
||||
inventory = res["inventory"]
|
||||
self._inventory = inventory
|
||||
log.info("psp inventory: %d game(s), %d video(s), %d homebrew, %d pkg",
|
||||
log.info("psp inventory: %d game(s), %d video(s), %d homebrew, %d theme(s), %d dlc, %d pkg",
|
||||
len(inventory["games"]), len(inventory["videos"]),
|
||||
len(inventory["homebrew"]), len(inventory.get("pkg", [])))
|
||||
len(inventory["homebrew"]), len(inventory.get("themes", [])),
|
||||
len(inventory.get("dlc", [])), len(inventory.get("pkg", [])))
|
||||
usage = res.get("usage")
|
||||
parts = [
|
||||
f"{len(inventory['games'])} game(s)",
|
||||
f"{len(inventory['videos'])} video(s)",
|
||||
f"{len(inventory['homebrew'])} homebrew",
|
||||
]
|
||||
if inventory.get("themes"):
|
||||
parts.append(f"{len(inventory['themes'])} theme(s)")
|
||||
if inventory.get("dlc"):
|
||||
parts.append(f"{len(inventory['dlc'])} dlc")
|
||||
if usage is not None:
|
||||
free_mb = usage["free"] / (1024 * 1024)
|
||||
total_mb = usage["total"] / (1024 * 1024)
|
||||
@@ -3007,6 +3024,8 @@ class PspSyncPage(QWidget):
|
||||
self._fill_table(self.games_table, inventory.get("games", []), "games")
|
||||
self._fill_table(self.videos_table, inventory.get("videos", []), "videos")
|
||||
self._fill_table(self.homebrew_table, inventory.get("homebrew", []), "homebrew")
|
||||
self._fill_table(self.themes_table, inventory.get("themes", []), "theme")
|
||||
self._fill_table(self.dlc_table, inventory.get("dlc", []), "dlc")
|
||||
self._fill_table(self.pkg_table, inventory.get("pkg", []), "pkg")
|
||||
|
||||
@staticmethod
|
||||
@@ -3055,6 +3074,11 @@ class PspSyncPage(QWidget):
|
||||
table.setItem(row, 2, name)
|
||||
table.setItem(row, 3, QTableWidgetItem(added))
|
||||
table.setItem(row, 4, NumericItem(f"{size / (1024 * 1024):.1f} MB" if size else "", size))
|
||||
elif kind == "dlc":
|
||||
table.setItem(row, 1, QTableWidgetItem(it.get("id") or ""))
|
||||
table.setItem(row, 2, name)
|
||||
table.setItem(row, 3, QTableWidgetItem(added))
|
||||
table.setItem(row, 4, NumericItem(f"{size / (1024 * 1024):.1f} MB" if size else "", size))
|
||||
else:
|
||||
table.setItem(row, 1, name)
|
||||
table.setItem(row, 2, QTableWidgetItem(added))
|
||||
@@ -3064,7 +3088,8 @@ class PspSyncPage(QWidget):
|
||||
|
||||
def _current_inventory_table(self):
|
||||
w = self.tabs.currentWidget()
|
||||
if w in (self.games_table, self.videos_table, self.homebrew_table, self.pkg_table):
|
||||
if w in (self.games_table, self.videos_table, self.homebrew_table,
|
||||
self.themes_table, self.dlc_table, self.pkg_table):
|
||||
return w
|
||||
return None
|
||||
|
||||
|
||||
+34
-1
@@ -221,8 +221,39 @@ def _pkg_items(root):
|
||||
return items
|
||||
|
||||
|
||||
def _theme_items(root):
|
||||
"""Installed themes (``PSP/THEME/*.ptf``)."""
|
||||
items = []
|
||||
theme_dir = root / "PSP" / "THEME"
|
||||
if theme_dir.is_dir():
|
||||
try:
|
||||
entries = sorted(theme_dir.iterdir())
|
||||
except OSError:
|
||||
return items
|
||||
for path in entries:
|
||||
if path.is_file() and path.suffix.lower() == ".ptf":
|
||||
items.append(_file_item(path, "theme"))
|
||||
return items
|
||||
|
||||
|
||||
def _dlc_items(root):
|
||||
"""Installed DLC (``PSP/GAME/<GAME_ID>/*.edat``)."""
|
||||
items = []
|
||||
game_dir = root / "PSP" / "GAME"
|
||||
if game_dir.is_dir():
|
||||
for path in sorted(game_dir.rglob("*")):
|
||||
if not path.is_file() or path.suffix.lower() != ".edat":
|
||||
continue
|
||||
if path.name.upper() == "ISO.BIN.EDAT":
|
||||
continue # package wrapper, not DLC content
|
||||
item = _file_item(path, "dlc")
|
||||
item["id"] = path.parent.name
|
||||
items.append(item)
|
||||
return items
|
||||
|
||||
|
||||
def list_installed(root):
|
||||
"""Return ``{games, videos, homebrew, pkg}`` inventories for a PSP root."""
|
||||
"""Return ``{games, videos, homebrew, pkg, themes, dlc}`` for a PSP root."""
|
||||
root = Path(root)
|
||||
games, videos, homebrew_isos = _iso_items(root)
|
||||
return {
|
||||
@@ -230,6 +261,8 @@ def list_installed(root):
|
||||
"videos": videos,
|
||||
"homebrew": _homebrew_items(root) + homebrew_isos,
|
||||
"pkg": _pkg_items(root),
|
||||
"themes": _theme_items(root),
|
||||
"dlc": _dlc_items(root),
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user