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"""
|
"""Make authenticated request to Jellyfin API"""
|
||||||
if not self.base_url or not self.api_key:
|
if not self.base_url or not self.api_key:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
url = f"{self.base_url}{endpoint}"
|
url = f"{self.base_url}{endpoint}"
|
||||||
headers = {
|
headers = {
|
||||||
'X-Emby-Token': self.api_key,
|
'X-Emby-Token': self.api_key,
|
||||||
'Content-Type': 'application/json'
|
'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
|
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:
|
try:
|
||||||
response = requests.get(url, headers=headers, params=params, timeout=timeout)
|
response = requests.get(url, headers=headers, params=params, timeout=timeout)
|
||||||
|
|
@ -505,17 +507,18 @@ class JellyfinClient:
|
||||||
|
|
||||||
if not response:
|
if not response:
|
||||||
consecutive_failures += 1
|
consecutive_failures += 1
|
||||||
if consecutive_failures >= 3:
|
|
||||||
logger.warning("🚨 Multiple track fetch failures - stopping")
|
|
||||||
break
|
|
||||||
|
|
||||||
if limit > 1000:
|
if limit > 1000:
|
||||||
limit = limit // 2
|
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
|
continue
|
||||||
else:
|
elif consecutive_failures >= 2:
|
||||||
|
logger.warning("🚨 Multiple track fetch failures at minimum batch size - stopping")
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
logger.warning("⚠️ Track fetch failed at minimum batch size - retrying once")
|
||||||
|
continue
|
||||||
|
|
||||||
consecutive_failures = 0
|
consecutive_failures = 0
|
||||||
batch_tracks = response.get('Items', [])
|
batch_tracks = response.get('Items', [])
|
||||||
if not batch_tracks:
|
if not batch_tracks:
|
||||||
|
|
@ -568,17 +571,18 @@ class JellyfinClient:
|
||||||
|
|
||||||
if not response:
|
if not response:
|
||||||
consecutive_failures += 1
|
consecutive_failures += 1
|
||||||
if consecutive_failures >= 3:
|
|
||||||
logger.warning("🚨 Multiple album fetch failures - stopping")
|
|
||||||
break
|
|
||||||
|
|
||||||
if limit > 1000:
|
if limit > 1000:
|
||||||
limit = limit // 2
|
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
|
continue
|
||||||
else:
|
elif consecutive_failures >= 2:
|
||||||
|
logger.warning("🚨 Multiple album fetch failures at minimum batch size - stopping")
|
||||||
break
|
break
|
||||||
|
else:
|
||||||
|
logger.warning("⚠️ Album fetch failed at minimum batch size - retrying once")
|
||||||
|
continue
|
||||||
|
|
||||||
consecutive_failures = 0
|
consecutive_failures = 0
|
||||||
batch_albums = response.get('Items', [])
|
batch_albums = response.get('Items', [])
|
||||||
if not batch_albums:
|
if not batch_albums:
|
||||||
|
|
|
||||||
|
|
@ -2945,6 +2945,15 @@
|
||||||
<button class="test-button" onclick="testConnection('jellyfin')">Test</button>
|
<button class="test-button" onclick="testConnection('jellyfin')">Test</button>
|
||||||
</div>
|
</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"
|
<div class="form-group" id="jellyfin-user-selector-container"
|
||||||
style="display: none;">
|
style="display: none;">
|
||||||
<label>Jellyfin User</label>
|
<label>Jellyfin User</label>
|
||||||
|
|
|
||||||
|
|
@ -1787,6 +1787,7 @@ async function loadSettingsData() {
|
||||||
// Populate Jellyfin settings
|
// Populate Jellyfin settings
|
||||||
document.getElementById('jellyfin-url').value = settings.jellyfin?.base_url || '';
|
document.getElementById('jellyfin-url').value = settings.jellyfin?.base_url || '';
|
||||||
document.getElementById('jellyfin-api-key').value = settings.jellyfin?.api_key || '';
|
document.getElementById('jellyfin-api-key').value = settings.jellyfin?.api_key || '';
|
||||||
|
document.getElementById('jellyfin-timeout').value = settings.jellyfin?.api_timeout || 30;
|
||||||
|
|
||||||
// Populate Navidrome settings
|
// Populate Navidrome settings
|
||||||
document.getElementById('navidrome-url').value = settings.navidrome?.base_url || '';
|
document.getElementById('navidrome-url').value = settings.navidrome?.base_url || '';
|
||||||
|
|
@ -2456,7 +2457,8 @@ async function saveSettings(quiet = false) {
|
||||||
},
|
},
|
||||||
jellyfin: {
|
jellyfin: {
|
||||||
base_url: document.getElementById('jellyfin-url').value,
|
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: {
|
navidrome: {
|
||||||
base_url: document.getElementById('navidrome-url').value,
|
base_url: document.getElementById('navidrome-url').value,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue