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:
@@ -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,10 +3309,13 @@ 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:
|
||||||
|
continue
|
||||||
|
if not allow_missing and not os.path.isfile(path):
|
||||||
|
continue
|
||||||
self._paths.append(path)
|
self._paths.append(path)
|
||||||
self._path_set.add(path)
|
self._path_set.add(path)
|
||||||
self._rebuild()
|
self._rebuild()
|
||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user