17 KiB
Download Tracking Analysis - newMusic Application
Overview
This document provides a comprehensive analysis of how the newMusic application accurately tracks downloads through the slskd API. The system implements sophisticated polling, status resolution, and queue management to provide real-time download status updates while maintaining UI responsiveness.
Architecture Overview
The download tracking system follows a multi-layered architecture:
- API Layer:
SoulseekClientinterfaces with slskd daemon - Worker Layer: Background threads handle expensive status processing
- UI Layer:
DownloadsPageandSyncPlaylistModalprovide user interfaces - Queue Management: Active and finished download queue management
Core Data Models
DownloadStatus (/core/soulseek_client.py:189)
@dataclass
class DownloadStatus:
id: str # Unique download identifier from slskd
filename: str # Full path of the downloading file
username: str # Soulseek user providing the file
state: str # Current slskd state (e.g., "InProgress", "Completed")
progress: float # Download progress percentage (0.0-100.0)
size: int # Total file size in bytes
transferred: int # Bytes transferred so far
speed: int # Average download speed
time_remaining: Optional[int] = None # Estimated time remaining
Primary API Functions
SoulseekClient.get_all_downloads() (/core/soulseek_client.py:789)
Purpose: Retrieves all active downloads from slskd API
Input: None
Output: List[DownloadStatus]
Process:
- Makes GET request to
/api/v0/transfers/downloads - Parses nested response structure:
[{"username": "user", "directories": [{"files": [...]}]}] - Extracts progress from state strings or
progressfield - Creates
DownloadStatusobjects for each file
Key Implementation:
async def get_all_downloads(self) -> List[DownloadStatus]:
response = await self._make_request('GET', 'transfers/downloads')
downloads = []
for user_data in response:
username = user_data.get('username', '')
directories = user_data.get('directories', [])
for directory in directories:
files = directory.get('files', [])
for file_data in files:
# Parse progress
progress = 0.0
if file_data.get('state', '').lower().startswith('completed'):
progress = 100.0
elif 'progress' in file_data:
progress = float(file_data.get('progress', 0.0))
status = DownloadStatus(
id=file_data.get('id', ''),
filename=file_data.get('filename', ''),
username=username,
state=file_data.get('state', ''),
progress=progress,
size=file_data.get('size', 0),
transferred=file_data.get('bytesTransferred', 0),
speed=file_data.get('averageSpeed', 0),
time_remaining=file_data.get('timeRemaining')
)
downloads.append(status)
SoulseekClient.get_download_status() (/core/soulseek_client.py:764)
Purpose: Retrieves status for a specific download by ID
Input: download_id: str
Output: Optional[DownloadStatus]
Process:
- Makes GET request to
/api/v0/transfers/downloads/{download_id} - Creates single
DownloadStatusobject - Returns
Noneif download not found
SoulseekClient.clear_all_completed_downloads() (/core/soulseek_client.py:910)
Purpose: Removes all completed downloads from slskd backend
Input: None
Output: bool (success/failure)
Process:
- Makes DELETE request to
/api/v0/transfers/downloads/all/completed - Clears downloads with "Completed", "Cancelled", or "Failed" status
- Used for backend cleanup to prevent API response bloat
Downloads Page Status Tracking
Main Status Update Function (/ui/pages/downloads.py:9504)
Function: update_download_status()
Purpose: Primary status update coordinator for the downloads page
Input: None (uses instance state)
Output: None (triggers UI updates via signals)
Process:
- Checks if status update is already running (prevents concurrent updates)
- Filters for active downloads only
- Creates
StatusProcessingWorkerwith current download items - Connects worker completion signal to
_handle_processed_status_updates() - Starts worker in thread pool
Key Implementation:
def update_download_status(self):
if self._is_status_update_running or not self.soulseek_client:
return
active_items = [item for item in self.download_queue.active_queue.download_items]
if not active_items:
self._is_status_update_running = False
return
self._is_status_update_running = True
worker = StatusProcessingWorker(
soulseek_client=self.soulseek_client,
download_items=active_items
)
worker.signals.completed.connect(self._handle_processed_status_updates)
worker.signals.error.connect(lambda e: print(f"Status Worker Error: {e}"))
self.status_processing_pool.start(worker)
Enhanced Status Update Function (/ui/pages/downloads.py:9206)
Function: update_download_status_v2()
Purpose: Optimized version with adaptive polling and thread safety
Input: None
Output: None
Key Improvements:
- Thread-safe access to download items
- Adaptive polling frequency based on download activity
- Enhanced transfer matching with duplicate prevention
- Improved error handling and state consistency
Background Status Processing Worker (/ui/pages/downloads.py:119)
Class: StatusProcessingWorker
Purpose: Performs expensive status processing in background thread
Input:
soulseek_client: API client instancedownload_items: List of download items to check
Output: Emits completed signal with list of status update results
Process:
- Creates async event loop in background thread
- Calls
soulseek_client._make_request('GET', 'transfers/downloads') - Flattens nested transfer data structure
- Matches downloads by ID, falls back to filename matching
- Determines new status based on slskd state
- Returns structured results for main thread processing
Key Status Mapping:
# Terminal states checked first (critical for correct status determination)
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'
Status Update Result Processing (/ui/pages/downloads.py:9327)
Function: _handle_processed_status_updates()
Purpose: Applies background worker results to UI on main thread
Input: results: List[dict] - Status update results from worker
Output: None (updates UI state)
Process:
- Iterates through results from background worker
- Finds corresponding download items by widget ID
- Updates download item status and progress
- Handles queue transitions for completed downloads
- Updates tab counts and progress indicators
Sync Page Status Tracking
Sync Status Polling (/ui/pages/sync.py:4099)
Function: poll_all_download_statuses()
Purpose: Status update coordinator for sync playlist modal
Input: None (uses self.active_downloads)
Output: None
Process:
- Creates snapshot of active download data for thread safety
- Filters downloads that have valid slskd results
- Creates
SyncStatusProcessingWorkerwith download data - Starts worker in dedicated thread pool
Key Implementation:
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)
})
worker = SyncStatusProcessingWorker(
self.parent_page.soulseek_client,
items_to_check
)
worker.signals.completed.connect(self._handle_processed_status_updates)
self.download_status_pool.start(worker)
Sync Background Status Worker (/ui/pages/sync.py:348)
Class: SyncStatusProcessingWorker
Purpose: Background status processing specific to sync modal
Input:
soulseek_client: API clientdownload_items_data: List of download data snapshots
Output: Emits results with status updates
Key Features:
- Enhanced transfer data parsing (handles both nested and flat structures)
- Grace period for missing downloads (3 polls before marking as failed)
- Automatic download ID correction via filename matching
- Comprehensive error state detection
Enhanced Transfer Parsing:
# Handles multiple response formats from slskd API
all_transfers = []
for user_data in transfers_data:
# Check for files directly under user object
if 'files' in user_data and isinstance(user_data['files'], list):
all_transfers.extend(user_data['files'])
# Also check for files nested inside directories
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'])
Sync Status Result Processing (/ui/pages/sync.py:4138)
Function: _handle_processed_status_updates()
Purpose: Processes sync worker results and triggers appropriate actions
Input: results: List[dict] - Worker results
Output: None
Process:
- Creates lookup map for active downloads
- Updates download IDs when corrected by filename matching
- Handles terminal states (completed, failed, cancelled)
- Manages retry logic for failed downloads
- Updates missing count tracking for grace period logic
Status Polling and Timers
Downloads Page Timer Setup (/ui/pages/downloads.py:4863)
self.download_status_timer = QTimer()
self.download_status_timer.timeout.connect(self.update_download_status)
self.download_status_timer.start(1000) # Poll every 1 second
Sync Page Timer Setup (/ui/pages/sync.py:3529)
self.download_status_timer = QTimer(self)
self.download_status_timer.timeout.connect(self.poll_all_download_statuses)
self.download_status_timer.start(2000) # Poll every 2 seconds
Adaptive Polling (/ui/pages/downloads.py:9183)
Function: _update_adaptive_polling()
Purpose: Adjusts polling frequency based on download activity
Logic:
- Active downloads present: 500ms intervals
- No active downloads: 5000ms intervals
- Optimizes performance by reducing unnecessary API calls
Download Matching Strategies
Primary: ID-Based Matching
The system primarily matches downloads using the unique ID assigned by slskd:
# Direct ID lookup
matching_transfer = transfers_by_id.get(item_data['download_id'])
Fallback: Filename-Based Matching
When ID matching fails, the system falls back to filename comparison:
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
Enhanced Matching in V2 (/ui/pages/downloads.py:9278)
Function: _find_matching_transfer_v2()
Features:
- Prevents duplicate matches across multiple downloads
- Tracks already-matched transfer IDs
- Maintains match consistency across polling cycles
Grace Period and Missing Download Handling
Missing Download Grace Period
The system implements a 3-poll grace period before marking downloads as failed:
# Grace period logic
item_data['api_missing_count'] = item_data.get('api_missing_count', 0) + 1
if item_data['api_missing_count'] >= 3:
print(f"❌ Download failed (missing from API after 3 checks): {expected_filename}")
payload = {'widget_id': item_data['widget_id'], 'status': 'failed'}
Purpose: Handles temporary API inconsistencies and network issues
Queue Management and State Transitions
Download Queue Structure
The system maintains separate queues:
- Active Queue: Currently downloading or queued items
- Finished Queue: Completed, cancelled, or failed downloads
State Transition Function (/ui/pages/downloads.py:315)
Function: atomic_state_transition()
Purpose: Thread-safe status updates with callback support
Input:
download_item: Item to updatenew_status: Target statuscallback: Optional callback function
Process:
- Captures old status
- Updates item status atomically
- Calls callback with old/new status if provided
Queue Movement Logic
Downloads transition between queues based on status:
# Terminal states move to finished queue
if new_status in ['completed', 'cancelled', 'failed']:
self.download_queue.move_to_finished(download_item)
self.download_queue.active_queue.remove_item(download_item)
Backend Cleanup System
Periodic Cleanup (/ui/pages/downloads.py:9534)
Function: _periodic_cleanup_check()
Purpose: Prevents slskd backend from accumulating completed downloads
Process:
- Identifies downloads needing cleanup from previous polling cycle
- Performs bulk cleanup for standard completed downloads
- Handles individual cleanup for errored downloads
- Prepares cleanup list for next cycle
Cleanup Categories
Bulk Cleanup States:
- 'Completed, Succeeded'
- 'Completed, Cancelled'
- 'Cancelled'
- 'Canceled'
Individual Cleanup States:
- 'Completed, Errored'
- 'Failed'
- 'Errored'
Backend Cleanup Execution (/ui/pages/downloads.py:9617)
Function: _cleanup_backend_downloads()
Process:
- Runs in background thread to avoid UI blocking
- Calls
soulseek_client.clear_all_completed_downloads() - Logs cleanup results
- Handles cleanup failures gracefully
Error Handling and Resilience
Connection Failure Handling
All API functions include comprehensive error handling:
try:
response = await self._make_request('GET', 'transfers/downloads')
if not response:
return []
# Process response...
except Exception as e:
logger.error(f"Error getting downloads: {e}")
return []
Thread Safety Measures
- Status Update Locks: Prevent concurrent status processing
- Queue Consistency Locks: Ensure atomic queue operations
- Worker Thread Pools: Manage background thread lifecycle
Graceful Degradation
- System continues functioning when API calls fail
- Missing downloads handled with grace period
- UI remains responsive during network issues
Performance Optimizations
Background Threading
All expensive operations run in background threads:
- API calls to slskd
- Status processing and matching
- Backend cleanup operations
Efficient Data Structures
- Transfer lookup dictionaries for O(1) matching
- Set-based duplicate tracking
- Minimal data copying between threads
Adaptive Polling
Polling frequency adjusts based on activity:
- High frequency when downloads active
- Low frequency when idle
- Immediate updates for user actions
Integration Points
Key Function Call Chains
- Timer → Status Update → Worker → Results → UI Update
- Download Start → Queue Add → Status Tracking → Completion → Queue Move
- Periodic Cleanup → Backend Query → Bulk Clear → UI Sync
Critical State Synchronization
- UI Thread: Handles all widget updates
- Background Threads: Perform API operations
- Signal/Slot System: Bridges thread boundaries safely
Summary
The newMusic download tracking system provides robust, real-time status monitoring through:
- Layered Architecture: Clean separation between API, processing, and UI layers
- Background Processing: Non-blocking status updates via worker threads
- Intelligent Matching: ID-based primary matching with filename fallback
- Grace Period Handling: Tolerance for temporary API inconsistencies
- Adaptive Polling: Performance optimization based on download activity
- Comprehensive Cleanup: Prevents backend bloat through periodic maintenance
- Thread Safety: Consistent state management across concurrent operations
This architecture ensures accurate download tracking while maintaining responsive UI performance and handling various edge cases and error conditions gracefully.