Configurable Jellyfin API timeout and retries
Add a configurable API timeout for Jellyfin bulk requests and improve fetch retry behavior. UI: adds an "API Timeout (seconds)" field (15–300s, default 30) in webui/index.html and persists it via webui/static/script.js (load/save). Client: jellyfin_client.py now reads api_timeout from config_manager, uses it as the bulk timeout and computes a sensible non-bulk timeout (max(5, bulk_timeout//6)). Fetch loops for tracks and albums were hardened: reducing batch size now resets consecutive failure counters, log messages were clarified, and the stopping/retry thresholds were adjusted to avoid premature aborts at minimum batch sizes.
This commit is contained in:
parent
3893d75ad0
commit
70156bdb98
3 changed files with 34 additions and 19 deletions
|
|
@ -444,16 +444,18 @@ class JellyfinClient:
|
|||
"""Make authenticated request to Jellyfin API"""
|
||||
if not self.base_url or not self.api_key:
|
||||
return None
|
||||
|
||||
|
||||
url = f"{self.base_url}{endpoint}"
|
||||
headers = {
|
||||
'X-Emby-Token': self.api_key,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
# Use longer timeout for bulk operations (lots of data)
|
||||
|
||||
# Use configurable timeout for bulk operations (lots of data)
|
||||
is_bulk_operation = params and params.get('Limit', 0) > 1000
|
||||
timeout = 30 if is_bulk_operation else 5
|
||||
config = config_manager.get_jellyfin_config()
|
||||
bulk_timeout = int(config.get('api_timeout', 30))
|
||||
timeout = bulk_timeout if is_bulk_operation else max(5, bulk_timeout // 6)
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=headers, params=params, timeout=timeout)
|
||||
|
|
@ -505,17 +507,18 @@ class JellyfinClient:
|
|||
|
||||
if not response:
|
||||
consecutive_failures += 1
|
||||
if consecutive_failures >= 3:
|
||||
logger.warning("🚨 Multiple track fetch failures - stopping")
|
||||
break
|
||||
|
||||
if limit > 1000:
|
||||
limit = limit // 2
|
||||
logger.warning(f"⚠️ Track fetch timeout - reducing batch size to {limit}")
|
||||
consecutive_failures = 0 # Reset — give the smaller batch a fair chance
|
||||
logger.warning(f"⚠️ Track fetch failed - reducing batch size to {limit}")
|
||||
continue
|
||||
else:
|
||||
elif consecutive_failures >= 2:
|
||||
logger.warning("🚨 Multiple track fetch failures at minimum batch size - stopping")
|
||||
break
|
||||
|
||||
else:
|
||||
logger.warning("⚠️ Track fetch failed at minimum batch size - retrying once")
|
||||
continue
|
||||
|
||||
consecutive_failures = 0
|
||||
batch_tracks = response.get('Items', [])
|
||||
if not batch_tracks:
|
||||
|
|
@ -568,17 +571,18 @@ class JellyfinClient:
|
|||
|
||||
if not response:
|
||||
consecutive_failures += 1
|
||||
if consecutive_failures >= 3:
|
||||
logger.warning("🚨 Multiple album fetch failures - stopping")
|
||||
break
|
||||
|
||||
if limit > 1000:
|
||||
limit = limit // 2
|
||||
logger.warning(f"⚠️ Album fetch timeout - reducing batch size to {limit}")
|
||||
consecutive_failures = 0 # Reset — give the smaller batch a fair chance
|
||||
logger.warning(f"⚠️ Album fetch failed - reducing batch size to {limit}")
|
||||
continue
|
||||
else:
|
||||
elif consecutive_failures >= 2:
|
||||
logger.warning("🚨 Multiple album fetch failures at minimum batch size - stopping")
|
||||
break
|
||||
|
||||
else:
|
||||
logger.warning("⚠️ Album fetch failed at minimum batch size - retrying once")
|
||||
continue
|
||||
|
||||
consecutive_failures = 0
|
||||
batch_albums = response.get('Items', [])
|
||||
if not batch_albums:
|
||||
|
|
|
|||
|
|
@ -2945,6 +2945,15 @@
|
|||
<button class="test-button" onclick="testConnection('jellyfin')">Test</button>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>API Timeout (seconds):</label>
|
||||
<input type="number" id="jellyfin-timeout" placeholder="30" min="15"
|
||||
max="300" value="30">
|
||||
<small style="color: #888; display: block; margin-top: 5px;">Timeout for
|
||||
bulk API requests during database sync (15-300s). Increase if your
|
||||
Jellyfin server is slow to respond.</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group" id="jellyfin-user-selector-container"
|
||||
style="display: none;">
|
||||
<label>Jellyfin User</label>
|
||||
|
|
|
|||
|
|
@ -1787,6 +1787,7 @@ async function loadSettingsData() {
|
|||
// Populate Jellyfin settings
|
||||
document.getElementById('jellyfin-url').value = settings.jellyfin?.base_url || '';
|
||||
document.getElementById('jellyfin-api-key').value = settings.jellyfin?.api_key || '';
|
||||
document.getElementById('jellyfin-timeout').value = settings.jellyfin?.api_timeout || 30;
|
||||
|
||||
// Populate Navidrome settings
|
||||
document.getElementById('navidrome-url').value = settings.navidrome?.base_url || '';
|
||||
|
|
@ -2456,7 +2457,8 @@ async function saveSettings(quiet = false) {
|
|||
},
|
||||
jellyfin: {
|
||||
base_url: document.getElementById('jellyfin-url').value,
|
||||
api_key: document.getElementById('jellyfin-api-key').value
|
||||
api_key: document.getElementById('jellyfin-api-key').value,
|
||||
api_timeout: parseInt(document.getElementById('jellyfin-timeout').value) || 30
|
||||
},
|
||||
navidrome: {
|
||||
base_url: document.getElementById('navidrome-url').value,
|
||||
|
|
|
|||
Loading…
Reference in a new issue