good
This commit is contained in:
parent
6285ef6615
commit
0f74043512
4 changed files with 17772 additions and 70 deletions
|
|
@ -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
17673
logs/app.log
File diff suppressed because it is too large
Load diff
Binary file not shown.
153
ui/pages/sync.py
153
ui/pages/sync.py
|
|
@ -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.
|
||||||
self.cancel_operations()
|
The window is only truly closed (and destroyed) when the process is finished
|
||||||
event.accept()
|
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.process_finished.emit()
|
||||||
|
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):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue