feat: run full refresh with EPG rematching
Run full live catalog refreshes through live source sync, complete channel analysis, EPG source refresh, and persisted EPG rematching while keeping check-new incremental. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -891,6 +891,14 @@ h4 {
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.live-analysis-action {
|
||||
display: flex;
|
||||
max-width: 19rem;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--space-xs);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.live-analysis-heading {
|
||||
flex-direction: column;
|
||||
|
||||
@ -490,11 +490,16 @@
|
||||
<div class="setting-hint">Group matching streams into channels and update detected languages.</div>
|
||||
</div>
|
||||
<div class="live-analysis-actions">
|
||||
<button type="button" class="btn btn-primary" id="live-check-new-button">Check for new streams</button>
|
||||
<button type="button" class="btn btn-secondary" id="live-rebuild-catalog-button">Rebuild entire catalog</button>
|
||||
<div class="live-analysis-action">
|
||||
<button type="button" class="btn btn-primary" id="live-check-new-button">Check for new streams</button>
|
||||
<span class="setting-hint">Sync IPTV sources and analyze only streams that are not already in the catalog.</span>
|
||||
</div>
|
||||
<div class="live-analysis-action">
|
||||
<button type="button" class="btn btn-secondary" id="live-rebuild-catalog-button">Run full refresh</button>
|
||||
<span class="setting-hint">Refresh IPTV and EPG data, re-analyze every stream, and rebuild channel-to-EPG matches.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="setting-hint">Check newly added streams, or rebuild the entire catalog if channel matching needs to be refreshed.</p>
|
||||
<div id="live-analysis-status" class="setting-hint" role="status">Checking status…</div>
|
||||
<div id="live-analysis-progress" class="laya-progress" hidden>
|
||||
<div id="live-analysis-progress-track" class="laya-progress-track" role="progressbar" aria-label="Channel analysis progress">
|
||||
|
||||
@ -108,8 +108,8 @@ class SettingsPage {
|
||||
|
||||
initLiveAnalysis() {
|
||||
document.getElementById('live-check-new-button')?.addEventListener('click', () => void this.runLiveAnalysis('check-new'));
|
||||
document.getElementById('live-rebuild-catalog-button')?.addEventListener('click', () => {
|
||||
if (confirm('Rebuild the entire channel catalog? This can take a while.')) void this.runLiveAnalysis('rebuild');
|
||||
document.getElementById('live-rebuild-catalog-button')?.addEventListener('click', async () => {
|
||||
if (await this.confirmFullRefresh()) void this.runLiveAnalysis('rebuild');
|
||||
});
|
||||
// Compatibility with pages cached before the actions were split.
|
||||
document.getElementById('live-analysis-button')?.addEventListener('click', () => void this.runLiveAnalysis('rebuild'));
|
||||
@ -153,6 +153,14 @@ class SettingsPage {
|
||||
|
||||
const presentation = LiveAnalysisPresentation.present(status);
|
||||
buttons.forEach(button => { button.disabled = presentation.busy; });
|
||||
const rebuildButton = document.getElementById('live-rebuild-catalog-button');
|
||||
const fullRefreshPhases = new Set([
|
||||
'refreshing_live_sources', 'refreshing_epg_sources', 'matching_epg',
|
||||
'laya_epg_matching', 'writing_epg_matches'
|
||||
]);
|
||||
const fullRefreshBusy = presentation.busy &&
|
||||
(this.currentLiveAnalysisAction === 'rebuild' || fullRefreshPhases.has(presentation.phase));
|
||||
if (rebuildButton) rebuildButton.textContent = fullRefreshBusy ? 'Refreshing…' : 'Run full refresh';
|
||||
statusElement.textContent = presentation.statusLabel;
|
||||
progress.hidden = !presentation.busy;
|
||||
track.classList.toggle('indeterminate', presentation.progressIndeterminate);
|
||||
@ -181,6 +189,7 @@ class SettingsPage {
|
||||
const buttons = ['live-check-new-button', 'live-rebuild-catalog-button', 'live-analysis-button']
|
||||
.map(id => document.getElementById(id)).filter(Boolean);
|
||||
if (buttons.some(button => button.disabled)) return;
|
||||
this.currentLiveAnalysisAction = action;
|
||||
this.liveAnalysisStatus = { ...(this.liveAnalysisStatus || {}), status: 'queued', processed: 0 };
|
||||
this.renderLiveAnalysisStatus(this.liveAnalysisStatus);
|
||||
try {
|
||||
@ -194,10 +203,39 @@ class SettingsPage {
|
||||
this.startLiveAnalysisPolling();
|
||||
} catch (err) {
|
||||
this.liveAnalysisStatus = { status: 'error', error: err.message };
|
||||
this.currentLiveAnalysisAction = '';
|
||||
this.renderLiveAnalysisStatus(this.liveAnalysisStatus);
|
||||
}
|
||||
}
|
||||
|
||||
confirmFullRefresh() {
|
||||
const titleText = 'Run a full refresh?';
|
||||
const bodyText = 'Nodecast will refresh IPTV sources, re-analyze every channel and language, refresh EPG data, and rebuild all EPG matches. This may take several minutes.';
|
||||
const modal = document.getElementById('modal');
|
||||
const title = document.getElementById('modal-title');
|
||||
const body = document.getElementById('modal-body');
|
||||
const footer = document.getElementById('modal-footer');
|
||||
const close = modal?.querySelector('.modal-close');
|
||||
if (!modal || !title || !body || !footer || !close) {
|
||||
return Promise.resolve(confirm(`${titleText}\n\n${bodyText}`));
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
title.textContent = titleText;
|
||||
body.textContent = bodyText;
|
||||
footer.innerHTML = '<button type="button" class="btn btn-secondary" id="full-refresh-cancel">Cancel</button>' +
|
||||
'<button type="button" class="btn btn-primary" id="full-refresh-confirm">Start full refresh</button>';
|
||||
modal.classList.add('active');
|
||||
const finish = confirmed => {
|
||||
modal.classList.remove('active');
|
||||
close.onclick = null;
|
||||
resolve(confirmed);
|
||||
};
|
||||
close.onclick = () => finish(false);
|
||||
document.getElementById('full-refresh-cancel').onclick = () => finish(false);
|
||||
document.getElementById('full-refresh-confirm').onclick = () => finish(true);
|
||||
});
|
||||
}
|
||||
|
||||
startLiveAnalysisPolling() {
|
||||
if (this.liveAnalysisPollTimer) return;
|
||||
this.liveAnalysisPollTimer = setInterval(async () => {
|
||||
@ -211,6 +249,7 @@ class SettingsPage {
|
||||
this.liveAnalysisPollTimer = null;
|
||||
if (presentation.complete) {
|
||||
await this.app.channelList?.loadChannels();
|
||||
this.currentLiveAnalysisAction = '';
|
||||
this.liveAnalysisStatus = status;
|
||||
this.renderLiveAnalysisStatus(status);
|
||||
}
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
||||
const BUSY_STATUSES = new Set(['queued', 'building', 'running']);
|
||||
const FAILED_STATUSES = new Set(['error', 'failed']);
|
||||
const INDETERMINATE_PHASES = new Set([
|
||||
'writing', 'refreshing_live_sources', 'refreshing_epg_sources', 'writing_epg_matches'
|
||||
]);
|
||||
|
||||
function progressValues(status, phase) {
|
||||
if (phase === 'laya_identity') {
|
||||
@ -33,7 +36,7 @@
|
||||
const progress = progressValues(status, phase);
|
||||
const processed = Number(progress.processed);
|
||||
const total = Number(progress.total);
|
||||
const determined = busy && phase !== 'writing' &&
|
||||
const determined = busy && !INDETERMINATE_PHASES.has(phase) &&
|
||||
progress.processed !== null && progress.processed !== undefined &&
|
||||
progress.total !== null && progress.total !== undefined &&
|
||||
Number.isFinite(processed) && Number.isFinite(total) && total > 0;
|
||||
@ -42,7 +45,15 @@
|
||||
: null;
|
||||
const previouslyCompleted = Boolean(status.completedAt) || Number(status.projectedCount) > 0;
|
||||
const statusLabel = busy
|
||||
? phase === 'laya_identity'
|
||||
? phase === 'refreshing_live_sources'
|
||||
? 'Refreshing IPTV sources…'
|
||||
: phase === 'refreshing_epg_sources'
|
||||
? 'Refreshing EPG data…'
|
||||
: ['matching_epg', 'laya_epg_matching'].includes(phase)
|
||||
? 'Matching channels to EPG data…'
|
||||
: phase === 'writing_epg_matches'
|
||||
? 'Finalizing…'
|
||||
: phase === 'laya_identity'
|
||||
? 'Analyzing with local AI…'
|
||||
: phase === 'laya_language'
|
||||
? 'Detecting stream languages…'
|
||||
@ -52,7 +63,7 @@
|
||||
? 'Saving channel catalog…'
|
||||
: 'Analyzing channels…'
|
||||
: complete ? 'Analysis complete' : failed ? 'Analysis failed' : 'Ready to analyze';
|
||||
const progressText = phase === 'writing' && busy
|
||||
const progressText = ['writing', 'writing_epg_matches'].includes(phase) && busy
|
||||
? 'Finalizing…'
|
||||
: progressPercent === null
|
||||
? (busy ? 'In progress…' : '')
|
||||
|
||||
Reference in New Issue
Block a user