feat: flag playlist files missing from disk (⚠ orange strikethrough)

Missing files are kept in the list instead of being silently dropped on load,
and styled distinctly with a tooltip. add_files gains allow_missing; tab
restore keeps missing entries so they're visible.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-06 12:12:03 +02:00
parent ec7138f51b
commit 109bc658c3
+29 -24
View File
@@ -3184,6 +3184,7 @@ class PlaylistWidget(QListWidget):
self._folder_counts: dict[str, dict[str, int]] = {} # path → {folder: count} self._folder_counts: dict[str, dict[str, int]] = {} # path → {folder: count}
self._all_subcat_counts: dict[str, int] = {} # profile-wide folder → count self._all_subcat_counts: dict[str, int] = {} # profile-wide folder → count
self._separators_before: set[str] = set() # paths that show a separator row above self._separators_before: set[str] = set() # paths that show a separator row above
self._missing: set[str] = set() # paths not present on disk
self._visible: list[str | None] = [] # rows shown; None = separator row self._visible: list[str | None] = [] # rows shown; None = separator row
self._selected_path: str | None = None self._selected_path: str | None = None
self.itemClicked.connect(self._on_item_clicked) self.itemClicked.connect(self._on_item_clicked)
@@ -3238,6 +3239,7 @@ class PlaylistWidget(QListWidget):
# Drop separator anchors for paths no longer present (keep end sentinel). # Drop separator anchors for paths no longer present (keep end sentinel).
self._separators_before &= set(self._paths) | {self._SEP_END} self._separators_before &= set(self._paths) | {self._SEP_END}
visible_paths = [p for p in self._paths if self._is_visible(p)] visible_paths = [p for p in self._paths if self._is_visible(p)]
self._missing = {p for p in visible_paths if not os.path.isfile(p)}
self._visible = [] self._visible = []
for path in visible_paths: for path in visible_paths:
if path in self._separators_before: if path in self._separators_before:
@@ -3265,15 +3267,26 @@ class PlaylistWidget(QListWidget):
return item return item
def _style_item(self, item: "QListWidgetItem", path: str) -> None: def _style_item(self, item: "QListWidgetItem", path: str) -> None:
"""Set an item's text and color based on hidden/done/disabled state.""" """Set an item's text and color based on hidden/done/disabled/missing state."""
name = os.path.basename(path) name = os.path.basename(path)
font = item.font()
font.setItalic(False)
font.setStrikeOut(False)
if name in self._hidden_basenames: if name in self._hidden_basenames:
item.setText(f"[hidden] {name}") item.setText(f"[hidden] {name}")
item.setForeground(QColor(120, 120, 120)) item.setForeground(QColor(120, 120, 120))
font = item.font()
font.setItalic(True) font.setItalic(True)
item.setFont(font) item.setFont(font)
return return
if path in self._missing:
item.setText(f"{name}")
item.setForeground(QColor(230, 120, 60)) # orange — missing on disk
font.setStrikeOut(True)
item.setFont(font)
item.setToolTip("File missing from disk")
return
item.setFont(font)
item.setToolTip("")
n = self._done_counts.get(path, 0) n = self._done_counts.get(path, 0)
if path in self._done_set: if path in self._done_set:
tag = f"[{n}]" if n else "" tag = f"[{n}]" if n else ""
@@ -3296,12 +3309,15 @@ class PlaylistWidget(QListWidget):
self._selected_path = None self._selected_path = None
self._rebuild() self._rebuild()
def add_files(self, paths: list[str]) -> None: def add_files(self, paths: list[str], allow_missing: bool = False) -> None:
was_empty = len(self._paths) == 0 was_empty = len(self._paths) == 0
for path in paths: for path in paths:
if path not in self._path_set and os.path.isfile(path): if path in self._path_set:
self._paths.append(path) continue
self._path_set.add(path) if not allow_missing and not os.path.isfile(path):
continue
self._paths.append(path)
self._path_set.add(path)
self._rebuild() self._rebuild()
if was_empty and self._visible: if was_empty and self._visible:
self._select(0) self._select(0)
@@ -3379,13 +3395,8 @@ class PlaylistWidget(QListWidget):
path = self._visible[row] path = self._visible[row]
if path is None: if path is None:
return return
name = os.path.basename(path) self._style_item(item, path)
if path in self._done_set: item.setText(f"{item.text()}")
n = self._done_counts.get(path, 0)
tag = f"[{n}] " if n else ""
else:
tag = ""
item.setText(f"{tag}{name}")
def _decorate_prev(self, row: int) -> None: def _decorate_prev(self, row: int) -> None:
item = self.item(row) item = self.item(row)
@@ -3394,13 +3405,7 @@ class PlaylistWidget(QListWidget):
path = self._visible[row] path = self._visible[row]
if path is None: if path is None:
return return
name = os.path.basename(path) self._style_item(item, path)
if path in self._done_set:
n = self._done_counts.get(path, 0)
tag = f"[{n}] " if n else ""
item.setText(f"{tag}{name}")
else:
item.setText(name)
def _on_item_clicked(self, item: QListWidgetItem) -> None: def _on_item_clicked(self, item: QListWidgetItem) -> None:
# Only load file when it's a plain click (no Ctrl/Shift for multi-select). # Only load file when it's a plain click (no Ctrl/Shift for multi-select).
@@ -4401,7 +4406,8 @@ class MainWindow(QMainWindow):
if separators: if separators:
pw._separators_before = set(separators) pw._separators_before = set(separators)
if files: if files:
pw.add_files([p for p in files if os.path.isfile(p)]) # Keep missing files so they're flagged in the list, not silently dropped.
pw.add_files(files, allow_missing=True)
if select: if select:
self._playlist_tabs.setCurrentIndex(idx) self._playlist_tabs.setCurrentIndex(idx)
if not self._loading_tabs: if not self._loading_tabs:
@@ -4474,14 +4480,13 @@ class MainWindow(QMainWindow):
if isinstance(seps, str): if isinstance(seps, str):
seps = [seps] if seps else [] seps = [seps] if seps else []
self._add_playlist_tab( self._add_playlist_tab(
"List 1", "List 1", files=list(files),
files=[f for f in files if os.path.isfile(f)],
separators=seps, select=True) separators=seps, select=True)
else: else:
for t in data["tabs"]: for t in data["tabs"]:
self._add_playlist_tab( self._add_playlist_tab(
t.get("label", "List"), t.get("label", "List"),
files=[f for f in t.get("files", []) if os.path.isfile(f)], files=list(t.get("files", [])),
separators=t.get("separators", []), select=False) separators=t.get("separators", []), select=False)
cur = min(max(0, data.get("current", 0)), cur = min(max(0, data.get("current", 0)),
self._playlist_tabs.count() - 1) self._playlist_tabs.count() - 1)