pull/2/head
Broque Thomas 10 months ago
parent f2e62217a0
commit 4324fd54da

@ -1 +1 @@
{"access_token": "BQCzY4NDr26JqIBSxwATrH7MQMG5i9kCZxPvvtjcoVTyFZPRYPOEltaZEqN44ukQhNDo6C8fvkhlSPS6tk2N7zVitnBk9bT2IRAQiQGpxRS67kNlUfxUJSw52YAW83vECziyLfUfOkLs-E2LzoJDQtq_QOx18b_wO883mT1C8OyNFjVGvvjV5Srf8oSMJ4KN6WYwMpllfTbno7YlT7R5oPOtjgNquuUOhLHRWWp9KXEermO-MZWtWl_OxBAlhhmi", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752342994, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}
{"access_token": "BQAn7glyksj8Yn1ryBKRgkrU9oH5tycZC3upkwXmsYo-j0dNX_kwuxaQHVO1yd77aIepn4zkVOeHMdmcyWKiAr3UFHsV5BkZGl28W8RyCuK6geFYbc6eoJ1VnOltZcwtjm7_YsxUlVccHJO7Wrwu12Rt1K_hqPcF8Cyl7rj5qwD_il4OdTipejXhGaACJM5dupAKNZLuTipvPn38bx2dqHdQctUspouiU5B1XRhFnc45GQ10nGXHmOgBX_G26pKc", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-read user-read-private playlist-read-private playlist-read-collaborative user-read-email", "expires_at": 1752353180, "refresh_token": "AQDmfQkPCGObfJeTUIbW1hAAwhSqkuHRA3Qh2dqVYMRh0eCkFMQgPNJDDzF8y-BiaVbj80zePkK_XSfYH1aJutMtNbnsqRKWuxP31BTrMc7pdUdbE7Fma4oH8wpDUKdG3MM"}

File diff suppressed because it is too large Load Diff

@ -644,6 +644,7 @@ class StreamingThread(QThread):
streaming_finished = pyqtSignal(str, object) # Message, search_result
streaming_failed = pyqtSignal(str, object) # Error message, search_result
streaming_progress = pyqtSignal(float, object) # Progress percentage (0-100), search_result
streaming_queued = pyqtSignal(str, object) # Queue message, search_result
def __init__(self, soulseek_client, search_result):
super().__init__()
@ -699,31 +700,63 @@ class StreamingThread(QThread):
max_wait_time = 45 # Wait up to 45 seconds
poll_interval = 2 # Check every 2 seconds
# Track elapsed time for fallback progress estimation
start_time = time.time()
estimated_duration = 15.0 # Estimate 15 seconds for typical download
last_progress_sent = 0.0
found_file = None # Initialize found_file outside the loop
# Queue state tracking
queue_start_time = None
queue_timeout = 15.0 # 15 seconds max in queue
last_download_state = None
actively_downloading = False
for wait_count in range(max_wait_time // poll_interval):
if self._stop_requested:
break
# Calculate fallback progress based on elapsed time
elapsed_time = time.time() - start_time
estimated_progress = min(95.0, (elapsed_time / estimated_duration) * 95.0) # Cap at 95%
# Only use real API progress data - no time-based estimation
# Check download progress via slskd API
api_progress = None
download_state = None
try:
download_status = loop.run_until_complete(self._check_download_progress())
# Use the same API call as download queue monitoring for consistency
transfers_data = loop.run_until_complete(self.soulseek_client._make_request('GET', 'transfers/downloads'))
download_status = self._find_streaming_download_in_transfers(transfers_data)
if download_status:
api_progress = download_status.progress
print(f"API Download progress: {api_progress:.1f}%")
api_progress = download_status.get('percentComplete', 0)
download_state = download_status.get('state', '').lower()
print(f"API Download - State: {download_status.get('state')}, Progress: {api_progress:.1f}%")
# Categorize download state (aligned with download queue logic)
original_state = download_status.get('state', '') # Keep original case for completion check
is_queued = any(keyword in download_state for keyword in ['queued', 'initializing', 'remote'])
is_downloading = 'inprogress' in download_state
is_completed = ('Succeeded' in original_state or ('Completed' in original_state and 'Errored' not in original_state)) or api_progress >= 100
# Track queue state timing
if is_queued and queue_start_time is None:
queue_start_time = time.time()
print(f"📋 Download entered queue state: {original_state}")
self.streaming_queued.emit(f"Queuing with uploader...", self.search_result)
elif is_downloading and not actively_downloading:
actively_downloading = True
queue_start_time = None # Reset queue timer
print(f"🚀 Download started actively downloading: {original_state}")
# Emit a progress update to indicate downloading has started
if api_progress > 0:
self.streaming_progress.emit(api_progress, self.search_result)
# Check for queue timeout
if is_queued and queue_start_time:
queue_elapsed = time.time() - queue_start_time
if queue_elapsed > queue_timeout:
print(f"⏰ Queue timeout after {queue_elapsed:.1f}s - download stuck in queue")
self.streaming_failed.emit(f"Queue timeout - uploader not responding. Try another source.", self.search_result)
return
# Check if download is complete
if download_status.state.lower().startswith('completed') or api_progress >= 100:
print(f"✓ Download completed via API status: {download_status.state}")
if is_completed:
print(f"✓ Download completed via API status: {original_state}")
# Try to find the actual file - with retries for file system sync
for retry_count in range(5): # Try up to 5 times with delays
found_file = self._find_downloaded_file(download_path)
@ -735,18 +768,42 @@ class StreamingThread(QThread):
time.sleep(1) # Wait 1 second for file system to sync
if found_file:
break # Exit main polling loop
print(f"✓ Found downloaded file: {found_file}")
# Move the file to Stream folder with original filename
original_filename = os.path.basename(found_file)
stream_path = os.path.join(stream_folder, original_filename)
try:
# Move file to Stream folder
shutil.move(found_file, stream_path)
print(f"✓ Moved file to stream folder: {stream_path}")
# Clean up empty directories left behind
self._cleanup_empty_directories(download_path, found_file)
# Signal that streaming is ready (100% progress)
self.streaming_progress.emit(100.0, self.search_result)
self.streaming_finished.emit(f"Stream ready: {os.path.basename(found_file)}", self.search_result)
self.temp_file_path = stream_path
print(f"✓ Stream file ready for playback: {stream_path}")
break # Exit main polling loop
except Exception as e:
print(f"Error moving file to stream folder: {e}")
self.streaming_failed.emit(f"Failed to prepare stream file: {e}", self.search_result)
break
else:
# Handle progress updates for active downloads
if is_downloading and actively_downloading and api_progress is not None and api_progress > 0:
if api_progress != last_progress_sent:
self.streaming_progress.emit(api_progress, self.search_result)
print(f"Progress update: {api_progress:.1f}% (Real API data)")
last_progress_sent = api_progress
except Exception as e:
print(f"Warning: Could not check download progress: {e}")
# Use API progress if available, otherwise use estimated progress
current_progress = api_progress if api_progress is not None and api_progress > 0 else estimated_progress
# Only emit progress updates if there's a meaningful change (>5%)
if current_progress - last_progress_sent >= 5.0:
self.streaming_progress.emit(current_progress, self.search_result)
print(f"Progress update: {current_progress:.1f}% (API: {api_progress}, Estimated: {estimated_progress:.1f}%)")
last_progress_sent = current_progress
# Continue to next iteration if API call fails
continue
# Search for the downloaded file in the downloads directory
found_file = self._find_downloaded_file(download_path)
@ -785,37 +842,6 @@ class StreamingThread(QThread):
# Timed out waiting for file
print(f"❌ Polling loop completed, timeout reached. found_file = {found_file}")
self.streaming_failed.emit("Stream download timed out - file not found", self.search_result)
# Post-polling file processing
print(f"📋 After polling loop: found_file = {found_file}")
if found_file:
print(f"🎯 Processing found file: {found_file}")
# Move the file to Stream folder with original filename
original_filename = os.path.basename(found_file)
stream_path = os.path.join(stream_folder, original_filename)
try:
# Move file to Stream folder
print(f"📁 Moving file from {found_file} to {stream_path}")
shutil.move(found_file, stream_path)
print(f"✅ Successfully moved file to stream folder: {stream_path}")
# Clean up empty directories left behind
self._cleanup_empty_directories(download_path, found_file)
# Signal that streaming is ready (100% progress)
self.streaming_progress.emit(100.0, self.search_result)
self.streaming_finished.emit(f"Stream ready: {os.path.basename(found_file)}", self.search_result)
self.temp_file_path = stream_path
print(f"🎵 Stream file ready for playback: {stream_path}")
except Exception as e:
print(f"❌ Error moving file to stream folder: {e}")
self.streaming_failed.emit(f"Failed to prepare stream file: {e}", self.search_result)
else:
print(f"❌ No file found after polling loop completed")
self.streaming_failed.emit("Stream download failed - file not found after completion", self.search_result)
else:
self.streaming_failed.emit("Streaming failed to start", self.search_result)
@ -839,31 +865,42 @@ class StreamingThread(QThread):
except Exception as e:
print(f"Error cleaning up streaming event loop: {e}")
async def _check_download_progress(self):
"""Check download progress via slskd API for the current streaming download"""
def _find_streaming_download_in_transfers(self, transfers_data):
"""Find streaming download in transfer data using same logic as download queue"""
try:
# Get all current downloads and find ours by filename match
all_downloads = await self.soulseek_client.get_all_downloads()
print(f"🔍 Looking for download - Target: {self.search_result.username}:{os.path.basename(self.search_result.filename)}")
print(f"🔍 Found {len(all_downloads)} total downloads in API")
if not transfers_data:
return None
# Flatten the transfers data structure (same as download queue logic)
all_transfers = []
for user_data in transfers_data:
if 'directories' in user_data:
for directory in user_data['directories']:
if 'files' in directory:
all_transfers.extend(directory['files'])
# Look for our specific file by filename and username
target_filename = os.path.basename(self.search_result.filename)
target_username = self.search_result.username
for i, download in enumerate(all_downloads):
download_filename = os.path.basename(download.filename)
print(f"📁 Download {i+1}: {download.username}:{download_filename} - State: {download.state} - Progress: {download.progress}%")
print(f"🔍 Looking for streaming download - Target: {target_username}:{target_filename}")
print(f"🔍 Found {len(all_transfers)} total transfers in API")
for i, transfer in enumerate(all_transfers):
transfer_filename = os.path.basename(transfer.get('filename', ''))
transfer_username = transfer.get('username', '')
print(f"📁 Transfer {i+1}: {transfer_username}:{transfer_filename} - State: {transfer.get('state')} - Progress: {transfer.get('percentComplete', 0):.1f}%")
if (download_filename == target_filename and
download.username == target_username):
print(f"✅ Found matching download: {download.state} - {download.progress}%")
return download
if (transfer_filename == target_filename and
transfer_username == target_username):
print(f"✅ Found matching streaming download: {transfer.get('state')} - {transfer.get('percentComplete', 0):.1f}%")
return transfer
print(f"❌ No matching download found for {target_username}:{target_filename}")
print(f"❌ No matching streaming download found for {target_username}:{target_filename}")
return None
except Exception as e:
print(f"Error checking download progress: {e}")
print(f"Error finding streaming download in transfers: {e}")
return None
def _find_downloaded_file(self, download_path):
@ -1070,6 +1107,20 @@ class TrackItem(QFrame):
self.play_btn.setText("")
self.play_btn.setEnabled(False)
def set_queue_state(self):
"""Set play button to queue state"""
self.play_btn.setText("📋")
self.play_btn.setEnabled(False)
self.play_btn.setStyleSheet("""
QPushButton {
background: rgba(255, 165, 0, 0.2);
border: 1px solid rgba(255, 165, 0, 0.4);
border-radius: 18px;
color: rgba(255, 165, 0, 0.8);
font-size: 12px;
}
""")
def set_download_queued_state(self):
"""Set download button to queued state (disabled, shows queued)"""
self.download_btn.setText("")
@ -1927,6 +1978,20 @@ class SearchResultItem(QFrame):
self.play_btn.setText("")
self.play_btn.setEnabled(False)
def set_queue_state(self):
"""Set play button to queue state"""
self.play_btn.setText("📋")
self.play_btn.setEnabled(False)
self.play_btn.setStyleSheet("""
QPushButton {
background: rgba(255, 165, 0, 0.2);
border: 1px solid rgba(255, 165, 0, 0.4);
border-radius: 18px;
color: rgba(255, 165, 0, 0.8);
font-size: 12px;
}
""")
def reset_download_state(self):
"""Reset the download button state"""
self.is_downloading = False
@ -2851,6 +2916,13 @@ class DownloadQueue(QFrame):
self.queue_layout.removeWidget(item)
item.deleteLater()
self.update_queue_count()
# Notify parent download manager to update tab counts
parent_widget = self.parent()
while parent_widget and not hasattr(parent_widget, 'update_tab_counts'):
parent_widget = parent_widget.parent()
if parent_widget and hasattr(parent_widget, 'update_tab_counts'):
parent_widget.update_tab_counts()
def clear_completed_downloads(self):
"""Remove all completed and cancelled download items"""
@ -4547,6 +4619,7 @@ class DownloadsPage(QWidget):
streaming_thread.streaming_started.connect(self.on_streaming_started, Qt.ConnectionType.QueuedConnection)
streaming_thread.streaming_finished.connect(self.on_streaming_finished, Qt.ConnectionType.QueuedConnection)
streaming_thread.streaming_progress.connect(self.on_streaming_progress, Qt.ConnectionType.QueuedConnection)
streaming_thread.streaming_queued.connect(self.on_streaming_queued, Qt.ConnectionType.QueuedConnection)
streaming_thread.streaming_failed.connect(self.on_streaming_failed, Qt.ConnectionType.QueuedConnection)
streaming_thread.finished.connect(
functools.partial(self.on_streaming_thread_finished, streaming_thread),
@ -4676,6 +4749,27 @@ class DownloadsPage(QWidget):
else:
print(f"🚫 Ignoring progress for old streaming result: {search_result.filename}")
def on_streaming_queued(self, queue_msg, search_result):
"""Handle streaming queue state updates"""
print(f"Queue status: {queue_msg} for {search_result.filename}")
# Check if this queue status is for the currently requested track
if hasattr(self, 'current_track_result') and self.current_track_result:
current_track_id = f"{self.current_track_result.username}:{self.current_track_result.filename}"
queued_track_id = f"{search_result.username}:{search_result.filename}"
if current_track_id == queued_track_id:
# Show queue status in button
if self.currently_playing_button:
try:
self.currently_playing_button.set_queue_state()
except RuntimeError:
# Button was deleted, ignore
pass
print(f"📋 Showing queue status for current track")
else:
print(f"🚫 Ignoring queue status for old streaming result: {search_result.filename}")
def on_streaming_failed(self, error_msg, search_result):
"""Handle streaming failure"""
print(f"Streaming failed: {error_msg}")
@ -5203,6 +5297,10 @@ class DownloadsPage(QWidget):
# After processing all download items, check for any that weren't found in the API
# This handles the case where downloads were removed from slskd externally
# Update download counters after processing all transfers
self.download_queue.update_tab_counts()
print(f"[DEBUG] Updated tab counts - Active: {len(self.download_queue.active_queue.download_items)}, Finished: {len(self.download_queue.finished_queue.download_items)}")
except Exception as e:
print(f"[ERROR] Error processing transfer status update: {e}")
import traceback

Loading…
Cancel
Save