Add configurable concurrent downloads setting

- New "Concurrent Downloads" dropdown on Settings page (1-10, default 3)
- Saved to download_source.max_concurrent config key
- All 6 batch creation sites use configured value instead of hardcoded 3
- Soulseek-only album downloads still use 1 worker (source reuse per user)
- Hybrid/YouTube/Tidal/Qobuz/Deezer albums use full configured concurrency
pull/253/head
Broque Thomas 1 month ago
parent b8870e7310
commit 9bcff9b43d

@ -1896,6 +1896,23 @@ download_batches = {} # batch_id -> {queue, active_count, max_concurrent}
tasks_lock = threading.Lock()
batch_locks = {} # batch_id -> Lock() for atomic batch operations
def _get_max_concurrent():
"""Get configured max concurrent downloads. Default 3."""
return config_manager.get('download_source.max_concurrent', 3)
def _get_batch_max_concurrent(is_album=False, source=None):
"""Get max concurrent workers for a batch.
Soulseek album downloads always use 1 (source reuse per user).
Everything else uses the configured setting."""
if is_album and source in ('soulseek', None):
# Check if active source is soulseek
if source == 'soulseek':
return 1
mode = config_manager.get('download_source.mode', 'soulseek')
if mode == 'soulseek':
return 1
return _get_max_concurrent()
# --- Session Download Statistics ---
# Track individual download completions (matches dashboard.py behavior)
session_completed_downloads = 0
@ -21794,7 +21811,7 @@ def _process_wishlist_automatically(automation_id=None):
'playlist_name': playlist_name,
'queue': [],
'active_count': 0,
'max_concurrent': 1 if current_cycle == 'albums' else 3, # 1 worker for album source reuse, 3 for singles
'max_concurrent': _get_batch_max_concurrent(is_album=(current_cycle == 'albums')),
'queue_index': 0,
'analysis_total': len(wishlist_tracks),
'analysis_processed': 0,
@ -22692,7 +22709,7 @@ def start_wishlist_missing_downloads():
'playlist_name': playlist_name,
'queue': task_queue,
'active_count': 0,
'max_concurrent': 1 if category == 'albums' else 3, # 1 worker for album source reuse, 3 for singles
'max_concurrent': _get_batch_max_concurrent(is_album=(category == 'albums')),
'queue_index': 0,
'analysis_total': len(wishlist_tracks),
'analysis_processed': 0,
@ -27134,7 +27151,7 @@ def start_playlist_missing_downloads(playlist_id):
download_batches[batch_id] = {
'queue': [],
'active_count': 0,
'max_concurrent': 3,
'max_concurrent': _get_max_concurrent(),
'queue_index': 0,
# Track state management (replicating sync.py)
'permanently_failed_tracks': [],
@ -29131,7 +29148,7 @@ def start_missing_tracks_process(playlist_id):
'playlist_name': playlist_name,
'queue': [],
'active_count': 0,
'max_concurrent': 1 if is_album_download else 3, # Album/EP: 1 worker for source reuse; Playlist: 3 workers
'max_concurrent': _get_batch_max_concurrent(is_album=is_album_download),
# Track state management (replicating sync.py)
'permanently_failed_tracks': [],
'cancelled_tracks': set(),
@ -29229,7 +29246,7 @@ def start_missing_downloads():
download_batches[batch_id] = {
'queue': [],
'active_count': 0,
'max_concurrent': 3,
'max_concurrent': _get_max_concurrent(),
'queue_index': 0,
# Track state management (replicating sync.py)
'permanently_failed_tracks': [],

@ -4245,6 +4245,24 @@
</div>
</div>
<div class="form-group">
<label>Concurrent Downloads:</label>
<select id="max-concurrent-downloads" class="form-select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3" selected>3 (Default)</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="8">8</option>
<option value="10">10</option>
</select>
<div class="setting-help-text">
Maximum simultaneous downloads per batch. Soulseek album downloads always use 1 worker
due to per-user upload limits. Higher values speed up large playlists and wishlists.
</div>
</div>
<!-- Hybrid Mode Settings (shown only when hybrid is selected) -->
<div id="hybrid-settings-container" style="display: none;">
<div class="setting-help-text">

@ -5781,6 +5781,7 @@ async function loadSettingsData() {
// Populate Download Source settings
document.getElementById('download-source-mode').value = settings.download_source?.mode || 'soulseek';
document.getElementById('stream-source').value = settings.download_source?.stream_source || 'youtube';
document.getElementById('max-concurrent-downloads').value = settings.download_source?.max_concurrent || '3';
loadHybridSourceOrder(settings);
document.getElementById('tidal-download-quality').value = settings.tidal_download?.quality || 'lossless';
document.getElementById('tidal-allow-fallback').checked = settings.tidal_download?.allow_fallback !== false;
@ -6814,6 +6815,7 @@ async function saveSettings(quiet = false) {
hybrid_secondary: document.getElementById('hybrid-secondary-source').value,
hybrid_order: getHybridOrder(),
stream_source: document.getElementById('stream-source').value,
max_concurrent: parseInt(document.getElementById('max-concurrent-downloads').value) || 3,
},
tidal_download: {
quality: document.getElementById('tidal-download-quality').value || 'lossless',

Loading…
Cancel
Save