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:
|
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
|
- **Games / UMD Videos / Homebrew-PSN / Themes / DLC / PKG** — tables of what's
|
||||||
delete-from-PSP (checkboxes and right-click).
|
installed, with delete-from-PSP (checkboxes and right-click).
|
||||||
- **UMD** — reads `PARAM.SFO` from a mounted UMD, shows the title/serial, and
|
- **UMD** — reads `PARAM.SFO` from a mounted UMD, shows the title/serial, and
|
||||||
can dump the disc image into your library.
|
can dump the disc image into your library.
|
||||||
- **Sync from Library** — copy discs from your library onto the PSP, optionally
|
- **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):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._root = ""
|
self._root = ""
|
||||||
self._inventory = {"games": [], "videos": [], "homebrew": [], "pkg": []}
|
self._inventory = {"games": [], "videos": [], "homebrew": [], "pkg": [], "themes": [], "dlc": []}
|
||||||
self._library_items = []
|
self._library_items = []
|
||||||
self._lib_raw = {}
|
self._lib_raw = {}
|
||||||
self._lib_norm = {}
|
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.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.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.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.pkg_table = self._make_table(["", "Name", "Added", "Size"], stretch_col=1)
|
||||||
self.tabs.addTab(self.games_table, "Games")
|
self.tabs.addTab(self.games_table, "Games")
|
||||||
self.tabs.addTab(self.videos_table, "UMD Videos")
|
self.tabs.addTab(self.videos_table, "UMD Videos")
|
||||||
self.tabs.addTab(self.homebrew_table, "Homebrew / PSN")
|
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.pkg_table, "PKG")
|
||||||
self.tabs.addTab(self._build_umd_tab(), "UMD")
|
self.tabs.addTab(self._build_umd_tab(), "UMD")
|
||||||
self.tabs.addTab(self._build_sync_tab(), "Sync from Library")
|
self.tabs.addTab(self._build_sync_tab(), "Sync from Library")
|
||||||
layout.addWidget(self.tabs, 1)
|
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.itemSelectionChanged.connect(self._update_delete_enabled)
|
||||||
tbl.itemChanged.connect(self._update_delete_enabled)
|
tbl.itemChanged.connect(self._update_delete_enabled)
|
||||||
tbl.setContextMenuPolicy(Qt.CustomContextMenu)
|
tbl.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||||
@@ -2711,7 +2716,7 @@ class PspSyncPage(QWidget):
|
|||||||
self.sync_search.textChanged.connect(self._filter_sync)
|
self.sync_search.textChanged.connect(self._filter_sync)
|
||||||
bar.addWidget(self.sync_search, 1)
|
bar.addWidget(self.sync_search, 1)
|
||||||
self.sync_type = QComboBox()
|
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)
|
self.sync_type.currentTextChanged.connect(self._filter_sync)
|
||||||
bar.addWidget(self.sync_type)
|
bar.addWidget(self.sync_type)
|
||||||
self.sync_installed = QComboBox()
|
self.sync_installed = QComboBox()
|
||||||
@@ -2883,6 +2888,12 @@ class PspSyncPage(QWidget):
|
|||||||
continue
|
continue
|
||||||
if typ == "Videos" and it.get("system") != "videos":
|
if typ == "Videos" and it.get("system") != "videos":
|
||||||
continue
|
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"))
|
on_psp = bool(it.get("on_psp"))
|
||||||
if inst == "Not installed" and on_psp:
|
if inst == "Not installed" and on_psp:
|
||||||
continue
|
continue
|
||||||
@@ -2906,7 +2917,8 @@ class PspSyncPage(QWidget):
|
|||||||
title = QTableWidgetItem(label)
|
title = QTableWidgetItem(label)
|
||||||
title.setData(Qt.UserRole, it)
|
title.setData(Qt.UserRole, it)
|
||||||
self.sync_table.setItem(row, 1, title)
|
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))
|
self.sync_table.setItem(row, 2, QTableWidgetItem(kind_label))
|
||||||
size = it.get("size")
|
size = it.get("size")
|
||||||
self.sync_table.setItem(row, 3, NumericItem(f"{size / (1024 * 1024):.1f} MB" if size else "", size or 0))
|
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
|
self._gen += 1
|
||||||
gen = self._gen
|
gen = self._gen
|
||||||
if not self._root:
|
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.status.setText("No PSP connected")
|
||||||
self._populate(self._inventory)
|
self._populate(self._inventory)
|
||||||
return
|
return
|
||||||
@@ -2979,21 +2991,26 @@ class PspSyncPage(QWidget):
|
|||||||
if gen != self._gen:
|
if gen != self._gen:
|
||||||
return
|
return
|
||||||
if "error" in res:
|
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']}")
|
self.status.setText(f"Error reading PSP: {res['error']}")
|
||||||
log.warning("psp inventory error: %s", res["error"])
|
log.warning("psp inventory error: %s", res["error"])
|
||||||
else:
|
else:
|
||||||
inventory = res["inventory"]
|
inventory = res["inventory"]
|
||||||
self._inventory = 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["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")
|
usage = res.get("usage")
|
||||||
parts = [
|
parts = [
|
||||||
f"{len(inventory['games'])} game(s)",
|
f"{len(inventory['games'])} game(s)",
|
||||||
f"{len(inventory['videos'])} video(s)",
|
f"{len(inventory['videos'])} video(s)",
|
||||||
f"{len(inventory['homebrew'])} homebrew",
|
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:
|
if usage is not None:
|
||||||
free_mb = usage["free"] / (1024 * 1024)
|
free_mb = usage["free"] / (1024 * 1024)
|
||||||
total_mb = usage["total"] / (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.games_table, inventory.get("games", []), "games")
|
||||||
self._fill_table(self.videos_table, inventory.get("videos", []), "videos")
|
self._fill_table(self.videos_table, inventory.get("videos", []), "videos")
|
||||||
self._fill_table(self.homebrew_table, inventory.get("homebrew", []), "homebrew")
|
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")
|
self._fill_table(self.pkg_table, inventory.get("pkg", []), "pkg")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -3055,6 +3074,11 @@ class PspSyncPage(QWidget):
|
|||||||
table.setItem(row, 2, name)
|
table.setItem(row, 2, name)
|
||||||
table.setItem(row, 3, QTableWidgetItem(added))
|
table.setItem(row, 3, QTableWidgetItem(added))
|
||||||
table.setItem(row, 4, NumericItem(f"{size / (1024 * 1024):.1f} MB" if size else "", size))
|
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:
|
else:
|
||||||
table.setItem(row, 1, name)
|
table.setItem(row, 1, name)
|
||||||
table.setItem(row, 2, QTableWidgetItem(added))
|
table.setItem(row, 2, QTableWidgetItem(added))
|
||||||
@@ -3064,7 +3088,8 @@ class PspSyncPage(QWidget):
|
|||||||
|
|
||||||
def _current_inventory_table(self):
|
def _current_inventory_table(self):
|
||||||
w = self.tabs.currentWidget()
|
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 w
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|||||||
+34
-1
@@ -221,8 +221,39 @@ def _pkg_items(root):
|
|||||||
return items
|
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):
|
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)
|
root = Path(root)
|
||||||
games, videos, homebrew_isos = _iso_items(root)
|
games, videos, homebrew_isos = _iso_items(root)
|
||||||
return {
|
return {
|
||||||
@@ -230,6 +261,8 @@ def list_installed(root):
|
|||||||
"videos": videos,
|
"videos": videos,
|
||||||
"homebrew": _homebrew_items(root) + homebrew_isos,
|
"homebrew": _homebrew_items(root) + homebrew_isos,
|
||||||
"pkg": _pkg_items(root),
|
"pkg": _pkg_items(root),
|
||||||
|
"themes": _theme_items(root),
|
||||||
|
"dlc": _dlc_items(root),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user