From 9f9183469d611631e14b6add291abbc9a41feae8 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Fri, 8 Aug 2025 13:00:12 -0700 Subject: [PATCH] wishlist modal somewhat ready --- .claude/settings.local.json | 4 +- .../matching_engine.cpython-312.pyc | Bin 27777 -> 29486 bytes .../__pycache__/dashboard.cpython-310.pyc | Bin 52479 -> 106085 bytes .../__pycache__/dashboard.cpython-312.pyc | Bin 150848 -> 185257 bytes ui/pages/dashboard.py | 1638 ++++++++++------- 5 files changed, 1026 insertions(+), 616 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index d76aadf0..005ca5a4 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -5,7 +5,9 @@ "Bash(rm:*)", "Bash(rg:*)", "Bash(grep:*)", - "WebFetch(domain:python-plexapi.readthedocs.io)" + "WebFetch(domain:python-plexapi.readthedocs.io)", + "Bash(git restore:*)", + "Bash(python3:*)" ], "deny": [] } diff --git a/core/__pycache__/matching_engine.cpython-312.pyc b/core/__pycache__/matching_engine.cpython-312.pyc index 8cd5e1a9d1605cd89fc9e1399e95bdb459804794..c18910f785bb95aed1d57eeb9b2cbc19765fe9e3 100644 GIT binary patch delta 4368 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 DVnv>M delta 2805 zcmaJ?4NP0t6@J%#e*771Fc<^j$NYpm$S+B@5@<{aw9tTIm?SKuG7rCpl)NR!s=Cz{o4Q`T+W&bbCg zrD;#{_wM=5x#yjqdyn7v9XY*3a(L&Sj2BY7TH&szo!aR`?k;sT_kBhg|#K(bnKvkRexy_CC;3?>$HyNxC7tF)e@1c%2* zy<AU!a!nI=>qG#Iu!ul(Ia?7!bOcN1h};_DlH;nPi^2-%5v&cC+UCGHDW*?QE#tX?q%$ zdR8hsUrxCMR*$^Vg#tDI7Lg4xd|}Y0)5Q?R?}uO#vV77lUvNu{MAk`Xmm*#8`DB+ zD(@lOfK}4d{8|)BPU&T&b|aia;LZLzu3kVmi*TOZYTjb!CB1+$HcuD%?ibm=nyaK& zkiEz@K6yW57M77R0G7Mg!NxY5n7p@_>`qMXtup1^170?+EAe~m`u;=Yd_rn_XpHkl z9s!d{!_i^7R&ZT})99nmf?h?4xp(5u#FA_>S-s&m0%z`K^wR<9Eef{PUbX7a?ZNYm)NTv z4mbw4I-atIK}nAz@FI;dYv&A^NqoQaM@DjkdAb8+k=^NT$irA=kv>4ne1zMKxE=Q2 z!&1+Q_`(zp1=S+s!Dxgov2E@m@{o19!{h^Y)BS46$6)5eBe?qD{Kcd2$j-)l;#L)= zqTis}RPmBQ&A*h+ti3x(0c-7bmDq7$jTn3e0v?Fq5k%oagD6Z8xsYAzy-vCkU0<0Z znjzQl44@a;1yRbVkVGGYZ7Qj3?3n7oRUXxqqvWLhANw)9v_cHq4ob&M=)`WhUFZg@j^&B zauJwQLeeahlrI#!=8IkLW`FSf-QvM%OHvY?H47C_%vU@yTe8sDJ>S@Ux57Q2UOH|5 zrXQ4M$IE+ul~wamWz7}eRoCp{`TDlImF;up^5xo{(+7TSu3oI$f$v&yZmqh~HD}pA zm%crjAyhSh(PC{8*Mij+ZnbQwT(wE;IQo)N;;B4lzwojxYt^D6=7)C{&O0}_1MAeG z{}ZwX`jCBNC_-8pQ4Wwlvu?$k^*vBb8X;>MV?S1g<7w!F-xI!>ySa^SK1Dc(fb&Vu z&_d%d-tn4ZW9000*A(v-eT0C0NO|Lqve5Zokb8*mR{)&^bZO=OWy1t}dHA`sZCLvx zVehydiAvu&Q$C+kd%)VC8bM1g+RAPptB&)5HHE$$2>l3s2>(F9Qn=|EKwX^B6iT-v z8$-AcAe(6iu7?o#{}!KKMwrrkUQ=FygP_&TRD*vLI87=hT#b}55Q-Y;G*be_QXb|N zNSqEd5%{ycbNr5BD~9amhnR;Z6Iu&KtB150H*7*Ui68$OyE^J87nwB_k!}DXSF^Jr zxws8=Jj5zo@eqq}(*Tryc6_iL1WmsXQq>}Pbq P(wRx*PX$C;3Ss;QTRO-n diff --git a/ui/pages/__pycache__/dashboard.cpython-310.pyc b/ui/pages/__pycache__/dashboard.cpython-310.pyc index f9ee8a6333f222563314924fa7cb9a90fecee573..d44486dbbbe5be3e0fcf380f97baa713f1cf6546 100644 GIT binary patch literal 106085 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 ZFY9f$vx%kK{nPx5{kx@M|Muzy{|_loj|~6- diff --git a/ui/pages/__pycache__/dashboard.cpython-312.pyc b/ui/pages/__pycache__/dashboard.cpython-312.pyc index 4c330a213cc399a5c474eba3610070a7255b4f67..248a1538a946fa749542c15f44735b67cfd2b8db 100644 GIT binary patch delta 67379 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 G(*FZ>5KyoH delta 39785 zcmc${33yw@wK%RBEtc$STejtWllOhkW@q!B-C3N4I1op+^!+DErCa0Nna@@z$;C+-#K%y7CWZ@_I>aB{(_9} zoqf)nZO)vT`{WZ5=f7fzdMz?CTm`@DAG&W(oBm`}67|uOg=21QZ+>4vM?qg@n=$+rUpksj&x3FU�kBN`f59Bd0b>~UEgAN$722x)w`r`X~)vOWgW}- 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 0.8: + result.exists_in_plex = True + result.plex_match = plex_match + result.confidence = confidence + except Exception as e: + result.error_message = f"DB check failed: {str(e)}" + + results.append(result) + self.signals.track_analyzed.emit(i + 1, result) + + if not self._cancelled: + self.signals.analysis_completed.emit(results) + except Exception as e: + if not self._cancelled: + self.signals.analysis_failed.emit(str(e)) + + def _check_track_in_db(self, spotify_track, db): + """ + Checks if a Spotify track exists in the database. + This logic now relies solely on the central MusicMatchingEngine for consistency. + """ + try: + original_title = spotify_track.name + + # The matching engine's clean_title now handles "(Original Mix)" and other noise. + # We create variations to be safe. + title_variations = [original_title] + cleaned_title = self.matching_engine.clean_title(original_title) + if cleaned_title.lower() != original_title.lower(): + title_variations.append(cleaned_title) + + unique_title_variations = list(dict.fromkeys(title_variations)) + + artists_to_search = spotify_track.artists if spotify_track.artists else [""] + for artist_name in artists_to_search: + if self._cancelled: return None, 0.0 + + for query_title in unique_title_variations: + if self._cancelled: return None, 0.0 + + db_track, confidence = db.check_track_exists(query_title, artist_name, confidence_threshold=0.7) + + if db_track and confidence >= 0.7: + class MockPlexTrack: + def __init__(self, db_track): + self.id = str(db_track.id) + self.title = db_track.title + self.artist_name = db_track.artist_name + self.album_title = db_track.album_title + self.track_number = db_track.track_number + self.duration = db_track.duration + self.file_path = db_track.file_path + + mock_track = MockPlexTrack(db_track) + return mock_track, confidence + + return None, 0.0 + + except Exception as e: + import traceback + print(f"Error checking track in database: {e}") + traceback.print_exc() + return None, 0.0 + +class SyncStatusProcessingWorkerSignals(QObject): + completed = pyqtSignal(list) + error = pyqtSignal(str) + +class SyncStatusProcessingWorker(QRunnable): + """Background worker for processing download status updates.""" + def __init__(self, soulseek_client, download_items_data): + super().__init__() + self.signals = SyncStatusProcessingWorkerSignals() + self.soulseek_client = soulseek_client + self.download_items_data = download_items_data + + def run(self): + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + transfers_data = loop.run_until_complete( + self.soulseek_client._make_request('GET', 'transfers/downloads') + ) + loop.close() + + results = [] + if not transfers_data: + transfers_data = [] + + all_transfers = [] + for user_data in transfers_data: + if 'files' in user_data and isinstance(user_data['files'], list): + all_transfers.extend(user_data['files']) + if 'directories' in user_data and isinstance(user_data['directories'], list): + for directory in user_data['directories']: + if 'files' in directory and isinstance(directory['files'], list): + all_transfers.extend(directory['files']) + + transfers_by_id = {t['id']: t for t in all_transfers} + + for item_data in self.download_items_data: + matching_transfer = None + if item_data.get('download_id'): + matching_transfer = transfers_by_id.get(item_data['download_id']) + + if not matching_transfer: + expected_basename = os.path.basename(item_data['file_path']).lower() + for t in all_transfers: + api_basename = os.path.basename(t.get('filename', '')).lower() + if api_basename == expected_basename: + matching_transfer = t + break + + if matching_transfer: + state = matching_transfer.get('state', 'Unknown') + progress = matching_transfer.get('percentComplete', 0) + + if 'Cancelled' in state or 'Canceled' in state: new_status = 'cancelled' + elif 'Failed' in state or 'Errored' in state: new_status = 'failed' + elif 'Completed' in state or 'Succeeded' in state: new_status = 'completed' + elif 'InProgress' in state: new_status = 'downloading' + else: new_status = 'queued' + + payload = { + 'widget_id': item_data['widget_id'], + 'status': new_status, + 'progress': int(progress), + 'transfer_id': matching_transfer.get('id'), + 'username': matching_transfer.get('username') + } + results.append(payload) + else: + item_data['api_missing_count'] = item_data.get('api_missing_count', 0) + 1 + if item_data['api_missing_count'] >= 3: + payload = {'widget_id': item_data['widget_id'], 'status': 'failed'} + results.append(payload) + + self.signals.completed.emit(results) + except Exception as e: + self.signals.error.emit(str(e)) + + + + + + + + + + + + + +# dashboard.py - Replace the old modal class with this new one + class DownloadMissingWishlistTracksModal(QDialog): - """Modal for downloading tracks from the wishlist with live progress tracking""" + """ + Enhanced modal for downloading missing wishlist tracks with live progress tracking. + Functionality is extended from the modals in sync.py and artists.py. + """ process_finished = pyqtSignal() - + def __init__(self, wishlist_service, parent_dashboard, downloads_page, spotify_client, plex_client, soulseek_client): super().__init__(parent_dashboard) self.wishlist_service = wishlist_service @@ -40,587 +284,746 @@ class DownloadMissingWishlistTracksModal(QDialog): self.spotify_client = spotify_client self.plex_client = plex_client self.soulseek_client = soulseek_client - - # Import matching engine self.matching_engine = MusicMatchingEngine() # State tracking self.wishlist_tracks = [] self.total_tracks = 0 + self.matched_tracks_count = 0 + self.tracks_to_download_count = 0 + self.downloaded_tracks_count = 0 + self.analysis_complete = False self.download_in_progress = False self.cancel_requested = False - self.active_parallel_downloads = 0 - self.download_queue_index = 0 - self.completed_downloads = 0 - self.successful_downloads = 0 - self.failed_downloads = 0 - - # Track active downloads and failed tracks - self.active_downloads = [] self.permanently_failed_tracks = [] - - # Parallel search tracking (adapted from sync.py) - self.parallel_search_tracking = {} - + self.analysis_results = [] + self.missing_tracks = [] + self.active_workers = [] + self.fallback_pools = [] + self.active_downloads = [] + + # Status Polling + self.download_status_pool = QThreadPool() + self.download_status_pool.setMaxThreadCount(1) + self._is_status_update_running = False + self.download_status_timer = QTimer(self) + self.download_status_timer.timeout.connect(self.poll_all_download_statuses) + self.download_status_timer.start(2000) + self.setup_ui() - self.load_wishlist_tracks() - - # Timer to periodically check for automatic processing status changes - self.status_check_timer = QTimer() - self.status_check_timer.timeout.connect(self.check_auto_processing_status) - self.status_check_timer.start(2000) # Check every 2 seconds - + self.load_and_populate_tracks() + + def start_search(self): + """ + Public method to start the search process. Can be called externally. + This will trigger the same action as clicking the 'Begin Search' button. + """ + if not self.download_in_progress: + self.on_begin_search_clicked() + + def load_and_populate_tracks(self): + """Fetches tracks from the wishlist service and prepares them for the modal.""" + + # A simple dataclass to mimic the structure of a Spotify track object + # that the rest of the modal logic expects. + @dataclass + class MockSpotifyTrack: + id: str + name: str + artists: List[str] + album: str + duration_ms: int = 0 + + try: + wishlist_data = self.wishlist_service.get_wishlist_tracks_for_download() + self.wishlist_tracks = [] + for track_data in wishlist_data: + # Convert artist dicts like [{'name': 'Artist'}] to a simple list ['Artist'] + artist_list = [artist['name'] for artist in track_data.get('artists', []) if 'name' in artist] + + mock_track = MockSpotifyTrack( + id=track_data.get('spotify_track_id', ''), + name=track_data.get('name', 'Unknown Track'), + artists=artist_list, + album=track_data.get('album_name', 'Unknown Album') + ) + self.wishlist_tracks.append(mock_track) + + self.total_tracks = len(self.wishlist_tracks) + self.total_count_label.setText(str(self.total_tracks)) + self.populate_track_table() + + except Exception as e: + logger.error(f"Failed to load wishlist tracks: {e}") + QMessageBox.critical(self, "Error", f"Could not load wishlist tracks: {e}") + def setup_ui(self): - """Setup the modal UI with enhanced styling""" - self.setWindowTitle("Wishlist Downloads") - self.setMinimumSize(900, 650) + self.setWindowTitle("Download Wishlist Tracks") + self.resize(1200, 900) + self.setWindowFlags(Qt.WindowType.Window) + self.setStyleSheet(""" - DownloadMissingWishlistTracksModal { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 #1a1a1a, - stop: 1 #0f0f0f); - border-radius: 12px; + QDialog { background-color: #1e1e1e; color: #ffffff; } + QLabel { color: #ffffff; } + QPushButton { + background-color: #1db954; color: #000000; border: none; + border-radius: 6px; font-size: 13px; font-weight: bold; + padding: 10px 20px; min-width: 100px; } + QPushButton:hover { background-color: #1ed760; } + QPushButton:disabled { background-color: #404040; color: #888888; } """) - layout = QVBoxLayout(self) - layout.setContentsMargins(24, 24, 24, 24) - layout.setSpacing(20) + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(25, 25, 25, 25) + main_layout.setSpacing(15) - # Header section with icon - header_layout = QHBoxLayout() - header_layout.setSpacing(12) + top_section = self.create_compact_top_section() + main_layout.addWidget(top_section) - # Wishlist icon - icon_label = QLabel("🎵") - icon_label.setFont(QFont("Arial", 20)) - icon_label.setFixedSize(32, 32) - icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter) - icon_label.setStyleSheet(""" - QLabel { - background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, - stop: 0 rgba(29, 185, 84, 0.15), - stop: 1 rgba(30, 215, 96, 0.1)); - border: 1px solid rgba(29, 185, 84, 0.3); - border-radius: 16px; - } - """) + progress_section = self.create_progress_section() + main_layout.addWidget(progress_section) - # Header text - header_label = QLabel("Wishlist Downloads") - header_label.setFont(QFont("SF Pro Display", 18, QFont.Weight.Bold)) - header_label.setStyleSheet(""" - color: #ffffff; - font-weight: 600; - letter-spacing: 0.3px; - """) + table_section = self.create_track_table() + main_layout.addWidget(table_section, stretch=1) - header_layout.addWidget(icon_label) - header_layout.addWidget(header_label) + button_section = self.create_buttons() + main_layout.addWidget(button_section) + + def create_compact_top_section(self): + top_frame = QFrame() + top_frame.setStyleSheet("background-color: #2d2d2d; border: 1px solid #444444; border-radius: 8px; padding: 15px;") + layout = QVBoxLayout(top_frame) + header_layout = QHBoxLayout() + title_section = QVBoxLayout() + + title = QLabel("Download Wishlist Tracks") + title.setFont(QFont("Arial", 16, QFont.Weight.Bold)) + title.setStyleSheet("color: #1db954;") + + subtitle = QLabel("Processing tracks from your wishlist") + subtitle.setFont(QFont("Arial", 11)) + subtitle.setStyleSheet("color: #aaaaaa;") + + title_section.addWidget(title) + title_section.addWidget(subtitle) + + dashboard_layout = QHBoxLayout() + self.total_card = self.create_compact_counter_card("📀 Total", "0", "#1db954") + self.matched_card = self.create_compact_counter_card("✅ Found", "0", "#4CAF50") + self.download_card = self.create_compact_counter_card("⬇️ Missing", "0", "#ff6b6b") + self.downloaded_card = self.create_compact_counter_card("✅ Downloaded", "0", "#4CAF50") + dashboard_layout.addWidget(self.total_card) + dashboard_layout.addWidget(self.matched_card) + dashboard_layout.addWidget(self.download_card) + dashboard_layout.addWidget(self.downloaded_card) + + header_layout.addLayout(title_section) header_layout.addStretch() + header_layout.addLayout(dashboard_layout) + layout.addLayout(header_layout) + return top_frame + + def create_compact_counter_card(self, title, count, color): + card = QFrame() + card.setStyleSheet(f"background-color: #3a3a3a; border: 2px solid {color}; border-radius: 6px; padding: 8px 12px; min-width: 80px;") + layout = QVBoxLayout(card) + count_label = QLabel(count) + count_label.setFont(QFont("Arial", 16, QFont.Weight.Bold)) + count_label.setStyleSheet(f"color: {color}; background: transparent;") + count_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + title_label = QLabel(title) + title_label.setFont(QFont("Arial", 9)) + title_label.setStyleSheet("color: #cccccc; background: transparent;") + title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + layout.addWidget(count_label) + layout.addWidget(title_label) + if "Total" in title: self.total_count_label = count_label + elif "Found" in title: self.matched_count_label = count_label + elif "Missing" in title: self.download_count_label = count_label + elif "Downloaded" in title: self.downloaded_count_label = count_label + return card + + def create_progress_section(self): + progress_frame = QFrame() + progress_frame.setStyleSheet("background-color: #2d2d2d; border: 1px solid #444444; border-radius: 8px; padding: 12px;") + layout = QVBoxLayout(progress_frame) + analysis_container = QVBoxLayout() + analysis_label = QLabel("🔍 Library Analysis") + analysis_label.setFont(QFont("Arial", 11, QFont.Weight.Bold)) + self.analysis_progress = QProgressBar() + self.analysis_progress.setFixedHeight(20) + self.analysis_progress.setStyleSheet("QProgressBar { border: 1px solid #555; border-radius: 10px; text-align: center; background-color: #444; color: #fff; font-size: 11px; } QProgressBar::chunk { background-color: #1db954; border-radius: 9px; }") + self.analysis_progress.setVisible(False) + analysis_container.addWidget(analysis_label) + analysis_container.addWidget(self.analysis_progress) + download_container = QVBoxLayout() + download_label = QLabel("⬇️ Download Progress") + download_label.setFont(QFont("Arial", 11, QFont.Weight.Bold)) + self.download_progress = QProgressBar() + self.download_progress.setFixedHeight(20) + self.download_progress.setStyleSheet("QProgressBar { border: 1px solid #555; border-radius: 10px; text-align: center; background-color: #444; color: #fff; font-size: 11px; } QProgressBar::chunk { background-color: #ff6b6b; border-radius: 9px; }") + self.download_progress.setVisible(False) + download_container.addWidget(download_label) + download_container.addWidget(self.download_progress) + layout.addLayout(analysis_container) + layout.addLayout(download_container) + return progress_frame + + def create_track_table(self): + """Create enhanced track table without the Duration column.""" + table_frame = QFrame() + table_frame.setStyleSheet("background-color: #2d2d2d; border: 1px solid #444444; border-radius: 8px; padding: 0px;") + layout = QVBoxLayout(table_frame) + layout.setContentsMargins(15, 15, 15, 15) - # Info label with better styling - self.info_label = QLabel("Loading wishlist tracks...") - self.info_label.setFont(QFont("SF Pro Text", 12)) - self.info_label.setStyleSheet(""" - color: rgba(255, 255, 255, 0.7); - padding: 8px 0px; - font-weight: 400; - """) - - # Enhanced track table self.track_table = QTableWidget() + # Change column count from 5 to 4 self.track_table.setColumnCount(4) - self.track_table.setHorizontalHeaderLabels(["Track", "Artist", "Retry Count", "Status"]) - - # Set more balanced column distribution - header = self.track_table.horizontalHeader() - header.setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch) # Track - flexible - header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch) # Artist - flexible - header.setSectionResizeMode(2, QHeaderView.ResizeMode.Stretch) # Retry Count - flexible - header.setSectionResizeMode(3, QHeaderView.ResizeMode.Stretch) # Status - flexible + # Remove "Duration" from the labels + self.track_table.setHorizontalHeaderLabels(["Track", "Artist", "Matched", "Status"]) - # Set minimum widths to ensure readability - self.track_table.setMinimumSize(800, 400) - header.setMinimumSectionSize(80) # Minimum width for any column - header.setDefaultSectionSize(150) # Default width for columns - - # Enhanced table styling + # Adjust resize modes for new column indices + self.track_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch) + self.track_table.horizontalHeader().setSectionResizeMode(2, QHeaderView.ResizeMode.Interactive) # "Matched" is now column 2 + self.track_table.setColumnWidth(2, 140) # Set width for "Matched" column + + self.track_table.setStyleSheet("QTableWidget { background-color: #3a3a3a; alternate-background-color: #424242; selection-background-color: #1db954; gridline-color: #555; color: #fff; border: 1px solid #555; font-size: 12px; } QHeaderView::section { background-color: #1db954; color: #000; font-weight: bold; font-size: 13px; padding: 12px 8px; border: none; } QTableWidget::item { padding: 12px 8px; border-bottom: 1px solid #4a4a4a; }") self.track_table.setAlternatingRowColors(True) self.track_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) - self.track_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) self.track_table.verticalHeader().setVisible(False) - self.track_table.setShowGrid(False) - - self.track_table.setStyleSheet(""" - QTableWidget { - background: transparent; - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 12px; - gridline-color: transparent; - outline: none; - font-family: "SF Pro Text"; - font-size: 11px; - } - QTableWidget::item { - padding: 12px 8px; - border: none; - color: rgba(255, 255, 255, 0.9); - background: transparent; - } - QTableWidget::item:alternate { - background: rgba(255, 255, 255, 0.02); - } - QTableWidget::item:hover { - background: rgba(255, 255, 255, 0.05); - } - QTableWidget::item:selected { - background: rgba(29, 185, 84, 0.1); - border-left: 2px solid #1db954; - } - QHeaderView::section { - background: qlineargradient(x1: 0, y1: 0, x2: 0, y2: 1, - stop: 0 rgba(255, 255, 255, 0.08), - stop: 1 rgba(255, 255, 255, 0.04)); - color: rgba(255, 255, 255, 0.9); - padding: 12px 8px; - border: none; - font-weight: 600; - font-size: 10px; - text-transform: uppercase; - letter-spacing: 0.5px; - } - QHeaderView::section:first { - border-top-left-radius: 12px; - } - QHeaderView::section:last { - border-top-right-radius: 12px; - } - QScrollBar:vertical { - background: rgba(255, 255, 255, 0.05); - width: 8px; - border-radius: 4px; - margin: 0; - } - QScrollBar::handle:vertical { - background: rgba(255, 255, 255, 0.2); - border-radius: 4px; - min-height: 20px; - } - QScrollBar::handle:vertical:hover { - background: rgba(255, 255, 255, 0.3); - } - """) - # Enhanced progress bar - self.download_progress = QProgressBar() - self.download_progress.setFixedHeight(8) - self.download_progress.setVisible(False) - self.download_progress.setTextVisible(False) - self.download_progress.setStyleSheet(""" - QProgressBar { - background: rgba(255, 255, 255, 0.1); - border: none; - border-radius: 4px; - } - QProgressBar::chunk { - background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, - stop: 0 #1db954, - stop: 1 #1ed760); - border-radius: 4px; - } - """) - - # Enhanced buttons - button_layout = QHBoxLayout() - button_layout.setSpacing(12) - - self.begin_download_btn = QPushButton("🚀 Begin Downloads") - self.begin_download_btn.setFixedHeight(44) - self.begin_download_btn.setMinimumWidth(160) - self.begin_download_btn.clicked.connect(self.start_downloads) - self.begin_download_btn.setStyleSheet(""" - QPushButton { - background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, - stop: 0 #1db954, - stop: 1 #1ed760); - border: 1px solid rgba(29, 185, 84, 0.3); - border-radius: 22px; - color: #000000; - font-family: "SF Pro Text"; - font-size: 13px; - font-weight: 600; - padding: 0px 24px; - letter-spacing: 0.2px; - } - QPushButton:hover { - background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, - stop: 0 #1ed760, - stop: 1 #22e968); - border: 1px solid rgba(30, 215, 96, 0.4); - transform: translateY(-1px); - } - QPushButton:pressed { - background: #1db954; - transform: translateY(0px); - } - QPushButton:disabled { - background: rgba(255, 255, 255, 0.05); - color: rgba(255, 255, 255, 0.3); - border: 1px solid rgba(255, 255, 255, 0.1); - } - """) - - # Clear wishlist button - self.clear_wishlist_btn = QPushButton("🗑️ Clear All") - self.clear_wishlist_btn.setFixedHeight(44) - self.clear_wishlist_btn.setMinimumWidth(120) - self.clear_wishlist_btn.clicked.connect(self.clear_wishlist) - self.clear_wishlist_btn.setStyleSheet(""" - QPushButton { - background: rgba(255, 107, 107, 0.1); - border: 1px solid rgba(255, 107, 107, 0.2); - border-radius: 22px; - color: #ff6b6b; - font-family: "SF Pro Text"; - font-size: 12px; - font-weight: 500; - padding: 0px 20px; - } - QPushButton:hover { - background: rgba(255, 107, 107, 0.15); - border: 1px solid rgba(255, 107, 107, 0.3); - } - QPushButton:pressed { - background: rgba(255, 107, 107, 0.05); - } - """) - - self.cancel_btn = QPushButton("Close") - self.cancel_btn.setFixedHeight(44) - self.cancel_btn.setMinimumWidth(100) - self.cancel_btn.clicked.connect(self.on_cancel_clicked) - self.cancel_btn.setStyleSheet(""" - QPushButton { - background: rgba(255, 255, 255, 0.05); - border: 1px solid rgba(255, 255, 255, 0.1); - border-radius: 22px; - color: rgba(255, 255, 255, 0.8); - font-family: "SF Pro Text"; - font-size: 12px; - font-weight: 500; - padding: 0px 20px; - } - QPushButton:hover { - background: rgba(255, 255, 255, 0.08); - border: 1px solid rgba(255, 255, 255, 0.15); - } - QPushButton:pressed { - background: rgba(255, 255, 255, 0.02); - } - """) - - # Add buttons to layout - button_layout.addWidget(self.clear_wishlist_btn) - button_layout.addStretch() - button_layout.addWidget(self.cancel_btn) - button_layout.addWidget(self.begin_download_btn) - - # Add all components to main layout - layout.addLayout(header_layout) - layout.addWidget(self.info_label) layout.addWidget(self.track_table) - layout.addWidget(self.download_progress) - layout.addLayout(button_layout) - - def load_wishlist_tracks(self): - """Load tracks from wishlist""" - try: - self.wishlist_tracks = self.wishlist_service.get_wishlist_tracks_for_download() - self.total_tracks = len(self.wishlist_tracks) - - if self.total_tracks == 0: - self.info_label.setText("No tracks in wishlist") - self.begin_download_btn.setEnabled(False) - return - - # Check if automatic processing is running and update UI accordingly - if hasattr(self.parent_dashboard, 'auto_processing_wishlist') and self.parent_dashboard.auto_processing_wishlist: - self.info_label.setText(f"Found {self.total_tracks} tracks in wishlist (⚡ Automatic processing in progress...)") - self.begin_download_btn.setText("⏳ Auto Processing...") - self.begin_download_btn.setEnabled(False) + return table_frame + + def populate_track_table(self): + """Populate track table with wishlist tracks, omitting the duration.""" + self.track_table.setRowCount(len(self.wishlist_tracks)) + for i, track in enumerate(self.wishlist_tracks): + self.track_table.setItem(i, 0, QTableWidgetItem(track.name)) + artist_name = track.artists[0] if track.artists else "Unknown" + self.track_table.setItem(i, 1, QTableWidgetItem(artist_name)) + + # --- DURATION LOGIC REMOVED --- + + # "Matched" is now column 2 + matched_item = QTableWidgetItem("⏳ Pending") + matched_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.track_table.setItem(i, 2, matched_item) + + # "Status" is now column 3 + status_item = QTableWidgetItem("—") + status_item.setTextAlignment(Qt.AlignmentFlag.AlignCenter) + self.track_table.setItem(i, 3, status_item) + + # Loop over 4 columns instead of 5 + for col in range(4): + item = self.track_table.item(i, col) + if item: + item.setFlags(item.flags() & ~Qt.ItemFlag.ItemIsEditable) + + def format_duration(self, duration_ms): + if not duration_ms: return "0:00" + seconds = duration_ms // 1000 + return f"{seconds // 60}:{seconds % 60:02d}" + + def create_buttons(self): + button_frame = QFrame(styleSheet="background-color: transparent; padding: 10px;") + layout = QHBoxLayout(button_frame) + self.correct_failed_btn = QPushButton("🔧 Correct Failed Matches") + self.correct_failed_btn.setFixedWidth(220) + self.correct_failed_btn.setStyleSheet("QPushButton { background-color: #ffc107; color: #000; border-radius: 20px; font-weight: bold; }") + self.correct_failed_btn.clicked.connect(self.on_correct_failed_matches_clicked) + self.correct_failed_btn.hide() + self.begin_search_btn = QPushButton("Begin Search") + self.begin_search_btn.setFixedSize(160, 40) + self.begin_search_btn.setStyleSheet("QPushButton { background-color: #1db954; color: #000; border: none; border-radius: 20px; font-size: 14px; font-weight: bold; }") + self.begin_search_btn.clicked.connect(self.on_begin_search_clicked) + self.cancel_btn = QPushButton("Cancel") + self.cancel_btn.setFixedSize(110, 40) + self.cancel_btn.setStyleSheet("QPushButton { background-color: #d32f2f; color: #fff; border-radius: 20px;}") + self.cancel_btn.clicked.connect(self.on_cancel_clicked) + self.cancel_btn.hide() + self.close_btn = QPushButton("Close") + self.close_btn.setFixedSize(110, 40) + self.close_btn.setStyleSheet("QPushButton { background-color: #616161; color: #fff; border-radius: 20px;}") + self.close_btn.clicked.connect(self.on_close_clicked) + layout.addStretch() + layout.addWidget(self.begin_search_btn) + layout.addWidget(self.cancel_btn) + layout.addWidget(self.correct_failed_btn) + layout.addWidget(self.close_btn) + return button_frame + + # --- All the logic methods from sync.py's modal --- + # (on_begin_search_clicked, start_plex_analysis, on_analysis_completed, etc.) + # are copied here without change, except for the modifications noted below. + + def on_begin_search_clicked(self): + self.parent_dashboard.auto_processing_wishlist = True + self.begin_search_btn.hide() + self.cancel_btn.show() + self.analysis_progress.setVisible(True) + self.analysis_progress.setMaximum(self.total_tracks) + self.analysis_progress.setValue(0) + self.download_in_progress = True + self.start_plex_analysis() + + def start_plex_analysis(self): + # This now uses the mock track objects from the wishlist + worker = PlaylistTrackAnalysisWorker(self.wishlist_tracks, self.plex_client) + worker.signals.analysis_started.connect(self.on_analysis_started) + worker.signals.track_analyzed.connect(self.on_track_analyzed) + worker.signals.analysis_completed.connect(self.on_analysis_completed) + worker.signals.analysis_failed.connect(self.on_analysis_failed) + self.active_workers.append(worker) + QThreadPool.globalInstance().start(worker) + + def find_track_index_in_playlist(self, spotify_track): + """Finds the table row index for a given track from the wishlist.""" + for i, track in enumerate(self.wishlist_tracks): + if track.id == spotify_track.id: + return i + return -1 # Return -1 if not found + + # ... Paste the rest of the methods from DownloadMissingTracksModal in sync.py here ... + # (on_analysis_started, on_track_analyzed, on_analysis_completed, on_analysis_failed, + # start_download_progress, start_parallel_downloads, start_next_batch_of_downloads, + # search_and_download_track_parallel, start_track_search_with_queries_parallel, + # start_search_worker_parallel, on_search_query_completed_parallel, + # start_validated_download_parallel, start_matched_download_via_infrastructure_parallel, + # poll_all_download_statuses, _handle_processed_status_updates, + # cancel_download_before_retry, retry_parallel_download_with_fallback, + # on_parallel_track_completed, on_parallel_track_failed, + # update_failed_matches_button, on_correct_failed_matches_clicked, + # on_manual_match_resolved, on_all_downloads_complete, on_cancel_clicked, + # on_close_clicked, cancel_operations, closeEvent, ParallelSearchWorker, + # get_valid_candidates, create_spotify_based_search_result_from_validation, + # generate_smart_search_queries) + + # NOTE: I am pasting all the required methods below for completeness. + + def on_analysis_started(self, total_tracks): + logger.debug(f"Analysis started for {total_tracks} tracks") + + def on_track_analyzed(self, track_index, result): + self.analysis_progress.setValue(track_index) + if result.exists_in_plex: + matched_text = f"✅ Found ({result.confidence:.1f})" + self.matched_tracks_count += 1 + self.matched_count_label.setText(str(self.matched_tracks_count)) + + track_id_to_remove = result.spotify_track.id + + if self.wishlist_service.remove_track_from_wishlist(track_id_to_remove): + logger.info(f"Removed pre-existing track '{result.spotify_track.name}' from wishlist during analysis.") else: - self.info_label.setText(f"Found {self.total_tracks} tracks in wishlist ready for retry") - - # Populate table - self.track_table.setRowCount(self.total_tracks) - - for i, track_data in enumerate(self.wishlist_tracks): - # Track name - self.track_table.setItem(i, 0, QTableWidgetItem(track_data.get('name', 'Unknown Track'))) - - # Artist - artists = track_data.get('artists', []) - artist_name = artists[0].get('name', 'Unknown Artist') if artists else 'Unknown Artist' - self.track_table.setItem(i, 1, QTableWidgetItem(artist_name)) - - # Retry count - retry_count = track_data.get('retry_count', 0) - self.track_table.setItem(i, 2, QTableWidgetItem(str(retry_count))) - - # Status - self.track_table.setItem(i, 3, QTableWidgetItem("Pending")) - - except Exception as e: - logger.error(f"Error loading wishlist tracks: {e}") - self.info_label.setText(f"Error loading tracks: {str(e)}") - - def check_auto_processing_status(self): - """Periodically check if automatic processing status has changed""" - try: - is_auto_processing = hasattr(self.parent_dashboard, 'auto_processing_wishlist') and self.parent_dashboard.auto_processing_wishlist - current_button_text = self.begin_download_btn.text() - - if is_auto_processing and "Auto Processing" not in current_button_text: - # Auto processing started, update UI - self.info_label.setText(f"Found {self.total_tracks} tracks in wishlist (⚡ Automatic processing in progress...)") - self.begin_download_btn.setText("⏳ Auto Processing...") - self.begin_download_btn.setEnabled(False) - logger.debug("Modal UI updated: Automatic processing started") - - elif not is_auto_processing and "Auto Processing" in current_button_text: - # Auto processing finished, refresh the modal - self.load_wishlist_tracks() - self.begin_download_btn.setText("🚀 Begin Downloads") - self.begin_download_btn.setEnabled(self.total_tracks > 0) - logger.debug("Modal UI updated: Automatic processing completed") - - except Exception as e: - logger.error(f"Error checking auto processing status: {e}") - - def refresh_if_auto_processing_complete(self): - """Check if automatic processing completed and refresh the modal""" - try: - if not hasattr(self.parent_dashboard, 'auto_processing_wishlist') or not self.parent_dashboard.auto_processing_wishlist: - # Auto processing is not running, refresh the modal - self.load_wishlist_tracks() - self.begin_download_btn.setText("🚀 Begin Downloads") - self.begin_download_btn.setEnabled(self.total_tracks > 0) - except Exception as e: - logger.error(f"Error refreshing modal after auto processing: {e}") - - def start_downloads(self): - """Start downloading all wishlist tracks""" - try: - if self.total_tracks == 0: - return - - # Check if automatic processing is already running - if hasattr(self.parent_dashboard, 'auto_processing_wishlist') and self.parent_dashboard.auto_processing_wishlist: - QMessageBox.information(self, "Wishlist Processing", - "Automatic wishlist processing is currently running in the background. " - "Please wait for it to complete before starting manual processing.") - return - - self.download_in_progress = True - self.cancel_requested = False - self.begin_download_btn.setEnabled(False) - self.download_progress.setVisible(True) - self.download_progress.setMaximum(self.total_tracks) - self.download_progress.setValue(0) - - # Start parallel downloads (simplified approach) - self.active_parallel_downloads = 0 - self.download_queue_index = 0 - self.completed_downloads = 0 - self.successful_downloads = 0 - self.failed_downloads = 0 - - # Initialize tracking - self.active_downloads = [] - self.permanently_failed_tracks = [] - self.parallel_search_tracking = {} - - self.start_next_batch_of_downloads() - - except Exception as e: - logger.error(f"Error starting downloads: {e}") - QMessageBox.critical(self, "Error", f"Failed to start downloads: {str(e)}") - + logger.warning(f"Could not remove pre-existing track '{track_id_to_remove}' from wishlist.") + + else: + matched_text = "❌ Missing" + self.tracks_to_download_count += 1 + self.download_count_label.setText(str(self.tracks_to_download_count)) + self.track_table.setItem(track_index - 1, 2, QTableWidgetItem(matched_text)) + + + def on_analysis_completed(self, results): + self.analysis_complete = True + self.analysis_results = results + self.missing_tracks = [r for r in results if not r.exists_in_plex] + logger.info(f"Analysis complete: {len(self.missing_tracks)} to download") + if self.missing_tracks: + self.start_download_progress() + else: + self.download_in_progress = False + self.cancel_btn.hide() + self.process_finished.emit() + QMessageBox.information(self, "Analysis Complete", "All wishlist tracks already exist in your library!") + + def on_analysis_failed(self, error_message): + logger.error(f"Analysis failed: {error_message}") + QMessageBox.critical(self, "Analysis Failed", f"Failed to analyze tracks: {error_message}") + self.cancel_btn.hide() + self.begin_search_btn.show() + + def start_download_progress(self): + self.download_progress.setVisible(True) + self.download_progress.setMaximum(len(self.missing_tracks)) + self.download_progress.setValue(0) + self.start_parallel_downloads() + + def start_parallel_downloads(self): + self.active_parallel_downloads = 0 + self.download_queue_index = 0 + self.failed_downloads = 0 + self.completed_downloads = 0 + self.successful_downloads = 0 + self.start_next_batch_of_downloads() + def start_next_batch_of_downloads(self, max_concurrent=3): - """Start the next batch of downloads up to the concurrent limit""" - while (self.active_parallel_downloads < max_concurrent and - self.download_queue_index < len(self.wishlist_tracks)): - track_data = self.wishlist_tracks[self.download_queue_index] - track_index = self.download_queue_index - - # Start search and download for this track (status updates handled by worker) - self.search_and_download_track_simple(track_data, self.download_queue_index) - - self.active_parallel_downloads += 1 + while (self.active_parallel_downloads < max_concurrent and + self.download_queue_index < len(self.missing_tracks)): + track_result = self.missing_tracks[self.download_queue_index] + track = track_result.spotify_track + track_index = self.find_track_index_in_playlist(track) + if track_index != -1: + # FIX: Changed column index from 4 to 3 to target the "Status" column. + self.track_table.setItem(track_index, 3, QTableWidgetItem("🔍 Searching...")) + self.search_and_download_track_parallel(track, self.download_queue_index, track_index) + self.active_parallel_downloads += 1 self.download_queue_index += 1 - if (self.download_queue_index >= len(self.wishlist_tracks) and self.active_parallel_downloads == 0): + if (self.download_queue_index >= len(self.missing_tracks) and self.active_parallel_downloads == 0): self.on_all_downloads_complete() - - def search_and_download_track_simple(self, track_data, download_index): - """Simplified search and download for wishlist tracks""" + + def search_and_download_track_parallel(self, spotify_track, download_index, track_index): + artist_name = spotify_track.artists[0] if spotify_track.artists else "" + search_queries = self.generate_smart_search_queries(artist_name, spotify_track.name) + self.start_track_search_with_queries_parallel(spotify_track, search_queries, track_index, track_index, download_index) + + def start_track_search_with_queries_parallel(self, spotify_track, search_queries, track_index, table_index, download_index): + if not hasattr(self, 'parallel_search_tracking'): + self.parallel_search_tracking = {} + self.parallel_search_tracking[download_index] = { + 'spotify_track': spotify_track, 'track_index': track_index, + 'table_index': table_index, 'download_index': download_index, + 'completed': False, 'used_sources': set(), 'candidates': [], 'retry_count': 0 + } + self.start_search_worker_parallel(search_queries, spotify_track, track_index, table_index, 0, download_index) + + def start_search_worker_parallel(self, queries, spotify_track, track_index, table_index, query_index, download_index): + if query_index >= len(queries): + self.on_parallel_track_failed(download_index, "All search strategies failed") + return + query = queries[query_index] + worker = self.ParallelSearchWorker(self.soulseek_client, query) + worker.signals.search_completed.connect(lambda r, q: self.on_search_query_completed_parallel(r, queries, spotify_track, track_index, table_index, query_index, q, download_index)) + worker.signals.search_failed.connect(lambda q, e: self.on_search_query_completed_parallel([], queries, spotify_track, track_index, table_index, query_index, q, download_index)) + QThreadPool.globalInstance().start(worker) + + def on_search_query_completed_parallel(self, results, queries, spotify_track, track_index, table_index, query_index, query, download_index): + if self.cancel_requested: return + valid_candidates = self.get_valid_candidates(results, spotify_track, query) + if valid_candidates: + self.parallel_search_tracking[download_index]['candidates'] = valid_candidates + best_match = valid_candidates[0] + self.start_validated_download_parallel(best_match, spotify_track, track_index, table_index, download_index) + return + next_query_index = query_index + 1 + if next_query_index < len(queries): + self.start_search_worker_parallel(queries, spotify_track, track_index, table_index, next_query_index, download_index) + else: + self.on_parallel_track_failed(download_index, f"No valid results after trying all {len(queries)} queries.") + + def start_validated_download_parallel(self, slskd_result, spotify_metadata, track_index, table_index, download_index): + track_info = self.parallel_search_tracking[download_index] + if track_info.get('completed', False): + track_info['completed'] = False + if self.failed_downloads > 0: self.failed_downloads -= 1 + self.active_parallel_downloads += 1 + if self.completed_downloads > 0: self.completed_downloads -= 1 + source_key = f"{getattr(slskd_result, 'username', 'unknown')}_{slskd_result.filename}" + track_info['used_sources'].add(source_key) + spotify_based_result = self.create_spotify_based_search_result_from_validation(slskd_result, spotify_metadata) + self.track_table.setItem(table_index, 4, QTableWidgetItem("... Queued")) + self.start_matched_download_via_infrastructure_parallel(spotify_based_result, track_index, table_index, download_index) + + def start_matched_download_via_infrastructure_parallel(self, spotify_based_result, track_index, table_index, download_index): try: - # Create a simple search worker - artist_name = track_data.get('artists', [{}])[0].get('name', '') if track_data.get('artists') else '' - track_name = track_data.get('name', '') - - if not track_name: - self.on_track_download_failed(download_index, "Missing track name") - return - - # Create search query - query = f"{artist_name} {track_name}".strip() - if not query: - self.on_track_download_failed(download_index, "Cannot create search query") - return - - # Use enhanced worker with status updates - worker = SimpleWishlistDownloadWorker(self.soulseek_client, query, track_data, download_index) - worker.signals.status_updated.connect(self.on_track_status_updated) - worker.signals.download_completed.connect(self.on_track_download_completed) - worker.signals.download_failed.connect(self.on_track_download_failed) - - QThreadPool.globalInstance().start(worker) - + artist = type('Artist', (), {'name': spotify_based_result.artist})() + download_item = self.downloads_page._start_download_with_artist(spotify_based_result, artist) + if download_item: + self.active_downloads.append({ + 'download_index': download_index, 'track_index': track_index, + 'table_index': table_index, 'download_id': download_item.download_id, + 'slskd_result': spotify_based_result, 'candidates': self.parallel_search_tracking[download_index]['candidates'] + }) + else: + self.on_parallel_track_failed(download_index, "Failed to start download") except Exception as e: - logger.error(f"Error starting track download: {e}") - self.on_track_download_failed(download_index, str(e)) - - def on_track_status_updated(self, download_index, status_text): - """Handle live status updates for individual tracks""" + self.on_parallel_track_failed(download_index, str(e)) + + def poll_all_download_statuses(self): + if self._is_status_update_running or not self.active_downloads: return + self._is_status_update_running = True + items_to_check = [] + for d in self.active_downloads: + if d.get('slskd_result') and hasattr(d['slskd_result'], 'filename'): + items_to_check.append({ + 'widget_id': d['download_index'], + 'download_id': d.get('download_id'), + 'file_path': d['slskd_result'].filename, + 'api_missing_count': d.get('api_missing_count', 0) + }) + if not items_to_check: + self._is_status_update_running = False + return + worker = SyncStatusProcessingWorker(self.soulseek_client, items_to_check) + worker.signals.completed.connect(self._handle_processed_status_updates) + worker.signals.error.connect(lambda e: logger.error(f"Status Worker Error: {e}")) + self.download_status_pool.start(worker) + + def _handle_processed_status_updates(self, results): + import time + active_downloads_map = {d['download_index']: d for d in self.active_downloads} + for result in results: + download_index = result['widget_id'] + new_status = result['status'] + download_info = active_downloads_map.get(download_index) + if not download_info: continue + if 'api_missing_count' in result: + download_info['api_missing_count'] = result['api_missing_count'] + if result.get('transfer_id') and download_info.get('download_id') != result['transfer_id']: + download_info['download_id'] = result['transfer_id'] + if new_status in ['failed', 'cancelled']: + if download_info in self.active_downloads: self.active_downloads.remove(download_info) + self.retry_parallel_download_with_fallback(download_info) + elif new_status == 'completed': + if download_info in self.active_downloads: self.active_downloads.remove(download_info) + self.on_parallel_track_completed(download_index, success=True) + elif new_status == 'downloading': + progress = result.get('progress', 0) + self.track_table.setItem(download_info['table_index'], 4, QTableWidgetItem(f"⏬ Downloading ({progress}%)")) + if 'queued_start_time' in download_info: del download_info['queued_start_time'] + if progress < 1: + if 'downloading_start_time' not in download_info: + download_info['downloading_start_time'] = time.time() + elif time.time() - download_info['downloading_start_time'] > 90: + self.cancel_download_before_retry(download_info) + if download_info in self.active_downloads: self.active_downloads.remove(download_info) + self.retry_parallel_download_with_fallback(download_info) + else: + if 'downloading_start_time' in download_info: del download_info['downloading_start_time'] + elif new_status == 'queued': + self.track_table.setItem(download_info['table_index'], 4, QTableWidgetItem("... Queued")) + if 'queued_start_time' not in download_info: + download_info['queued_start_time'] = time.time() + elif time.time() - download_info['queued_start_time'] > 90: + self.cancel_download_before_retry(download_info) + if download_info in self.active_downloads: self.active_downloads.remove(download_info) + self.retry_parallel_download_with_fallback(download_info) + self._is_status_update_running = False + + def cancel_download_before_retry(self, download_info): try: - if 0 <= download_index < self.track_table.rowCount(): - self.track_table.setItem(download_index, 3, QTableWidgetItem(status_text)) - logger.debug(f"Updated track {download_index} status to: {status_text}") + slskd_result = download_info.get('slskd_result') + if not slskd_result: return + download_id = download_info.get('download_id') + username = getattr(slskd_result, 'username', None) + if download_id and username: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + try: + loop.run_until_complete(self.soulseek_client.cancel_download(download_id, username, remove=False)) + finally: + loop.close() except Exception as e: - logger.error(f"Error updating track status: {e}") - - def on_track_download_completed(self, download_index, download_id): - """Handle successful track download""" - try: - track_data = self.wishlist_tracks[download_index] - track_id = track_data.get('spotify_track_id') - - # Update UI - self.track_table.setItem(download_index, 3, QTableWidgetItem("✅ Downloaded")) - - # Mark as successful in wishlist service - if track_id: - self.wishlist_service.mark_track_download_result(track_id, success=True) - + logger.error(f"Error cancelling download: {e}") + + def retry_parallel_download_with_fallback(self, failed_download_info): + download_index = failed_download_info['download_index'] + track_info = self.parallel_search_tracking[download_index] + track_info['retry_count'] += 1 + if track_info['retry_count'] > 2: + self.on_parallel_track_failed(download_index, "All retries failed.") + return + candidates = failed_download_info.get('candidates', []) + used_sources = track_info.get('used_sources', set()) + next_candidate = None + for candidate in candidates: + source_key = f"{getattr(candidate, 'username', 'unknown')}_{candidate.filename}" + if source_key not in used_sources: + next_candidate = candidate + break + if not next_candidate: + self.on_parallel_track_failed(download_index, "No alternative sources in cache") + return + self.track_table.setItem(failed_download_info['table_index'], 4, QTableWidgetItem(f"🔄 Retrying ({track_info['retry_count']})...")) + self.start_validated_download_parallel(next_candidate, track_info['spotify_track'], track_info['track_index'], track_info['table_index'], download_index) + + def on_parallel_track_completed(self, download_index, success): + track_info = self.parallel_search_tracking.get(download_index) + if not track_info or track_info.get('completed', False): return + track_info['completed'] = True + if success: + self.track_table.setItem(track_info['table_index'], 4, QTableWidgetItem("✅ Downloaded")) + self.downloaded_tracks_count += 1 + self.downloaded_count_label.setText(str(self.downloaded_tracks_count)) self.successful_downloads += 1 - self.completed_downloads += 1 - self.active_parallel_downloads -= 1 - - # Update progress - self.download_progress.setValue(self.completed_downloads) - - # Continue with next downloads - self.start_next_batch_of_downloads() - - except Exception as e: - logger.error(f"Error handling download completion: {e}") - - def on_track_download_failed(self, download_index, error_message): - """Handle failed track download""" - try: - track_data = self.wishlist_tracks[download_index] - track_id = track_data.get('spotify_track_id') - - # Update UI - self.track_table.setItem(download_index, 3, QTableWidgetItem("❌ Failed")) - - # Mark as failed in wishlist service (increment retry count) - if track_id: - self.wishlist_service.mark_track_download_result(track_id, success=False, error_message=error_message) - + + self.wishlist_service.remove_track_from_wishlist(track_info['spotify_track'].id) + + + logger.info(f"Successfully downloaded and removed '{track_info['spotify_track'].name}' from wishlist.") + else: + self.track_table.setItem(track_info['table_index'], 4, QTableWidgetItem("❌ Failed")) self.failed_downloads += 1 - self.completed_downloads += 1 - self.active_parallel_downloads -= 1 - - # Update progress - self.download_progress.setValue(self.completed_downloads) - - # Continue with next downloads - self.start_next_batch_of_downloads() - - except Exception as e: - logger.error(f"Error handling download failure: {e}") - + if track_info not in self.permanently_failed_tracks: + self.permanently_failed_tracks.append(track_info) + self.update_failed_matches_button() + self.completed_downloads += 1 + self.active_parallel_downloads -= 1 + self.download_progress.setValue(self.completed_downloads) + self.start_next_batch_of_downloads() + + def on_parallel_track_failed(self, download_index, reason): + logger.error(f"Parallel download {download_index + 1} failed: {reason}") + self.on_parallel_track_completed(download_index, False) + + def update_failed_matches_button(self): + count = len(self.permanently_failed_tracks) + if count > 0: + self.correct_failed_btn.setText(f"🔧 Correct {count} Failed Match{'es' if count > 1 else ''}") + self.correct_failed_btn.show() + else: + self.correct_failed_btn.hide() + + def on_correct_failed_matches_clicked(self): + if not self.permanently_failed_tracks: return + # This requires ManualMatchModal to be copied or imported + from ui.pages.sync import ManualMatchModal + manual_modal = ManualMatchModal(self) + manual_modal.track_resolved.connect(self.on_manual_match_resolved) + manual_modal.exec() + + def on_manual_match_resolved(self, resolved_track_info): + original_failed_track = next((t for t in self.permanently_failed_tracks if t['download_index'] == resolved_track_info['download_index']), None) + if original_failed_track: + self.permanently_failed_tracks.remove(original_failed_track) + self.update_failed_matches_button() + def on_all_downloads_complete(self): - """Handle completion of all downloads""" - try: - self.download_in_progress = False - - # Show completion message - message = f"Wishlist processing complete!\n\n" - message += f"Successfully downloaded: {self.successful_downloads}\n" - message += f"Failed: {self.failed_downloads}\n" - message += f"Total processed: {self.completed_downloads}\n\n" - - if self.failed_downloads > 0: - message += "Failed tracks remain in wishlist for future retry." - else: - message += "All tracks downloaded successfully!" - - QMessageBox.information(self, "Downloads Complete", message) - - # Emit signal to update parent - self.process_finished.emit() - - # Close modal - self.accept() - - except Exception as e: - logger.error(f"Error handling downloads completion: {e}") - - def clear_wishlist(self): - """Clear all tracks from wishlist""" - try: - reply = QMessageBox.question( - self, "Clear Wishlist", - "Are you sure you want to clear all tracks from the wishlist?\n\nThis action cannot be undone.", - QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No - ) - - if reply == QMessageBox.StandardButton.Yes: - if self.wishlist_service.clear_wishlist(): - QMessageBox.information(self, "Wishlist Cleared", "All tracks have been removed from the wishlist.") - self.process_finished.emit() # Update parent count - self.accept() # Close modal - else: - QMessageBox.warning(self, "Error", "Failed to clear wishlist.") - except Exception as e: - logger.error(f"Error clearing wishlist: {e}") - QMessageBox.critical(self, "Error", f"Failed to clear wishlist: {str(e)}") - + self.download_in_progress = False + self.parent_dashboard.auto_processing_wishlist = False + self.cancel_btn.hide() + self.process_finished.emit() + if self.successful_downloads > 0 and hasattr(self.parent_dashboard, 'scan_manager') and self.parent_dashboard.scan_manager: + self.parent_dashboard.scan_manager.request_scan(f"Wishlist download completed ({self.successful_downloads} tracks)") + + wishlist_added_count = 0 + if self.permanently_failed_tracks: + source_context = {'added_from': 'wishlist_modal', 'timestamp': datetime.now().isoformat()} + for failed_track_info in self.permanently_failed_tracks: + if self.wishlist_service.add_failed_track_from_modal(track_info=failed_track_info, source_type='wishlist', source_context=source_context): + wishlist_added_count += 1 + + final_message = f"Completed downloading {self.successful_downloads}/{len(self.missing_tracks)} missing tracks!\n\n" + if wishlist_added_count > 0: + final_message += f"✨ Re-added {wishlist_added_count} failed track{'s' if wishlist_added_count > 1 else ''} to wishlist for future retry.\n\n" + if self.permanently_failed_tracks: + final_message += "You can also manually correct failed downloads." + else: + final_message += "All tracks were downloaded successfully!" + logger.info("Wishlist processing complete. Scheduling next run in 60 minutes.") + self.parent_dashboard.wishlist_retry_timer.start(3600000) # 60 minutes + QMessageBox.information(self, "Downloads Complete", final_message) + def on_cancel_clicked(self): - """Handle cancel button""" - self.cancel_requested = True + self.cancel_operations() self.process_finished.emit() self.reject() - + + def on_close_clicked(self): + if self.cancel_requested or not self.download_in_progress: + self.cancel_operations() + self.process_finished.emit() + self.reject() + + def cancel_operations(self): + self.cancel_requested = True + for worker in self.active_workers: + if hasattr(worker, 'cancel'): + worker.cancel() + self.active_workers.clear() + self.download_status_timer.stop() + def closeEvent(self, event): - """Clean up when modal is closed""" - try: - # Stop the status check timer - if hasattr(self, 'status_check_timer') and self.status_check_timer: - self.status_check_timer.stop() - - # Cancel any ongoing downloads - if self.download_in_progress: - self.cancel_requested = True - - except Exception as e: - logger.error(f"Error during modal close: {e}") - - super().closeEvent(event) + """Override close event to hide the modal if a download is in progress.""" + if self.download_in_progress and not self.cancel_requested: + # If a download is running, just hide the window. + # The user can bring it back by clicking the wishlist button again. + logger.info("Hiding wishlist modal while download is in progress.") + self.hide() + event.ignore() + else: + # If not downloading or cancelled, allow it to close for real. + logger.info("Closing wishlist modal.") + self.cancel_operations() + self.process_finished.emit() + event.accept() + + class ParallelSearchWorker(QRunnable): + def __init__(self, soulseek_client, query): + super().__init__() + self.soulseek_client = soulseek_client + self.query = query + self.signals = self.create_signals() + def create_signals(self): + class Signals(QObject): + search_completed = pyqtSignal(list, str) + search_failed = pyqtSignal(str, str) + return Signals() + def run(self): + loop = None + try: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + search_result = loop.run_until_complete(self.soulseek_client.search(self.query)) + results_list = search_result[0] if isinstance(search_result, tuple) and search_result else [] + self.signals.search_completed.emit(results_list, self.query) + except Exception as e: + self.signals.search_failed.emit(self.query, str(e)) + finally: + if loop: loop.close() + + def get_valid_candidates(self, results, spotify_track, query): + if not results: return [] + initial_candidates = self.matching_engine.find_best_slskd_matches_enhanced(spotify_track, results) + if not initial_candidates: return [] + verified_candidates = [] + spotify_artist_name = spotify_track.artists[0] if spotify_track.artists else "" + normalized_spotify_artist = re.sub(r'[^a-zA-Z0-9]', '', spotify_artist_name).lower() + for candidate in initial_candidates: + normalized_slskd_path = re.sub(r'[^a-zA-Z0-9]', '', candidate.filename).lower() + if normalized_spotify_artist in normalized_slskd_path: + verified_candidates.append(candidate) + return verified_candidates + + def create_spotify_based_search_result_from_validation(self, slskd_result, spotify_metadata): + class SpotifyBasedSearchResult: + def __init__(self): + self.filename = getattr(slskd_result, 'filename', f"{spotify_metadata.name}.flac") + self.username = getattr(slskd_result, 'username', 'unknown') + self.size = getattr(slskd_result, 'size', 0) + self.quality = getattr(slskd_result, 'quality', 'flac') + self.artist = spotify_metadata.artists[0] if spotify_metadata.artists else "Unknown" + self.title = spotify_metadata.name + self.album = spotify_metadata.album + return SpotifyBasedSearchResult() + + def generate_smart_search_queries(self, artist_name, track_name): + class MockSpotifyTrack: + def __init__(self, name, artists, album=None): + self.name = name + self.artists = artists if isinstance(artists, list) else [artists] if artists else [] + self.album = album + mock_track = MockSpotifyTrack(track_name, [artist_name] if artist_name else [], None) + queries = self.matching_engine.generate_download_queries(mock_track) + legacy_queries = [track_name.strip()] + if artist_name: + artist_words = artist_name.split() + if artist_words: + first_word = artist_words[0] + if first_word.lower() == 'the' and len(artist_words) > 1: + first_word = artist_words[1] + if len(first_word) > 1: + legacy_queries.append(f"{track_name} {first_word}".strip()) + all_queries = queries + legacy_queries + unique_queries = list(dict.fromkeys(q for q in all_queries if q)) + return unique_queries + + + class SimpleWishlistDownloadWorker(QRunnable): @@ -1936,12 +2339,33 @@ class DashboardPage(QWidget): # Track if automatic processing is currently running self.auto_processing_wishlist = False - + self.wishlist_download_modal = None # Load initial database statistics (with delay to avoid startup issues) QTimer.singleShot(1000, self.refresh_database_statistics) # Load initial wishlist count (with slight delay) QTimer.singleShot(1500, self.update_wishlist_button_count) + + def _ensure_wishlist_modal_exists(self): + """Creates the persistent wishlist modal instance if it doesn't exist.""" + if self.wishlist_download_modal is None: + logger.info("Creating persistent wishlist download modal instance.") + spotify_client = self.service_clients.get('spotify_client') + plex_client = self.service_clients.get('plex_client') + soulseek_client = self.service_clients.get('soulseek_client') + downloads_page = self.downloads_page + + if not all([spotify_client, plex_client, soulseek_client, downloads_page]): + QMessageBox.critical(self, "Error", "Required services not available for wishlist search.") + return False + + self.wishlist_download_modal = DownloadMissingWishlistTracksModal( + self.wishlist_service, self, downloads_page, + spotify_client, plex_client, soulseek_client + ) + self.wishlist_download_modal.process_finished.connect(self.on_wishlist_modal_finished) + return True + def set_service_clients(self, spotify_client, plex_client, soulseek_client, downloads_page=None): """Called from main window to provide service client references""" self.data_provider.set_service_clients(spotify_client, plex_client, soulseek_client) @@ -2410,6 +2834,25 @@ class DashboardPage(QWidget): except Exception as e: logger.error(f"Error updating database info: {e}") + def on_wishlist_modal_finished(self): + """Called when the modal's download process is completely done or cancelled.""" + logger.info("Wishlist download process finished. Resetting modal instance.") + # We can now safely discard the modal instance. A new one will be created on the next run. + self.wishlist_download_modal = None + self.update_wishlist_button_count() + + def start_wishlist_search_process(self): + """ + Ensures the wishlist modal exists and tells it to start the search process. + This is the single entry point for automatic searches. + """ + if not self._ensure_wishlist_modal_exists(): + return # Modal creation failed + + # Tell the modal to begin its search process + self.wishlist_download_modal.start_search() + + def _cleanup_stats_worker(self, worker): """Clean up a finished stats worker""" try: @@ -2741,46 +3184,33 @@ class DashboardPage(QWidget): thread.deleteLater() self.data_provider._test_threads.clear() + + def on_wishlist_button_clicked(self): - """Handle wishlist button click - open wishlist modal""" + """ + Shows the persistent wishlist modal, creating it if it doesn't exist yet. + If a search is in progress, this will reveal the live state. + """ try: - summary = self.wishlist_service.get_wishlist_summary() - total_tracks = summary['total_tracks'] - - if total_tracks == 0: - QMessageBox.information(self, "Wishlist", "Your wishlist is empty!\n\nFailed download tracks will be automatically added here for retry.") - return - - # Need to get service clients to pass to modal - if not hasattr(self, 'service_clients'): - QMessageBox.warning(self, "Wishlist", "Service clients not initialized. Please restart the application.") - return - - spotify_client = self.service_clients.get('spotify_client') - plex_client = self.service_clients.get('plex_client') - soulseek_client = self.service_clients.get('soulseek_client') - downloads_page = self.downloads_page - - if not all([spotify_client, plex_client, soulseek_client, downloads_page]): - QMessageBox.warning(self, "Wishlist", "Required services not available. Please check your service connections.") + # If the modal doesn't exist and there are no tracks, show info and return. + if self.wishlist_download_modal is None and self.wishlist_service.get_wishlist_count() == 0: + QMessageBox.information(self, "Wishlist", "Your wishlist is empty!") return - - # Create and show the wishlist download modal - modal = DownloadMissingWishlistTracksModal( - self.wishlist_service, - self, - downloads_page, - spotify_client, - plex_client, - soulseek_client - ) - modal.process_finished.connect(self.update_wishlist_button_count) # Update count when done - modal.exec() - + + # Ensure the modal instance exists before trying to show it. + if not self._ensure_wishlist_modal_exists(): + return # Modal creation failed, error message already shown. + + # Now that we're sure the modal exists, just show it. + self.wishlist_download_modal.show() + self.wishlist_download_modal.activateWindow() + self.wishlist_download_modal.raise_() + except Exception as e: logger.error(f"Error opening wishlist: {e}") QMessageBox.critical(self, "Error", f"Failed to open wishlist: {str(e)}") - + + def update_wishlist_button_count(self): """Update the wishlist button with current count""" try: @@ -2828,53 +3258,31 @@ class DashboardPage(QWidget): logger.error(f"Error updating wishlist button count: {e}") def process_wishlist_automatically(self): - """Automatically process wishlist tracks in the background""" + """Automatically process wishlist tracks in the background.""" try: - # Skip if already processing or no service clients available if self.auto_processing_wishlist: - logger.debug("Wishlist auto-processing already running, skipping") - return - - if not hasattr(self, 'service_clients') or not self.service_clients: - logger.debug("Service clients not available for wishlist auto-processing") + logger.debug("Wishlist auto-processing already running, skipping.") + # Reschedule the next check + self.wishlist_retry_timer.start(3600000) # 60 minutes return - - # Check if we have tracks to process - wishlist_count = self.wishlist_service.get_wishlist_count() - if wishlist_count == 0: - logger.debug("No tracks in wishlist for auto-processing") - return - - # Get service clients - spotify_client = self.service_clients.get('spotify_client') - plex_client = self.service_clients.get('plex_client') - soulseek_client = self.service_clients.get('soulseek_client') - downloads_page = self.downloads_page - - if not all([spotify_client, plex_client, soulseek_client, downloads_page]): - logger.warning("Required services not available for wishlist auto-processing") + + if self.wishlist_service.get_wishlist_count() == 0: + logger.debug("No tracks in wishlist for auto-processing.") + # Reschedule the next check + self.wishlist_retry_timer.start(3600000) # 60 minutes return - - logger.info(f"Starting automatic wishlist processing for {wishlist_count} tracks") - self.auto_processing_wishlist = True - - # Create and run the background processing worker - worker = AutoWishlistProcessorWorker( - self.wishlist_service, - spotify_client, - plex_client, - soulseek_client, - downloads_page - ) - worker.signals.processing_complete.connect(self.on_auto_wishlist_processing_complete) - worker.signals.processing_error.connect(self.on_auto_wishlist_processing_error) - - # Run in thread pool - QThreadPool.globalInstance().start(worker) - + + logger.info("Starting automatic wishlist processing...") + # Use the central method to start the process + self.start_wishlist_search_process() + + # The on_all_downloads_complete method will handle rescheduling the timer. + except Exception as e: logger.error(f"Error starting automatic wishlist processing: {e}") self.auto_processing_wishlist = False + # Reschedule on error + self.wishlist_retry_timer.start(3600000) # 60 minutes def on_auto_wishlist_processing_complete(self, successful, failed, total): """Handle completion of automatic wishlist processing"""