🐛 | Code quality fixes
All checks were successful
SonarQube Code Quality Scan / SonarQube Scan (push) Successful in 1m43s
All checks were successful
SonarQube Code Quality Scan / SonarQube Scan (push) Successful in 1m43s
- config.py: move 'import shutil' to module level - player.py: unify type hints, narrow except, add debug log - discord_rpc.py: replace broad except Exception with specific types - gui.py: remove unused import; replace lambda closures with method refs; fix closure scope bug; extract _populate_list_page() to remove duplicate code; add public accessors; make flush_volume public; add AudioVisualizer.reset_activity() - tests: replace unused _f variable with Path.touch()
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
"""Tunetti configuration — stored at ~/.config/tunetti/config.json ."""
|
"""Tunetti configuration — stored at ~/.config/tunetti/config.json ."""
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# ── Config file location (XDG compliant) ──────────────────────────────────
|
# ── Config file location (XDG compliant) ──────────────────────────────────
|
||||||
@@ -42,7 +43,6 @@ def _migrate_legacy() -> None:
|
|||||||
if old_db_path.parent == Path(__file__).parent and old_db_path.exists():
|
if old_db_path.parent == Path(__file__).parent and old_db_path.exists():
|
||||||
new_db_path = CONFIG_DIR / "music_history.db"
|
new_db_path = CONFIG_DIR / "music_history.db"
|
||||||
if not new_db_path.exists():
|
if not new_db_path.exists():
|
||||||
import shutil
|
|
||||||
shutil.copy2(str(old_db_path), str(new_db_path))
|
shutil.copy2(str(old_db_path), str(new_db_path))
|
||||||
old["db_path"] = str(new_db_path)
|
old["db_path"] = str(new_db_path)
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class DiscordRPC:
|
|||||||
log.warning("Discord not running – RPC unavailable")
|
log.warning("Discord not running – RPC unavailable")
|
||||||
self._connected = False
|
self._connected = False
|
||||||
return False
|
return False
|
||||||
except Exception as exc:
|
except (OSError, RuntimeError, DiscordNotFound) as exc:
|
||||||
log.debug("Discord RPC connect failed: %s", exc)
|
log.debug("Discord RPC connect failed: %s", exc)
|
||||||
self._connected = False
|
self._connected = False
|
||||||
return False
|
return False
|
||||||
@@ -125,7 +125,7 @@ class DiscordRPC:
|
|||||||
if self._rpc:
|
if self._rpc:
|
||||||
try:
|
try:
|
||||||
self._rpc.close()
|
self._rpc.close()
|
||||||
except Exception:
|
except (OSError, RuntimeError):
|
||||||
pass
|
pass
|
||||||
self._rpc = None
|
self._rpc = None
|
||||||
self._connected = False
|
self._connected = False
|
||||||
@@ -139,7 +139,7 @@ class DiscordRPC:
|
|||||||
if self._connected and self._rpc is not None:
|
if self._connected and self._rpc is not None:
|
||||||
try:
|
try:
|
||||||
self._rpc.clear()
|
self._rpc.clear()
|
||||||
except Exception:
|
except (OSError, RuntimeError):
|
||||||
self._connected = False
|
self._connected = False
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ class DiscordRPC:
|
|||||||
start=start,
|
start=start,
|
||||||
end=end_ts,
|
end=end_ts,
|
||||||
)
|
)
|
||||||
except Exception as exc:
|
except (OSError, RuntimeError) as exc:
|
||||||
log.debug("RPC update failed: %s", exc)
|
log.debug("RPC update failed: %s", exc)
|
||||||
self._connected = False
|
self._connected = False
|
||||||
|
|
||||||
|
|||||||
123
gui.py
123
gui.py
@@ -9,7 +9,6 @@ import logging
|
|||||||
import sys
|
import sys
|
||||||
import math
|
import math
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
# Qt imports are guarded so the module can be imported for its pure helper
|
# Qt imports are guarded so the module can be imported for its pure helper
|
||||||
@@ -169,7 +168,10 @@ def _load_thumbnail(video_id: str, thumb_url: str,
|
|||||||
nam = _get_thumbnail_nam()
|
nam = _get_thumbnail_nam()
|
||||||
reply = nam.get(QNetworkRequest(QUrl(thumb_url)))
|
reply = nam.get(QNetworkRequest(QUrl(thumb_url)))
|
||||||
|
|
||||||
def _on_downloaded():
|
def _on_downloaded(reply=reply, nam=nam, cache_path=cache_path,
|
||||||
|
art_label=art_label, size=size,
|
||||||
|
style_pass=style_pass, style_fail=style_fail,
|
||||||
|
video_id=video_id):
|
||||||
# The label may have been deleted (e.g. search results refreshed)
|
# The label may have been deleted (e.g. search results refreshed)
|
||||||
# while the network request was in flight — guard against that.
|
# while the network request was in flight — guard against that.
|
||||||
try:
|
try:
|
||||||
@@ -408,14 +410,17 @@ class SongItemWidget(QWidget):
|
|||||||
# flooding the connection when many items appear at once (e.g. search).
|
# flooding the connection when many items appear at once (e.g. search).
|
||||||
# Guard with try/except because the widget may be deleted before the
|
# Guard with try/except because the widget may be deleted before the
|
||||||
# timer fires (search results refreshed, etc.).
|
# timer fires (search results refreshed, etc.).
|
||||||
|
self._deferred_song = song
|
||||||
if _cached_thumb_path(self._video_id).exists():
|
if _cached_thumb_path(self._video_id).exists():
|
||||||
self._load_art(self._art, song)
|
self._load_art(self._art, song)
|
||||||
else:
|
else:
|
||||||
QTimer.singleShot(500, lambda: self._deferred_load_art(song))
|
QTimer.singleShot(500, self._load_deferred_art)
|
||||||
|
|
||||||
def _deferred_load_art(self, song: dict) -> None:
|
def _load_deferred_art(self) -> None:
|
||||||
try:
|
try:
|
||||||
self._load_art(self._art, song)
|
song = self._deferred_song
|
||||||
|
if song:
|
||||||
|
self._load_art(self._art, song)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
pass # widget was deleted
|
pass # widget was deleted
|
||||||
|
|
||||||
@@ -589,6 +594,10 @@ class AudioVisualizer(QWidget):
|
|||||||
* Idle "breathing" animation when audio is playing but no buffer arrives.
|
* Idle "breathing" animation when audio is playing but no buffer arrives.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
def reset_activity(self) -> None:
|
||||||
|
"""Reset the last activity counter (called on seek)."""
|
||||||
|
self._last_activity = 0
|
||||||
|
|
||||||
# Windows XP marquee loading pattern — sliding 4-bar block
|
# Windows XP marquee loading pattern — sliding 4-bar block
|
||||||
_LOADING_PATTERN = [0.55, 0.85, 0.85, 0.55]
|
_LOADING_PATTERN = [0.55, 0.85, 0.85, 0.55]
|
||||||
|
|
||||||
@@ -1280,7 +1289,8 @@ class PlaybackBar(QWidget):
|
|||||||
self._progress.setEnabled(False)
|
self._progress.setEnabled(False)
|
||||||
self._visualizer.set_playing(False)
|
self._visualizer.set_playing(False)
|
||||||
self._visualizer.set_loading(False)
|
self._visualizer.set_loading(False)
|
||||||
QTimer.singleShot(4000, lambda: self._clear_error())
|
# Use a weak-bound method ref so the timer doesn't keep self alive.
|
||||||
|
QTimer.singleShot(4000, self._clear_error)
|
||||||
|
|
||||||
def _clear_error(self) -> None:
|
def _clear_error(self) -> None:
|
||||||
"""Restore the "Not playing" text after an error."""
|
"""Restore the "Not playing" text after an error."""
|
||||||
@@ -1392,8 +1402,11 @@ class PlaybackBar(QWidget):
|
|||||||
# to the new audio section instead of decaying through idle.
|
# to the new audio section instead of decaying through idle.
|
||||||
if hasattr(self, "_band_peaks"):
|
if hasattr(self, "_band_peaks"):
|
||||||
self._band_peaks = [0.001] * len(self._band_peaks)
|
self._band_peaks = [0.001] * len(self._band_peaks)
|
||||||
self._visualizer._last_activity = 0
|
self._visualizer.reset_activity()
|
||||||
QTimer.singleShot(200, lambda: setattr(self, "_updating_progress", False))
|
QTimer.singleShot(200, self._clear_seek_flag)
|
||||||
|
|
||||||
|
def _clear_seek_flag(self) -> None:
|
||||||
|
self._updating_progress = False
|
||||||
|
|
||||||
def _toggle_mute(self) -> None:
|
def _toggle_mute(self) -> None:
|
||||||
if self._player.volume() > 0:
|
if self._player.volume() > 0:
|
||||||
@@ -1431,9 +1444,7 @@ class PlaybackBar(QWidget):
|
|||||||
self._vol_save_timer = QTimer(self)
|
self._vol_save_timer = QTimer(self)
|
||||||
self._vol_save_timer.setSingleShot(True)
|
self._vol_save_timer.setSingleShot(True)
|
||||||
self._vol_save_timer.setInterval(500)
|
self._vol_save_timer.setInterval(500)
|
||||||
self._vol_save_timer.timeout.connect(
|
self._vol_save_timer.timeout.connect(self.flush_volume)
|
||||||
lambda: save_volume(self._saved_volume)
|
|
||||||
)
|
|
||||||
self._vol_save_timer.start()
|
self._vol_save_timer.start()
|
||||||
|
|
||||||
# ── Visualizer data feeding ──
|
# ── Visualizer data feeding ──
|
||||||
@@ -1597,7 +1608,7 @@ class PlaybackBar(QWidget):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _flush_volume(self) -> None:
|
def flush_volume(self) -> None:
|
||||||
"""Immediately persist the current volume, bypassing the debounce timer."""
|
"""Immediately persist the current volume, bypassing the debounce timer."""
|
||||||
if hasattr(self, "_vol_save_timer") and self._vol_save_timer.isActive():
|
if hasattr(self, "_vol_save_timer") and self._vol_save_timer.isActive():
|
||||||
self._vol_save_timer.stop()
|
self._vol_save_timer.stop()
|
||||||
@@ -1814,6 +1825,7 @@ class SearchPage(QWidget):
|
|||||||
btn = QPushButton("⋯ Show more")
|
btn = QPushButton("⋯ Show more")
|
||||||
btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
btn.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||||
btn.setFixedHeight(38)
|
btn.setFixedHeight(38)
|
||||||
|
btn.setProperty("category", category)
|
||||||
btn.setStyleSheet("""
|
btn.setStyleSheet("""
|
||||||
QPushButton {
|
QPushButton {
|
||||||
border: none;
|
border: none;
|
||||||
@@ -1829,9 +1841,17 @@ class SearchPage(QWidget):
|
|||||||
color: #a5b4fc;
|
color: #a5b4fc;
|
||||||
}
|
}
|
||||||
""")
|
""")
|
||||||
btn.clicked.connect(lambda: self._toggle_expand(category))
|
btn.clicked.connect(self._on_show_more_clicked)
|
||||||
self._results_layout.addWidget(btn)
|
self._results_layout.addWidget(btn)
|
||||||
|
|
||||||
|
def _on_show_more_clicked(self) -> None:
|
||||||
|
btn = self.sender()
|
||||||
|
if btn is None:
|
||||||
|
return
|
||||||
|
category = btn.property("category")
|
||||||
|
if isinstance(category, str):
|
||||||
|
self._toggle_expand(category)
|
||||||
|
|
||||||
def _toggle_expand(self, category: str) -> None:
|
def _toggle_expand(self, category: str) -> None:
|
||||||
if category == "videos":
|
if category == "videos":
|
||||||
self._videos_expanded = True
|
self._videos_expanded = True
|
||||||
@@ -2194,7 +2214,7 @@ class SettingsDialog(QDialog):
|
|||||||
|
|
||||||
def _on_visualizer_toggled(self, enabled: bool) -> None:
|
def _on_visualizer_toggled(self, enabled: bool) -> None:
|
||||||
save_setting("visualizer_enabled", enabled)
|
save_setting("visualizer_enabled", enabled)
|
||||||
viz = self._main_window._visualizer
|
viz = self._main_window.get_visualizer()
|
||||||
if enabled:
|
if enabled:
|
||||||
viz.setVisible(True)
|
viz.setVisible(True)
|
||||||
viz.setFixedHeight(44)
|
viz.setFixedHeight(44)
|
||||||
@@ -2207,13 +2227,13 @@ class SettingsDialog(QDialog):
|
|||||||
def _on_discord_toggled(self, enabled: bool) -> None:
|
def _on_discord_toggled(self, enabled: bool) -> None:
|
||||||
save_setting("discord_rpc_enabled", enabled)
|
save_setting("discord_rpc_enabled", enabled)
|
||||||
if enabled:
|
if enabled:
|
||||||
self._main_window.rpc.start()
|
self._main_window.get_rpc().start()
|
||||||
# Re-send current song if one is playing.
|
# Re-send current song if one is playing.
|
||||||
current = self._main_window.player.get_current()
|
current = self._main_window.player.get_current()
|
||||||
if current:
|
if current:
|
||||||
self._main_window.rpc.update_song(current)
|
self._main_window.get_rpc().update_song(current)
|
||||||
else:
|
else:
|
||||||
self._main_window.rpc.stop()
|
self._main_window.get_rpc().stop()
|
||||||
|
|
||||||
|
|
||||||
# ── Helpers ───────────────────────────────────────────────────────────────────
|
# ── Helpers ───────────────────────────────────────────────────────────────────
|
||||||
@@ -2825,36 +2845,8 @@ class TunettiWindow(QMainWindow):
|
|||||||
# ── Favourites ──
|
# ── Favourites ──
|
||||||
|
|
||||||
def _refresh_favourites(self) -> None:
|
def _refresh_favourites(self) -> None:
|
||||||
lst = self._fav_page._list
|
|
||||||
lst.clear()
|
|
||||||
rows = self.db.get_favourites()
|
rows = self.db.get_favourites()
|
||||||
if not rows:
|
self._populate_list_page(self._fav_page, rows, default_fav=True)
|
||||||
lst.addItem("No favourites yet.")
|
|
||||||
self._fav_page._all_songs = []
|
|
||||||
return
|
|
||||||
songs_data = []
|
|
||||||
for r in rows:
|
|
||||||
song = {
|
|
||||||
"videoId": r["video_id"],
|
|
||||||
"title": r["title"],
|
|
||||||
"artists": json.loads(r["artists"]) if r["artists"] else [],
|
|
||||||
"album": json.loads(r["album"]) if r["album"] else None,
|
|
||||||
"duration": r["duration"],
|
|
||||||
"duration_label": _fmt_ms(r["duration"] * 1000) if r["duration"] else "?",
|
|
||||||
"thumbnail": r["thumbnail"] or "",
|
|
||||||
"_is_fav": True,
|
|
||||||
}
|
|
||||||
songs_data.append(song)
|
|
||||||
widget = SongItemWidget(song, is_fav=True)
|
|
||||||
widget.play_requested.connect(self._play_video)
|
|
||||||
widget.fav_toggled.connect(self._on_fav_toggled)
|
|
||||||
widget.queue_next_requested.connect(self._queue_next)
|
|
||||||
item = QListWidgetItem(lst)
|
|
||||||
item.setSizeHint(widget.sizeHint())
|
|
||||||
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsSelectable)
|
|
||||||
lst.addItem(item)
|
|
||||||
lst.setItemWidget(item, widget)
|
|
||||||
self._fav_page._all_songs = songs_data
|
|
||||||
|
|
||||||
@Slot(str, bool)
|
@Slot(str, bool)
|
||||||
def _on_fav_toggled(self, video_id: str, new_state: bool) -> None:
|
def _on_fav_toggled(self, video_id: str, new_state: bool) -> None:
|
||||||
@@ -2863,15 +2855,27 @@ class TunettiWindow(QMainWindow):
|
|||||||
# ── History ──
|
# ── History ──
|
||||||
|
|
||||||
def _refresh_history(self) -> None:
|
def _refresh_history(self) -> None:
|
||||||
lst = self._hist_page._list
|
|
||||||
lst.clear()
|
|
||||||
rows = self.db.get_history(50)
|
rows = self.db.get_history(50)
|
||||||
|
self._populate_list_page(self._hist_page, rows, default_fav=False)
|
||||||
|
|
||||||
|
def _populate_list_page(self, page, rows, default_fav: bool = False) -> None:
|
||||||
|
"""Populate a QListWidget-based page from DB rows.
|
||||||
|
|
||||||
|
Shared by _refresh_favourites and _refresh_history to avoid
|
||||||
|
duplicate widget-building code.
|
||||||
|
"""
|
||||||
|
lst = page._list
|
||||||
|
lst.clear()
|
||||||
|
|
||||||
if not rows:
|
if not rows:
|
||||||
lst.addItem("No play history yet.")
|
placeholder = "No favourites yet." if default_fav else "No play history yet."
|
||||||
self._hist_page._all_songs = []
|
lst.addItem(placeholder)
|
||||||
|
page._all_songs = []
|
||||||
return
|
return
|
||||||
|
|
||||||
songs_data = []
|
songs_data = []
|
||||||
for r in rows:
|
for r in rows:
|
||||||
|
is_fav = default_fav or bool(r["is_favourite"])
|
||||||
song = {
|
song = {
|
||||||
"videoId": r["video_id"],
|
"videoId": r["video_id"],
|
||||||
"title": r["title"],
|
"title": r["title"],
|
||||||
@@ -2880,10 +2884,10 @@ class TunettiWindow(QMainWindow):
|
|||||||
"duration": r["duration"],
|
"duration": r["duration"],
|
||||||
"duration_label": _fmt_ms(r["duration"] * 1000) if r["duration"] else "?",
|
"duration_label": _fmt_ms(r["duration"] * 1000) if r["duration"] else "?",
|
||||||
"thumbnail": r["thumbnail"] or "",
|
"thumbnail": r["thumbnail"] or "",
|
||||||
"_is_fav": bool(r["is_favourite"]),
|
"_is_fav": is_fav,
|
||||||
}
|
}
|
||||||
songs_data.append(song)
|
songs_data.append(song)
|
||||||
widget = SongItemWidget(song, is_fav=bool(r["is_favourite"]))
|
widget = SongItemWidget(song, is_fav=is_fav)
|
||||||
widget.play_requested.connect(self._play_video)
|
widget.play_requested.connect(self._play_video)
|
||||||
widget.fav_toggled.connect(self._on_fav_toggled)
|
widget.fav_toggled.connect(self._on_fav_toggled)
|
||||||
widget.queue_next_requested.connect(self._queue_next)
|
widget.queue_next_requested.connect(self._queue_next)
|
||||||
@@ -2892,7 +2896,7 @@ class TunettiWindow(QMainWindow):
|
|||||||
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsSelectable)
|
item.setFlags(item.flags() | Qt.ItemFlag.ItemIsSelectable)
|
||||||
lst.addItem(item)
|
lst.addItem(item)
|
||||||
lst.setItemWidget(item, widget)
|
lst.setItemWidget(item, widget)
|
||||||
self._hist_page._all_songs = songs_data
|
page._all_songs = songs_data
|
||||||
|
|
||||||
# ── Stats ──
|
# ── Stats ──
|
||||||
|
|
||||||
@@ -2939,6 +2943,17 @@ class TunettiWindow(QMainWindow):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.error("_poll_minimized error: %s", exc)
|
log.error("_poll_minimized error: %s", exc)
|
||||||
|
|
||||||
|
# ── Public accessors (for child dialogs) ──
|
||||||
|
|
||||||
|
def get_visualizer(self) -> AudioVisualizer:
|
||||||
|
return self._visualizer
|
||||||
|
|
||||||
|
def get_rpc(self) -> DiscordRPC:
|
||||||
|
return self.rpc
|
||||||
|
|
||||||
|
def get_player(self) -> AudioPlayer:
|
||||||
|
return self.player
|
||||||
|
|
||||||
# ── Cleanup ──
|
# ── Cleanup ──
|
||||||
|
|
||||||
def resizeEvent(self, event) -> None:
|
def resizeEvent(self, event) -> None:
|
||||||
@@ -2962,7 +2977,7 @@ class TunettiWindow(QMainWindow):
|
|||||||
|
|
||||||
def closeEvent(self, event) -> None:
|
def closeEvent(self, event) -> None:
|
||||||
# Flush any pending volume save before closing
|
# Flush any pending volume save before closing
|
||||||
self._playback._flush_volume()
|
self._playback.flush_volume()
|
||||||
self.player.shutdown()
|
self.player.shutdown()
|
||||||
self.rpc.stop()
|
self.rpc.stop()
|
||||||
self.db.close()
|
self.db.close()
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ import shutil
|
|||||||
import threading
|
import threading
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
# In this module we use Optional[str] for consistency
|
||||||
|
# with Qt signal/slot typing conventions.
|
||||||
|
|
||||||
import yt_dlp
|
import yt_dlp
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
@@ -75,7 +78,7 @@ VERBOSE: bool = False # enable yt-dlp debug output
|
|||||||
# Temp-file housekeeping
|
# Temp-file housekeeping
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
_TUNETTI_TMP: str | None = None
|
_TUNETTI_TMP: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
def _tmp_dir() -> str:
|
def _tmp_dir() -> str:
|
||||||
@@ -166,6 +169,8 @@ class DownloadWorker(QObject):
|
|||||||
@Slot(int, dict)
|
@Slot(int, dict)
|
||||||
def _do_download(self, task_id: int, song: dict) -> None:
|
def _do_download(self, task_id: int, song: dict) -> None:
|
||||||
"""Entry point — invoked on the worker thread."""
|
"""Entry point — invoked on the worker thread."""
|
||||||
|
log.debug("Download starting: task_id=%d, video_id=%s",
|
||||||
|
task_id, song.get("videoId") or song.get("video_id", ""))
|
||||||
self._task_id = task_id
|
self._task_id = task_id
|
||||||
self._cancelled = False
|
self._cancelled = False
|
||||||
|
|
||||||
@@ -232,7 +237,7 @@ class DownloadWorker(QObject):
|
|||||||
if not self._cancelled:
|
if not self._cancelled:
|
||||||
self.download_succeeded.emit(task_id, resolved)
|
self.download_succeeded.emit(task_id, resolved)
|
||||||
|
|
||||||
except Exception as exc:
|
except (OSError, RuntimeError) as exc:
|
||||||
log.error("Download error for %s: %s", video_id, exc)
|
log.error("Download error for %s: %s", video_id, exc)
|
||||||
self._cleanup_path(temp_path)
|
self._cleanup_path(temp_path)
|
||||||
with self._lock:
|
with self._lock:
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import os
|
|||||||
import struct
|
import struct
|
||||||
import math
|
import math
|
||||||
import tempfile
|
import tempfile
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@@ -239,8 +240,9 @@ class TestStaleFiles:
|
|||||||
td = tempfile.mkdtemp(prefix="tunetti_test_")
|
td = tempfile.mkdtemp(prefix="tunetti_test_")
|
||||||
monkeypatch.setattr("player._TUNETTI_TMP", td)
|
monkeypatch.setattr("player._TUNETTI_TMP", td)
|
||||||
|
|
||||||
with open(os.path.join(td, "vid123.mp3"), "w") as _f:
|
# Create an empty file to verify it's excluded
|
||||||
pass # empty; _f unused intentionally
|
empty_path = os.path.join(td, "vid123.mp3")
|
||||||
|
Path(empty_path).touch()
|
||||||
|
|
||||||
result = _stale_files("vid123")
|
result = _stale_files("vid123")
|
||||||
assert result == []
|
assert result == []
|
||||||
|
|||||||
Reference in New Issue
Block a user