Add Soulseek peer queue filtering and configurable download timeout
- Add max_peer_queue setting to skip peers with long queues (soft filter with fallback to unfiltered if all results removed) - Add download_timeout setting replacing hardcoded 10-minute limit - Include quality_score (peer health: upload speed, free slots, queue length) in result ranking — was calculated but never used in sort key - New UI controls in Soulseek settings section
This commit is contained in:
parent
f0270ce7a5
commit
b9c83a50fa
5 changed files with 52 additions and 12 deletions
|
|
@ -360,7 +360,9 @@ class ConfigManager:
|
|||
"slskd_url": "",
|
||||
"api_key": "",
|
||||
"download_path": "./downloads",
|
||||
"transfer_path": "./Transfer"
|
||||
"transfer_path": "./Transfer",
|
||||
"max_peer_queue": 0,
|
||||
"download_timeout": 600
|
||||
},
|
||||
"download_source": {
|
||||
"mode": "soulseek", # Options: "soulseek", "youtube", "tidal", "qobuz", "hifi", "hybrid"
|
||||
|
|
|
|||
|
|
@ -867,32 +867,45 @@ class MusicMatchingEngine:
|
|||
|
||||
return adjusted_confidence, version_type
|
||||
|
||||
def find_best_slskd_matches_enhanced(self, spotify_track: SpotifyTrack, slskd_results: List[TrackResult]) -> List[TrackResult]:
|
||||
def find_best_slskd_matches_enhanced(self, spotify_track: SpotifyTrack, slskd_results: List[TrackResult],
|
||||
max_peer_queue: int = 0) -> List[TrackResult]:
|
||||
"""
|
||||
Enhanced version of find_best_slskd_matches with version-aware scoring.
|
||||
Returns candidates sorted by adjusted confidence (preferring originals).
|
||||
|
||||
Args:
|
||||
max_peer_queue: Skip peers with queue longer than this (0 = no limit)
|
||||
"""
|
||||
if not slskd_results:
|
||||
return []
|
||||
|
||||
# Apply queue filter if configured
|
||||
if max_peer_queue > 0:
|
||||
filtered = [r for r in slskd_results if r.queue_length <= max_peer_queue]
|
||||
# Fall back to unfiltered if everything got removed (rare files)
|
||||
if filtered:
|
||||
slskd_results = filtered
|
||||
|
||||
scored_results = []
|
||||
for slskd_track in slskd_results:
|
||||
# Use enhanced confidence calculation
|
||||
confidence, version_type = self.calculate_slskd_match_confidence_enhanced(spotify_track, slskd_track)
|
||||
|
||||
|
||||
# Store the adjusted confidence and version info
|
||||
slskd_track.confidence = confidence
|
||||
slskd_track.version_type = getattr(slskd_track, 'version_type', 'original')
|
||||
scored_results.append(slskd_track)
|
||||
|
||||
# Sort by confidence score (descending), then by version preference, then by size
|
||||
# Sort by confidence, version preference, peer quality, then file size
|
||||
def sort_key(r):
|
||||
# Primary: confidence score
|
||||
# Secondary: prefer originals (original=0, others=penalty value for tie-breaking)
|
||||
version_priority = 0.0 if r.version_type == 'original' else getattr(r, 'version_penalty', 0.1)
|
||||
# Tertiary: file size
|
||||
return (r.confidence, -version_priority, r.size)
|
||||
|
||||
# Tertiary: peer quality (upload speed, queue, free slots)
|
||||
peer_quality = r.quality_score
|
||||
# Quaternary: file size
|
||||
return (r.confidence, -version_priority, peer_quality, r.size)
|
||||
|
||||
sorted_results = sorted(scored_results, key=sort_key, reverse=True)
|
||||
|
||||
# Filter out very low-confidence results
|
||||
|
|
|
|||
|
|
@ -6993,7 +6993,8 @@ def stream_enhanced_search_track():
|
|||
logger.info(f"✅ Found {len(tracks_result)} results for query: '{query}'")
|
||||
|
||||
# Use matching engine to find best match
|
||||
best_matches = matching_engine.find_best_slskd_matches_enhanced(temp_track, tracks_result)
|
||||
_max_q = config_manager.get('soulseek.max_peer_queue', 0) or 0
|
||||
best_matches = matching_engine.find_best_slskd_matches_enhanced(temp_track, tracks_result, max_peer_queue=_max_q)
|
||||
|
||||
if best_matches:
|
||||
# Get the first (best) result
|
||||
|
|
@ -20683,7 +20684,8 @@ def get_valid_candidates(results, spotify_track, query):
|
|||
if not results:
|
||||
return []
|
||||
# Uses the existing, powerful matching engine for scoring
|
||||
initial_candidates = matching_engine.find_best_slskd_matches_enhanced(spotify_track, results)
|
||||
_max_q = config_manager.get('soulseek.max_peer_queue', 0) or 0
|
||||
initial_candidates = matching_engine.find_best_slskd_matches_enhanced(spotify_track, results, max_peer_queue=_max_q)
|
||||
if not initial_candidates:
|
||||
return []
|
||||
|
||||
|
|
@ -23318,8 +23320,9 @@ def _build_batch_status_data(batch_id, batch, live_transfers_lookup):
|
|||
task_start_time = task.get('status_change_time', current_time)
|
||||
task_age = current_time - task_start_time
|
||||
|
||||
# If task has been running for more than 10 minutes, check if file completed
|
||||
if task_age > 600 and task['status'] in ['downloading', 'queued', 'searching']:
|
||||
# If task has been running too long, check if file completed
|
||||
_dl_timeout = config_manager.get('soulseek.download_timeout', 600) or 600
|
||||
if task_age > _dl_timeout and task['status'] in ['downloading', 'queued', 'searching']:
|
||||
stuck_state = task['status']
|
||||
task_filename = task.get('filename') or (task.get('track_info') or {}).get('filename')
|
||||
|
||||
|
|
|
|||
|
|
@ -3934,6 +3934,24 @@
|
|||
</select>
|
||||
<small class="settings-hint">Ignore search results from peers with upload speed below this threshold</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Max Peer Queue Length:</label>
|
||||
<select id="soulseek-max-peer-queue">
|
||||
<option value="0">No limit</option>
|
||||
<option value="5">5</option>
|
||||
<option value="10">10</option>
|
||||
<option value="20">20</option>
|
||||
<option value="50">50</option>
|
||||
<option value="100">100</option>
|
||||
</select>
|
||||
<small class="settings-hint">Skip peers with a queue longer than this (prefers peers with shorter queues regardless)</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Download Timeout (minutes):</label>
|
||||
<input type="number" id="soulseek-download-timeout" placeholder="10" min="2"
|
||||
max="60" value="10">
|
||||
<small class="settings-hint">Abandon stuck downloads after this many minutes</small>
|
||||
</div>
|
||||
<div class="form-actions" style="margin-top: 8px;">
|
||||
<button class="test-button" onclick="testConnection('soulseek')">Test Soulseek Connection</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4916,6 +4916,8 @@ async function loadSettingsData() {
|
|||
document.getElementById('soulseek-search-timeout').value = settings.soulseek?.search_timeout || 60;
|
||||
document.getElementById('soulseek-search-timeout-buffer').value = settings.soulseek?.search_timeout_buffer || 15;
|
||||
document.getElementById('soulseek-min-peer-speed').value = settings.soulseek?.min_peer_upload_speed || 0;
|
||||
document.getElementById('soulseek-max-peer-queue').value = settings.soulseek?.max_peer_queue || 0;
|
||||
document.getElementById('soulseek-download-timeout').value = Math.round((settings.soulseek?.download_timeout || 600) / 60);
|
||||
|
||||
// Populate ListenBrainz settings
|
||||
document.getElementById('listenbrainz-base-url').value = settings.listenbrainz?.base_url || '';
|
||||
|
|
@ -5826,7 +5828,9 @@ async function saveSettings(quiet = false) {
|
|||
transfer_path: document.getElementById('transfer-path').value,
|
||||
search_timeout: parseInt(document.getElementById('soulseek-search-timeout').value) || 60,
|
||||
search_timeout_buffer: parseInt(document.getElementById('soulseek-search-timeout-buffer').value) || 15,
|
||||
min_peer_upload_speed: parseInt(document.getElementById('soulseek-min-peer-speed').value) || 0
|
||||
min_peer_upload_speed: parseInt(document.getElementById('soulseek-min-peer-speed').value) || 0,
|
||||
max_peer_queue: parseInt(document.getElementById('soulseek-max-peer-queue').value) || 0,
|
||||
download_timeout: (parseInt(document.getElementById('soulseek-download-timeout').value) || 10) * 60
|
||||
},
|
||||
listenbrainz: {
|
||||
base_url: document.getElementById('listenbrainz-base-url').value,
|
||||
|
|
|
|||
Loading…
Reference in a new issue