This commit is contained in:
Broque Thomas 2025-07-23 21:46:31 -07:00
parent 6285ef6615
commit 0f74043512
4 changed files with 17772 additions and 70 deletions

View file

@ -1 +1 @@
{"access_token": "BQC18WaAYE4mMb-fxpccf3_kqLmSJKQ5P38RuSSkNj-QMbEew3fdEZeOt8sabT4OYFb93n4v859YdkpBt0hT0DlRoWHeiHkpb9Bg6n_i__jEIR2unKdNTdKFoupmP9Xed1-ZVczEAVhDb7LL-bPNMVWKseocEh72C5WdDq3jonUE_BzLW0g-8RKG9Qf2Sh8xVJCAUnUk3fhSegMW3jZHw0-72NKmnrkcGAhr5hmGzpeZ7qF-e4ecKp9olkFJl9vx", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1753326837, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"} {"access_token": "BQAk5vVhLVv9j1H0w4esI33zLvfuC6cw2icwwe1dYmbtpcH-1nEzhsPo7-RZsoaRLvZ4uLVjBFr9b0NWpKs2JBsFq1mVCxoDckML082ogffnsKhyP3QgKdETruf04FBetm5IY1s4THMwJyK-DzDQRuHHshpakw53pLaojKMp6dR1IJN98v6I-3RBexumJgX9pvf6mn-FFURWyi-Df8iB-2UWIWR4ANLhIdTte3LqRybl5tm63aFkG7D6Rcl_XZBc", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1753334396, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}

17673
logs/app.log

File diff suppressed because it is too large Load diff

View file

@ -797,59 +797,24 @@ class PlaylistDetailsModal(QDialog):
"""Handle Download Missing Tracks button click""" """Handle Download Missing Tracks button click"""
print("🔄 Download Missing Tracks button clicked!") print("🔄 Download Missing Tracks button clicked!")
if not self.playlist: if not self.playlist or not self.playlist.tracks:
print("❌ No playlist selected")
QMessageBox.warning(self, "Error", "No playlist selected")
return
if not self.playlist.tracks:
print("❌ Playlist tracks not loaded")
QMessageBox.warning(self, "Error", "Playlist tracks not loaded") QMessageBox.warning(self, "Error", "Playlist tracks not loaded")
return return
print(f"✅ Playlist: {self.playlist.name} with {len(self.playlist.tracks)} tracks") playlist_item_widget = self.parent_page.find_playlist_item_widget(self.playlist.id)
if not playlist_item_widget:
# Get access to parent's Plex and Soulseek clients through parent reference QMessageBox.critical(self, "Error", "Could not find the associated playlist item on the main page.")
if not hasattr(self.parent_page, 'plex_client'):
print("❌ Plex client not available")
QMessageBox.warning(self, "Service Unavailable",
"Plex client not available. Please check your configuration.")
return return
if not hasattr(self.parent_page, 'soulseek_client') or not self.parent_page.soulseek_client: print("🚀 Creating DownloadMissingTracksModal...")
print("❌ Soulseek client not available") modal = DownloadMissingTracksModal(self.playlist, playlist_item_widget, self.parent_page, self.parent_page.downloads_page)
QMessageBox.warning(self, "Service Unavailable",
"Soulseek client not available. Please check your configuration.")
return
print("✅ Plex and Soulseek clients available") playlist_item_widget.download_modal = modal
# Create and show the enhanced download missing tracks modal self.parent_page.on_download_process_started(self.playlist.id, playlist_item_widget)
try:
print("🚀 Creating modal...")
modal = DownloadMissingTracksModal(self.playlist, self.parent_page, self, self.parent_page.downloads_page)
print("✅ Modal created successfully")
# Store modal reference to prevent garbage collection self.accept()
self.download_modal = modal modal.show()
# Find and store modal reference in playlist item for reopening
playlist_item = self.find_playlist_item_from_sync_modal()
if playlist_item:
playlist_item.set_download_modal(modal)
print("🖥️ Closing current sync modal...")
self.accept() # Close the current sync modal
print("🖥️ Showing download modal...")
result = modal.exec()
print(f"✅ Modal closed with result: {result}")
except Exception as e:
print(f"❌ Exception creating modal: {e}")
import traceback
traceback.print_exc()
QMessageBox.critical(self, "Modal Error", f"Failed to open download modal: {str(e)}")
def find_playlist_item_from_sync_modal(self): def find_playlist_item_from_sync_modal(self):
"""Find the PlaylistItem widget for this playlist from sync modal""" """Find the PlaylistItem widget for this playlist from sync modal"""
@ -1164,8 +1129,9 @@ class PlaylistItem(QFrame):
self.name = name self.name = name
self.track_count = track_count self.track_count = track_count
self.sync_status = sync_status self.sync_status = sync_status
self.playlist = playlist # Store playlist object reference self.playlist = playlist
self.is_selected = False self.is_selected = False
self.download_modal = None # This line is new
self.setup_ui() self.setup_ui()
def setup_ui(self): def setup_ui(self):
@ -1241,7 +1207,7 @@ class PlaylistItem(QFrame):
# Action button or status indicator # Action button or status indicator
self.action_btn = QPushButton("Sync / Download") self.action_btn = QPushButton("Sync / Download")
self.action_btn.setFixedSize(120, 30) # Slightly wider for longer text self.action_btn.setFixedSize(120, 30) # Slightly wider for longer text
self.action_btn.clicked.connect(self.on_view_details_clicked) self.action_btn.clicked.connect(self.on_action_clicked)
self.action_btn.setStyleSheet(""" self.action_btn.setStyleSheet("""
QPushButton { QPushButton {
background: transparent; background: transparent;
@ -1288,17 +1254,34 @@ class PlaylistItem(QFrame):
layout.addWidget(self.action_btn) layout.addWidget(self.action_btn)
layout.addWidget(self.status_label) layout.addWidget(self.status_label)
def show_operation_status(self, status_text="View Progress"):
"""Changes the button to show an operation is in progress."""
self.status_label.setText(status_text)
self.status_label.show()
self.action_btn.hide()
def hide_operation_status(self):
"""Resets the button to its default state."""
self.status_label.hide()
self.action_btn.show()
def mousePressEvent(self, event): def mousePressEvent(self, event):
if event.button() == Qt.MouseButton.LeftButton: if event.button() == Qt.MouseButton.LeftButton:
self.checkbox.setChecked(not self.checkbox.isChecked()) self.checkbox.setChecked(not self.checkbox.isChecked())
super().mousePressEvent(event) super().mousePressEvent(event)
def on_view_details_clicked(self): def on_action_clicked(self):
"""Handle View Details button click""" """
if self.playlist: If a download is in progress, show the modal. Otherwise, open details.
"""
if self.download_modal:
self.download_modal.show()
self.download_modal.activateWindow()
else:
self.view_details_clicked.emit(self.playlist) self.view_details_clicked.emit(self.playlist)
def show_operation_status(self, status_text): def show_operation_status(self, status_text="View Progress"):
"""Show operation status and hide action button""" """Show operation status and hide action button"""
self.status_label.setText(status_text) self.status_label.setText(status_text)
self.status_label.show() self.status_label.show()
@ -1411,7 +1394,7 @@ class SyncPage(QWidget):
self.downloads_page = downloads_page self.downloads_page = downloads_page
self.current_playlists = [] self.current_playlists = []
self.playlist_loader = None self.playlist_loader = None
self.active_download_processes = {}
# Track cache for performance # Track cache for performance
self.track_cache = {} # playlist_id -> tracks self.track_cache = {} # playlist_id -> tracks
@ -1425,6 +1408,42 @@ class SyncPage(QWidget):
self.show_initial_state() self.show_initial_state()
self.playlists_loaded = False self.playlists_loaded = False
# Add these three methods inside the SyncPage class
def find_playlist_item_widget(self, playlist_id):
"""Finds the PlaylistItem widget in the UI that corresponds to a given playlist ID."""
for i in range(self.playlist_layout.count()):
item = self.playlist_layout.itemAt(i)
widget = item.widget()
if isinstance(widget, PlaylistItem) and widget.playlist.id == playlist_id:
return widget
return None
def on_download_process_started(self, playlist_id, playlist_item_widget):
"""Disables refresh button and updates the playlist item UI."""
print(f"Download process started for playlist: {playlist_id}. Disabling refresh.")
self.active_download_processes[playlist_id] = playlist_item_widget
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)
)
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}.")
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()
if not self.active_download_processes:
print("All download processes finished. Re-enabling refresh.")
self.refresh_btn.setEnabled(True)
self.refresh_btn.setText("🔄 Refresh")
def showEvent(self, event): def showEvent(self, event):
"""Auto-load playlists when page becomes visible (but not during app startup)""" """Auto-load playlists when page becomes visible (but not during app startup)"""
super().showEvent(event) super().showEvent(event)
@ -2417,13 +2436,12 @@ class ManualMatchModal(QDialog):
class DownloadMissingTracksModal(QDialog): class DownloadMissingTracksModal(QDialog):
"""Enhanced modal for downloading missing tracks with live progress tracking""" """Enhanced modal for downloading missing tracks with live progress tracking"""
process_finished = pyqtSignal()
def __init__(self, playlist, parent_page, sync_modal, downloads_page): def __init__(self, playlist, playlist_item, parent_page, downloads_page):
print(f"🏗️ Initializing DownloadMissingTracksModal...") super().__init__(parent_page)
super().__init__(sync_modal)
self.playlist = playlist self.playlist = playlist
self.playlist_item = playlist_item
self.parent_page = parent_page self.parent_page = parent_page
self.sync_modal = sync_modal
self.downloads_page = downloads_page self.downloads_page = downloads_page
# State tracking # State tracking
@ -2507,7 +2525,7 @@ class DownloadMissingTracksModal(QDialog):
"""Set up the enhanced modal UI""" """Set up the enhanced modal UI"""
self.setWindowTitle(f"Download Missing Tracks - {self.playlist.name}") self.setWindowTitle(f"Download Missing Tracks - {self.playlist.name}")
self.resize(1200, 900) self.resize(1200, 900)
self.setModal(True) self.setWindowFlags(Qt.WindowType.Window)
self.setWindowFlags(Qt.WindowType.Dialog) self.setWindowFlags(Qt.WindowType.Dialog)
self.setStyleSheet(""" self.setStyleSheet("""
@ -2811,6 +2829,7 @@ class DownloadMissingTracksModal(QDialog):
self.analysis_progress.setVisible(True) self.analysis_progress.setVisible(True)
self.analysis_progress.setMaximum(self.total_tracks) self.analysis_progress.setMaximum(self.total_tracks)
self.analysis_progress.setValue(0) self.analysis_progress.setValue(0)
self.download_in_progress = True # Set flag
self.start_plex_analysis() self.start_plex_analysis()
def start_plex_analysis(self): def start_plex_analysis(self):
@ -3141,10 +3160,20 @@ class DownloadMissingTracksModal(QDialog):
print("🛑 All operations cancelled successfully") print("🛑 All operations cancelled successfully")
def closeEvent(self, event): def closeEvent(self, event):
"""Handle modal close event""" """
self.download_status_timer.stop() Override close event. If the user clicks the 'X', we just hide the window.
The window is only truly closed (and destroyed) when the process is finished
or explicitly cancelled.
"""
if self.cancel_requested or not self.download_in_progress:
# If cancelled or finished, let it close for real.
self.cancel_operations() self.cancel_operations()
self.process_finished.emit()
event.accept() event.accept()
else:
# If downloads are running, just hide the window.
self.hide()
event.ignore()
# Inner class for the search worker # Inner class for the search worker
class ParallelSearchWorker(QRunnable): class ParallelSearchWorker(QRunnable):