diff options
Diffstat (limited to 'tidal_dl_ng/gui.py')
| -rw-r--r-- | tidal_dl_ng/gui.py | 191 |
1 files changed, 190 insertions, 1 deletions
diff --git a/tidal_dl_ng/gui.py b/tidal_dl_ng/gui.py index df5c3d7..ac88dc5 100644 --- a/tidal_dl_ng/gui.py +++ b/tidal_dl_ng/gui.py @@ -55,6 +55,7 @@ from tidalapi.session import LinkLogin from tidal_dl_ng import __version__, update_available from tidal_dl_ng.dialog import DialogLogin, DialogPreferences, DialogVersion +from tidal_dl_ng.dialog_history import DialogHistory from tidal_dl_ng.helper.gui import ( FilterHeader, HumanProxyModel, @@ -101,6 +102,7 @@ from tidalapi.session import SearchTypes from tidal_dl_ng.config import HandlingApp, Settings, Tidal from tidal_dl_ng.constants import FAVORITES, QualityVideo, QueueDownloadStatus, TidalLists from tidal_dl_ng.download import Download +from tidal_dl_ng.history import HistoryService from tidal_dl_ng.logger import XStream, logger_gui from tidal_dl_ng.model.gui_data import ProgressBars, QueueDownloadItem, ResultItem, StatusbarMessage from tidal_dl_ng.model.meta import ReleaseLatest @@ -119,6 +121,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): settings: Settings tidal: Tidal dl: Download + history_service: HistoryService threadpool: QtCore.QThreadPool tray: QtWidgets.QSystemTrayIcon spinners: dict @@ -164,6 +167,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): # XStream.stderr().messageWritten.connect(self._log_output) self.settings = Settings() + self.history_service = HistoryService() self._init_threads() self._init_gui() @@ -177,6 +181,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self._populate_quality(self.cb_quality_video, QualityVideo) self._populate_search_types(self.cb_search_type, SearchTypes) self.apply_settings(self.settings) + self._init_menu_actions() self._init_signals() self._init_buttons() self.init_tidal(tidal) @@ -375,6 +380,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): tree.setColumnWidth(5, skinny_width) # duration tree.setColumnWidth(6, narrow_width) # quality tree.setColumnWidth(7, narrow_width) # date + tree.setColumnWidth(8, skinny_width) # downloaded? header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) # Connect the contextmenu tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) @@ -386,7 +392,17 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): Args: model (QStandardItemModel): The model to initialize. """ - labels_column: list[str] = ["#", "obj", "Artist", "Title", "Album", "Duration", "Quality", "Date"] + labels_column: list[str] = [ + "#", + "obj", + "Artist", + "Title", + "Album", + "Duration", + "Quality", + "Date", + "Downloaded?", + ] model.setColumnCount(len(labels_column)) model.setRowCount(0) @@ -505,6 +521,37 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): tree.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) tree.customContextMenuRequested.connect(self.menu_context_tree_lists) + def _init_menu_actions(self) -> None: + """Initialize custom menu actions.""" + # Create Tools menu if it doesn't exist + menubar = self.menuBar() + tools_menu = None + + # Find or create Tools menu + for action in menubar.actions(): + if action.text() == "Tools": + tools_menu = action.menu() + break + + if not tools_menu: + tools_menu = menubar.addMenu("Tools") + + # Create View History action + self.a_view_history = QtGui.QAction("View Download History...", self) + self.a_view_history.triggered.connect(self.on_view_history) + tools_menu.addAction(self.a_view_history) + + # Add separator + tools_menu.addSeparator() + + # Add duplicate prevention toggle + self.a_toggle_duplicate_prevention = QtGui.QAction("Prevent Duplicate Downloads", self) + self.a_toggle_duplicate_prevention.setCheckable(True) + is_preventing = self.history_service.get_settings().get("preventDuplicates", True) + self.a_toggle_duplicate_prevention.setChecked(is_preventing) + self.a_toggle_duplicate_prevention.triggered.connect(self.on_toggle_duplicate_prevention) + tools_menu.addAction(self.a_toggle_duplicate_prevention) + def on_update_check(self, on_startup: bool = True) -> None: """Check for application updates and emit update signals. @@ -516,6 +563,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): if (on_startup and is_available) or not on_startup: self.s_update_show.emit(True, is_available, info) + # The rest of the class remains unchanged def apply_settings(self, settings: Settings) -> None: """Apply user settings to the GUI. @@ -636,6 +684,18 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): if isinstance(media, Track | Video) and hasattr(media, "album") and media.album: menu.addAction("Download Full Album", lambda: self.thread_download_album_from_track(point)) + # Add mark/unmark as downloaded for Tracks + if isinstance(media, Track): + track_id = str(media.id) + is_downloaded = self.history_service.is_downloaded(track_id) + + if is_downloaded: + menu.addAction( + "✖️ Mark as Not Downloaded", lambda: self.on_mark_track_as_not_downloaded(track_id, index) + ) + else: + menu.addAction("✅ Mark as Downloaded", lambda: self.on_mark_track_as_downloaded(media, index)) + menu.addAction("Copy Share URL", lambda: self.on_copy_url_share(self.tr_results, point)) menu.exec(self.tr_results.mapToGlobal(point)) @@ -676,6 +736,77 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): self.tr_queue_download.takeTopLevelItem(index) logger_gui.info("Removed item from download queue") + def on_mark_track_as_downloaded(self, track: Track, index: QtCore.QModelIndex) -> None: + """Mark a track as downloaded in history. + + Args: + track (Track): The track to mark. + index (QModelIndex): The model index of the track. + """ + track_id = str(track.id) + + # Determine source information (manual for now) + source_type = "manual" + source_id = None + source_name = None + + # Try to get album information if available + if hasattr(track, "album") and track.album: + source_type = "album" + source_id = str(track.album.id) + source_name = track.album.name + + self.history_service.add_track_to_history( + track_id=track_id, source_type=source_type, source_id=source_id, source_name=source_name + ) + + # Update the UI - refresh the downloaded column + self._update_downloaded_column(index, True) + logger_gui.info(f"Marked track as downloaded: {track.name}") + + def on_mark_track_as_not_downloaded(self, track_id: str, index: QtCore.QModelIndex) -> None: + """Remove a track from download history. + + Args: + track_id (str): The track ID to remove. + index (QModelIndex): The model index of the track. + """ + success = self.history_service.remove_track_from_history(track_id) + + if success: + # Update the UI - refresh the downloaded column + self._update_downloaded_column(index, False) + logger_gui.info(f"Unmarked track (ID: {track_id})") + + def _update_downloaded_column(self, index: QtCore.QModelIndex, is_downloaded: bool) -> None: + """Update the Downloaded? column for a specific index. + + Args: + index (QModelIndex): The model index of the item. + is_downloaded (bool): Whether the track is downloaded. + """ + # Get the source index (column 0) + source_index = self.proxy_tr_results.mapToSource(index) + + # Get the item from the model + item = self.model_tr_results.itemFromIndex(source_index) + if not item: + return + + # Get the parent row (the row contains all columns) + row = item.row() + parent = item.parent() + + # Column 8 is "Downloaded?" + downloaded_item = self.model_tr_results.item(row, 8) if parent is None else parent.child(row, 8) + + if downloaded_item: + if is_downloaded: + downloaded_item.setText("✅") + downloaded_item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + else: + downloaded_item.setText("") + def thread_download_list_media(self, point: QtCore.QPoint) -> None: """Start download of a list media item in a thread. @@ -1228,6 +1359,14 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): item.date_user_added if item.date_user_added != "" else item.date_release ) + # Check download history + child_downloaded: QtGui.QStandardItem = QtGui.QStandardItem() + if isinstance(item.obj, Track): + track_id = str(item.obj.id) + if self.history_service.is_downloaded(track_id): + child_downloaded.setText("✅") + child_downloaded.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignCenter) + if isinstance(item.obj, Mix | Playlist | Album | Artist): # Add a disabled dummy child, so expansion arrow will appear. This Child will be replaced on expansion. child_dummy: QtGui.QStandardItem = QtGui.QStandardItem() @@ -1244,6 +1383,7 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): child_duration, child_quality, child_date, + child_downloaded, ) def on_tr_results_add_top_level_item(self, item_child: Sequence[QtGui.QStandardItem]): @@ -2296,6 +2436,34 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): file_template = get_format_template(media, self.settings) + # Determine source information + source_type = "manual" + source_id = None + source_name = None + + if isinstance(media, Album): + source_type = "album" + source_id = str(media.id) + source_name = media.name + elif isinstance(media, Playlist | UserPlaylist): + source_type = "playlist" + source_id = str(media.id) if hasattr(media, "id") else None + source_name = media.name if hasattr(media, "name") else None + elif isinstance(media, Mix): + source_type = "mix" + source_id = str(media.id) + source_name = media.title + elif isinstance(media, Track): + # For individual tracks, try to get album info + if hasattr(media, "album") and media.album: + source_type = "album" + source_id = str(media.album.id) + source_name = media.album.name + else: + source_type = "track" + source_id = str(media.id) + source_name = media.name + if isinstance(media, Track | Video): result_dl, path_file = dl.item( media=media, @@ -2303,6 +2471,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): download_delay=delay_track, quality_audio=quality_audio, quality_video=quality_video, + source_type=source_type, + source_id=source_id, + source_name=source_name, ) elif isinstance(media, Album | Playlist | Mix): dl.items( @@ -2312,6 +2483,9 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): download_delay=self.settings.data.download_delay, quality_audio=quality_audio, quality_video=quality_video, + source_type=source_type, + source_id=source_id, + source_name=source_name, ) # Dummy values @@ -2345,6 +2519,21 @@ class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow): """Open the preferences dialog.""" DialogPreferences(settings=self.settings, settings_save=self.s_settings_save, parent=self) + def on_view_history(self) -> None: + """Open the download history dialog.""" + DialogHistory(history_service=self.history_service, parent=self) + + def on_toggle_duplicate_prevention(self, enabled: bool) -> None: + """Toggle duplicate download prevention on or off. + + Args: + enabled (bool): Whether duplicate prevention is enabled. + """ + self.history_service.update_settings(preventDuplicates=enabled) + status_msg = "enabled" if enabled else "disabled" + logger_gui.info(f"Duplicate download prevention {status_msg}") + self.s_statusbar_message.emit(StatusbarMessage(message=f"Duplicate prevention {status_msg}.", timeout=2500)) + def on_tr_results_expanded(self, index: QtCore.QModelIndex) -> None: """Handle the event when a result item group is expanded. |
