diff --git a/.spotify_cache b/.spotify_cache index e67b7df2..383f97a1 100644 --- a/.spotify_cache +++ b/.spotify_cache @@ -1 +1 @@ -{"access_token": "BQBcawhTowBatKdkvO7n3N80j8Pe7sZlSAoUGyWlT6Iyz-SHjUBxehwq0FomzdFUGEkJk2pQ3RKNoCY60Kvd9mDjgmh3rSFYphYE0LAEq71j1i0SR0vZnhpKV_5Ia7DdLWS3Z5em2t1XTe0dsnJoRHlvfguCDGFTGrgzOEUUtzJ6rUMqcWlgQTqWcqif0xAKij8l4hwiPI5fsWbRVQUdD-MXLqBveArU3tcJunylwDbqJfqSJ_8HxRNLDQoxtqfn", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1753338014, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"} \ No newline at end of file +{"access_token": "BQCLT_slOYLk3v2BHMORcGETcy_0zWC43NqlAHsNvKZg6YnGZwlW3U98evnFP_mlm-PRC5LjSZc39mtX9Z5ikNILw13_nCpCLAYD5Qh0ZP4tUtCwlbmkCyDrhnzFXRgv7BaiFhCmxz4QV63j1YMhgk_NM95Bg8cMy8_K7jcdgNP5ApVC6LXbhNK4ytYFhuicULb-fTbJna2DAmp37QduR9dc1LHazfFYGb7Um_fAYfo7CjipUhyFI54NH3zy32aQ", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1753341652, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"} \ No newline at end of file diff --git a/logs/app.log b/logs/app.log index b414ece3..58eb02f1 100644 --- a/logs/app.log +++ b/logs/app.log @@ -446604,3 +446604,33 @@ 2025-07-23 22:21:16 - newmusic.soulseek_client - DEBUG - _make_request:243 - Headers: {'Content-Type': 'application/json', 'X-API-Key': '1234567891234567'} 2025-07-23 22:21:16 - newmusic.soulseek_client - DEBUG - _make_request:254 - Response status: 200 2025-07-23 22:21:16 - newmusic.soulseek_client - DEBUG - _make_request:255 - Response text: [{"username":"DuckinthePond","directories":[{"directory":"@@qkrmw\\Music\\Flac Files\\Chart\\Billboard Top 100\\Billboard Top 100 - Hits of 2000-2009\\Billboard Top 100 - Hits of 2006","fileCount":1,"files":[{"id":"1c1afe3d-46fb-4d8a-ba73-1d1af559489c","username":"DuckinthePond","direction":"Download","filename":"@@qkrmw\\Music\\Flac Files\\Chart\\Billboard Top 100\\Billboard Top 100 - Hits of 2000-2009\\Billboard Top 100 - Hits of 2006\\035 - Ludacris feat Pharrell - Money Maker.flac","size":30... +2025-07-23 23:17:01 - newmusic - INFO - setup_logging:57 - Logging initialized with level: DEBUG +2025-07-23 23:17:01 - newmusic.main - INFO - main:211 - Starting NewMusic application +2025-07-23 23:17:01 - newmusic.spotify_client - INFO - _setup_client:179 - Spotify client initialized (user info will be fetched when needed) +2025-07-23 23:17:01 - newmusic.soulseek_client - INFO - _setup_client:220 - Soulseek client configured with slskd at http://localhost:5030 +2025-07-23 23:17:01 - newmusic.spotify_client - INFO - _setup_client:179 - Spotify client initialized (user info will be fetched when needed) +2025-07-23 23:17:02 - newmusic.main - INFO - change_page:163 - Changed to page: dashboard +2025-07-23 23:17:02 - newmusic.main - INFO - setup_media_player_connections:150 - Media player connections established between sidebar and downloads page +2025-07-23 23:17:02 - newmusic.plex_client - INFO - _find_music_library:116 - Found music library: Music +2025-07-23 23:17:02 - newmusic.plex_client - INFO - _setup_client:102 - Successfully connected to Plex server: PLEX-MACHINE +2025-07-23 23:17:10 - newmusic.main - INFO - change_page:163 - Changed to page: downloads +2025-07-23 23:17:10 - newmusic.main - INFO - change_page:163 - Changed to page: sync +2025-07-23 23:17:10 - newmusic.spotify_client - INFO - _ensure_user_id:195 - Successfully authenticated with Spotify as broquethomas +2025-07-23 23:17:11 - newmusic.spotify_client - INFO - get_user_playlists_metadata_only:256 - Retrieved 7 playlist metadata (first batch) +2025-07-23 23:20:35 - newmusic.main - INFO - closeEvent:176 - Closing application... +2025-07-23 23:20:35 - newmusic.main - INFO - closeEvent:181 - Cleaning up Downloads page threads... +2025-07-23 23:20:35 - newmusic.main - INFO - closeEvent:186 - Stopping status monitoring thread... +2025-07-23 23:20:36 - newmusic.main - INFO - closeEvent:191 - Closing Soulseek client... +2025-07-23 23:20:36 - newmusic.main - INFO - closeEvent:197 - Application closed successfully +2025-07-23 23:20:38 - newmusic - INFO - setup_logging:57 - Logging initialized with level: DEBUG +2025-07-23 23:20:38 - newmusic.main - INFO - main:211 - Starting NewMusic application +2025-07-23 23:20:38 - newmusic.spotify_client - INFO - _setup_client:179 - Spotify client initialized (user info will be fetched when needed) +2025-07-23 23:20:38 - newmusic.soulseek_client - INFO - _setup_client:220 - Soulseek client configured with slskd at http://localhost:5030 +2025-07-23 23:20:38 - newmusic.spotify_client - INFO - _setup_client:179 - Spotify client initialized (user info will be fetched when needed) +2025-07-23 23:20:39 - newmusic.main - INFO - change_page:163 - Changed to page: dashboard +2025-07-23 23:20:39 - newmusic.main - INFO - setup_media_player_connections:150 - Media player connections established between sidebar and downloads page +2025-07-23 23:20:39 - newmusic.plex_client - INFO - _find_music_library:116 - Found music library: Music +2025-07-23 23:20:39 - newmusic.plex_client - INFO - _setup_client:102 - Successfully connected to Plex server: PLEX-MACHINE +2025-07-23 23:20:52 - newmusic.main - INFO - change_page:163 - Changed to page: sync +2025-07-23 23:20:52 - newmusic.spotify_client - INFO - _ensure_user_id:195 - Successfully authenticated with Spotify as broquethomas +2025-07-23 23:20:52 - newmusic.spotify_client - INFO - get_user_playlists_metadata_only:256 - Retrieved 7 playlist metadata (first batch) diff --git a/ui/pages/__pycache__/sync.cpython-312.pyc b/ui/pages/__pycache__/sync.cpython-312.pyc index 1cb06597..e222ab7d 100644 Binary files a/ui/pages/__pycache__/sync.cpython-312.pyc and b/ui/pages/__pycache__/sync.cpython-312.pyc differ diff --git a/ui/pages/sync.py b/ui/pages/sync.py index 867b4cf9..74b6c5d9 100644 --- a/ui/pages/sync.py +++ b/ui/pages/sync.py @@ -811,7 +811,9 @@ class PlaylistDetailsModal(QDialog): playlist_item_widget.download_modal = modal - self.parent_page.on_download_process_started(self.playlist.id, playlist_item_widget) + # --- FIX --- + # The call to on_download_process_started has been removed from here + # and moved to the modal's "Begin Search" button. self.accept() modal.show() @@ -1425,18 +1427,22 @@ class SyncPage(QWidget): playlist_item_widget.show_operation_status() self.refresh_btn.setEnabled(False) self.refresh_btn.setText("🔄 Working...") - # Connect the finished signal from the modal - playlist_item_widget.download_modal.process_finished.connect( - lambda: self.on_download_process_finished(playlist_id) - ) + # --- FIX: Connect the finished signal from the modal --- + # This ensures that when the modal is finished (or cancelled), the cleanup function is called. + if playlist_item_widget.download_modal: + playlist_item_widget.download_modal.process_finished.connect( + lambda: self.on_download_process_finished(playlist_id) + ) def on_download_process_finished(self, playlist_id): """Re-enables refresh button if no other downloads are active.""" - print(f"Download process finished for playlist: {playlist_id}.") + print(f"Download process finished or cancelled for playlist: {playlist_id}.") if playlist_id in self.active_download_processes: playlist_item_widget = self.active_download_processes.pop(playlist_id) - playlist_item_widget.download_modal = None - playlist_item_widget.hide_operation_status() + # --- FIX: Reset the UI state of the playlist item --- + if playlist_item_widget: + playlist_item_widget.download_modal = None + playlist_item_widget.hide_operation_status() if not self.active_download_processes: print("All download processes finished. Re-enabling refresh.") @@ -2833,6 +2839,10 @@ class DownloadMissingTracksModal(QDialog): def on_begin_search_clicked(self): """Handle Begin Search button click - starts Plex analysis""" + # --- FIX: Trigger the UI change on the main page --- + # This is the correct point to signal that the process has started. + self.parent_page.on_download_process_started(self.playlist.id, self.playlist_item) + self.begin_search_btn.hide() self.cancel_btn.show() self.analysis_progress.setVisible(True) @@ -2840,6 +2850,7 @@ class DownloadMissingTracksModal(QDialog): self.analysis_progress.setValue(0) self.download_in_progress = True # Set flag self.start_plex_analysis() + def start_plex_analysis(self): """Start Plex analysis using existing worker""" @@ -3179,24 +3190,66 @@ class DownloadMissingTracksModal(QDialog): f"Completed downloading {self.successful_downloads}/{len(self.missing_tracks)} missing tracks!") def on_cancel_clicked(self): - """Handle Cancel button - cancels operations and closes modal""" + """Handle Cancel button - cancels operations, emits finished signal, and closes modal.""" + # --- FIX: The full cancellation logic is now centralized here. --- self.cancel_operations() - self.reject() + self.process_finished.emit() # Signal the main page to clean up and reset the button. + self.reject() # Close the modal. def on_close_clicked(self): self.reject() def cancel_operations(self): - """Cancel any ongoing operations""" - self.cancel_requested = True + """Cancel any ongoing operations, including active slskd downloads.""" + print("🛑 Cancelling all operations for this playlist...") + self.cancel_requested = True # Flag to stop any new workers from starting. + + # --- FIX: Actively cancel downloads on the slskd server --- + if self.active_downloads: + print(f"Requesting cancellation for {len(self.active_downloads)} active download(s)...") + + import asyncio + try: + loop = asyncio.get_running_loop() + except RuntimeError: + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + + soulseek_client = self.parent_page.soulseek_client + + # Create tasks to cancel all active downloads concurrently + tasks = [] + for download_info in self.active_downloads: + download_id = download_info.get('download_id') + # Assumes the soulseek_client has a method to make raw API calls. + # A DELETE request is standard for cancellation in RESTful APIs like slskd's. + if download_id and hasattr(soulseek_client, '_make_request'): + tasks.append( + soulseek_client._make_request('DELETE', f'transfers/downloads/{download_id}') + ) + + if tasks: + try: + # Wait for all cancellation requests to be sent + loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) + print("All cancellation requests sent to slskd.") + except Exception as e: + print(f"An error occurred while sending cancellation requests: {e}") + + # Cancel background workers (like the initial Plex analysis) for worker in self.active_workers: - if hasattr(worker, 'cancel'): worker.cancel() + if hasattr(worker, 'cancel'): + worker.cancel() self.active_workers.clear() + + # Clean up any fallback thread pools for pool in self.fallback_pools: pool.waitForDone(1000) self.fallback_pools.clear() + + # Stop the status polling timer to prevent further checks self.download_status_timer.stop() - print("🛑 All operations cancelled successfully") + print("🛑 Modal operations cancelled successfully.") def closeEvent(self, event): """