feat: add background library sync thread with configurable interval to eliminate browser dependency for automatic scanning

Add SYNC_INTERVAL_SECONDS environment variable with default 1800 (30 minutes). Add library_cache global dict with rows/series/updated_at/error/ready fields and library_cache_lock for thread-safe access. Extract refresh_library_cache() function to update Radarr/Sonarr data with error handling that preserves last good snapshot during outages. Add background_library_sync() daemon
This commit is contained in:
2026-08-25 20:11:38 +02:00
parent 7313eb96c3
commit 952797005f
4 changed files with 62 additions and 14 deletions
+1 -1
View File
@@ -101,7 +101,7 @@ document.querySelector('#rescan').onclick=async()=>{const b=document.querySelect
</script>
<script>
const scanLabel=document.createElement('label');scanLabel.htmlFor='autoScan';scanLabel.textContent='Automatisches Scanning';const scanHelp=document.createElement('span');scanHelp.className='setting-help';scanHelp.textContent='Bibliotheken werden automatisch geprüft, solange das Dashboard geöffnet ist.';const scanSelect=document.createElement('select');scanSelect.id='autoScan';scanSelect.className='setting-select';[['0','Deaktiviert'],['1800000','Alle 30 Minuten'],['3600000','Jede Stunde'],['7200000','Alle 2 Stunden'],['10800000','Alle 3 Stunden'],['14400000','Alle 4 Stunden'],['18000000','Alle 5 Stunden']].forEach(([value,label])=>{const option=document.createElement('option');option.value=value;option.textContent=label;scanSelect.appendChild(option)});const settingsActions=document.querySelector('.settings-actions');if(settingsActions){settingsActions.before(scanLabel,scanHelp,scanSelect)}
let autoScanTimer=null;function configureAutoScan(){if(autoScanTimer)clearInterval(autoScanTimer);const interval=Number(localStorage.getItem('auto-scan-interval')||0);if(!interval)return;autoScanTimer=setInterval(()=>{['radarr','sonarr'].forEach(source=>{if(document.querySelector(`#${source}`))fetch(`/api/rescan?source=${source}`,{method:'POST'})})},interval)}function saveAutoScan(){localStorage.setItem('auto-scan-interval',scanSelect.value);configureAutoScan()}scanSelect.value=localStorage.getItem('auto-scan-interval')||'0';configureAutoScan();document.querySelector('#settingsSave').addEventListener('click',saveAutoScan);
function configureAutoScan(){}function saveAutoScan(){localStorage.setItem('auto-scan-interval',scanSelect.value)}scanSelect.value=localStorage.getItem('auto-scan-interval')||'0';document.querySelector('#settingsSave').addEventListener('click',saveAutoScan);
const seasonZeroLabel=document.createElement('label');seasonZeroLabel.className='setting-toggle';seasonZeroLabel.htmlFor='showSeasonZero';seasonZeroLabel.innerHTML='<input type="checkbox" id="showSeasonZero"><span>Staffel 0 anzeigen</span>';const seasonZeroHelp=document.createElement('span');seasonZeroHelp.className='setting-help';seasonZeroHelp.textContent='Gilt für Sonarr und die Liste Ohne Deutsch.';if(settingsActions){settingsActions.before(seasonZeroLabel,seasonZeroHelp)}
function applySeasonZeroPreference(){const show=localStorage.getItem('show-season-zero')==='true';const rows=[...document.querySelectorAll('#sonarr tbody tr:not(.season-divider),#missing .missing-series tbody tr:not(.season-divider)')];rows.forEach(row=>{const title=row.querySelector('.title')?.textContent.trim()||'';row.classList.toggle('season-zero-hidden',!show&&/^S00E/i.test(title))});document.querySelectorAll('#sonarr details.series,#missing details.missing-series').forEach(detail=>{const episodeRows=[...detail.querySelectorAll('tbody tr:not(.season-divider)')];detail.classList.toggle('season-zero-empty',episodeRows.length>0&&episodeRows.every(row=>row.classList.contains('season-zero-hidden')))});const missingCount=document.querySelector('#missingCount'),missingNavCount=document.querySelector('#missingNavCount');if(missingCount||missingNavCount){const movies=document.querySelectorAll('#missingMovies tbody tr:not(.hidden)').length,episodes=document.querySelectorAll('#missing .missing-series tbody tr:not(.season-divider):not(.season-zero-hidden)').length,total=movies+episodes;if(missingCount)missingCount.textContent=`${total} Einträge ohne Deutsch`;if(missingNavCount)missingNavCount.textContent=total}const hint=document.querySelector('#seasonZeroHint');if(hint)hint.textContent=show?'Vorhanden oder wird gerade geladen · Staffel 0 eingeblendet':'Vorhanden oder wird gerade geladen · Staffel 0 ausgeblendet'}const seasonZeroToggle=document.querySelector('#showSeasonZero');if(seasonZeroToggle){seasonZeroToggle.checked=localStorage.getItem('show-season-zero')==='true';seasonZeroToggle.addEventListener('change',()=>{localStorage.setItem('show-season-zero',seasonZeroToggle.checked?'true':'false');applySeasonZeroPreference()})}applySeasonZeroPreference();
function syncDashboardMissingCounter(){const visibleMissing=document.querySelectorAll('#missingMovies tbody tr:not(.hidden),#missing .missing-series tbody tr:not(.season-zero-hidden):not(.season-divider)').length;const dashboardMissing=document.querySelector('#dashboardMissing');if(dashboardMissing)dashboardMissing.textContent=visibleMissing}syncDashboardMissingCounter();document.querySelector('#showSeasonZero')?.addEventListener('change',()=>setTimeout(syncDashboardMissingCounter,0));