from PySide6.QtCore import Qt, QPoint, Signal, QThread from network.direct_download.handle import DirectDownloadHandle from utils.logging.logs import consoleLog, remove_download_log from utils.network.download import download_selected from utils.data.tracker import get_magnet_link from utils.general.wrappers import run_thread from PySide6.QtWidgets import QMessageBox from utils.data.state import state from send2trash import send2trash from PySide6 import QtWidgets import threading import webbrowser import subprocess import platform import os class MagnetWorker(QThread): finished = Signal(str) def __init__(self, url, parent=None): super().__init__(parent) self.url = url def run(self): magnet = get_magnet_link(self.url) if magnet: self.finished.emit(magnet) class ContextMenu_Downloads: def __init__(self, main_window): self.main_window = main_window self.context_menu = QtWidgets.QMenu(main_window) self.context_menu.addAction("Open Containing Folder", self.openFolderAction) self.context_menu.addAction("Copy Download Link", self.copyMagnetURIAction) self.context_menu.addAction("Remove from list", self.cancelDownloadAction) self.context_menu.addAction("Delete File", self.deleteFileAction) main_window.downloadList.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) main_window.downloadList.customContextMenuRequested.connect(self._show_context_menu) @property def downloadList(self): return self.main_window.downloadList def _show_context_menu(self, pos: QPoint): index = self.downloadList.indexAt(pos) if index.isValid(): self._context_menu_row = index.row() self.context_menu.exec(self.downloadList.viewport().mapToGlobal(pos)) def openFolderAction(self): if not hasattr(self, '_context_menu_row'): return row = self._context_menu_row with state.downloads_lock: if row < 0 or row >= len(state.active_downloads): return keys = list(state.active_downloads.keys()) magnet_link = keys[row] magnetdl = state.active_downloads[magnet_link] download_path = magnetdl.save_path() if download_path and os.path.exists(download_path): if platform.system() == "Windows": os.startfile(os.path.normpath(download_path)) elif platform.system() == "Linux": subprocess.Popen(["xdg-open", download_path]) elif platform.system() == "Darwin": subprocess.Popen(["open", download_path]) def copyMagnetURIAction(self): if not hasattr(self, '_context_menu_row'): return row = self._context_menu_row with state.downloads_lock: if row < 0 or row >= len(state.active_downloads): return keys = list(state.active_downloads.keys()) magnet_link = keys[row] clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(magnet_link) def cancelDownloadAction(self): selected_rows = sorted(set(index.row() for index in self.downloadList.selectedIndexes()), reverse=True) if not selected_rows: return names = [] items_to_remove = [] with state.downloads_lock: keys = list(state.active_downloads.keys()) for row in selected_rows: if 0 <= row < len(keys): magnet_link = keys[row] magnetdl = state.active_downloads[magnet_link] try: name = magnetdl.status().name except RuntimeError: name = "Unknown" names.append(name) items_to_remove.append((magnet_link, magnetdl, name)) confirm = QMessageBox.question( self.main_window, "Cancel Downloads", f"Are you sure you want to cancel {len(names)} download(s)?\n\n" + "\n".join(names), QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if confirm != QMessageBox.StandardButton.Yes: return for magnet_link, magnetdl, name in items_to_remove: with state.downloads_lock: state.active_downloads.pop(magnet_link, None) remove_download_log(magnet_link) if isinstance(magnetdl, DirectDownloadHandle): try: magnetdl.stop() except Exception as e: consoleLog(f"Exception while stopping direct download: {e}") elif state.dl_session: try: state.dl_session.remove_torrent(magnetdl) except Exception as e: consoleLog(f"Exception while removing download from LibTorrent: {e}") consoleLog(f"Cancelled download: {name}", True) def deleteFileAction(self): selected_rows = sorted(set(index.row() for index in self.downloadList.selectedIndexes()), reverse=True) if not selected_rows: return items_to_remove = [] with state.downloads_lock: keys = list(state.active_downloads.keys()) for row in selected_rows: if 0 <= row < len(keys): magnet_link = keys[row] magnetdl = state.active_downloads[magnet_link] try: status = magnetdl.status() save_path = status.save_path torrent_name = status.name except RuntimeError: consoleLog("Error: torrent handle already invalid", True) continue download_path = os.path.join(save_path, torrent_name) items_to_remove.append((magnet_link, magnetdl, torrent_name, download_path)) if not items_to_remove: return names = [item[2] for item in items_to_remove] confirm = QMessageBox.question( self.main_window, "Delete Files", f"Are you sure you want to delete {len(names)} download(s)?\n\n" + "\n".join(names), QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No ) if confirm != QMessageBox.StandardButton.Yes: return for magnet_link, magnetdl, torrent_name, download_path in items_to_remove: with state.downloads_lock: state.active_downloads.pop(magnet_link, None) remove_download_log(magnet_link) if isinstance(magnetdl, DirectDownloadHandle): try: magnetdl.stop() except Exception as e: consoleLog(f"Exception while stopping direct download: {e}") elif state.dl_session: try: state.dl_session.remove_torrent(magnetdl) except Exception as e: consoleLog(f"Exception while removing download from LibTorrent: {e}") if download_path and os.path.exists(download_path): try: if os.path.isfile(download_path) or os.path.isdir(download_path): send2trash(download_path) consoleLog(f"Deleted files for: {torrent_name}", True) except Exception as e: consoleLog(f"Error deleting files: {e}", True) else: consoleLog(f"Removed entry (files not found): {torrent_name}", True) class ContextMenu_TrackerTable: def __init__(self, main_window): self.main_window = main_window self.context_menu = QtWidgets.QMenu(main_window) self.context_menu.addAction("Copy Download Link", self.copyMagnetURIAction) self.context_menu.addAction("Open in Browser", self.openInBrowserAction) self.context_menu.addAction("Download", self.downloadItemAction) state.trackertable.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) state.trackertable.customContextMenuRequested.connect(self._show_context_menu) @property def TrackerTable(self): return state.trackertable def _show_context_menu(self, pos: QPoint): index = self.TrackerTable.indexAt(pos) if index.isValid(): self._context_menu_row = index.row() self.context_menu.exec(self.TrackerTable.viewport().mapToGlobal(pos)) def copyMagnetURIAction(self): if not hasattr(self, '_context_menu_row'): return row = self._context_menu_row if not state.posts or row < 0 or row >= len(state.posts): return post = state.posts[row] url = post.get("url") if not url: return worker = MagnetWorker(url, parent=self.main_window) worker.finished.connect(self._on_magnet_fetched) worker.finished.connect(worker.deleteLater) worker.start() def _on_magnet_fetched(self, magnet): clipboard = QtWidgets.QApplication.clipboard() clipboard.setText(magnet) consoleLog("Magnet URI copied to clipboard!", True) def openInBrowserAction(self): if not hasattr(self, '_context_menu_row'): return row = self._context_menu_row if not state.posts or row < 0 or row >= len(state.posts): return post = state.posts[row] url = post.get("url") if url: try: webbrowser.open(url) except Exception as e: consoleLog(f"Failed to open URL: {e}", True) def downloadItemAction(self): if not hasattr(self, '_context_menu_row'): return row = self._context_menu_row if not state.posts or row < 0 or row >= len(state.posts): return run_thread(threading.Thread(target=download_selected, args=(state.trackertable.selectedItems(),)))