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:
blomios
2026-10-08 00:05:00 +02:00
parent b81bc10d49
commit b3dbb3cfba
14 changed files with 404 additions and 39 deletions

View File

@ -339,6 +339,18 @@ function rebuild(database = null, options = {}) {
}
}
async function fullRebuildOrchestrator(database = null, dependencies = {}) {
database ||= defaultDatabase();
const syncService = dependencies.syncService || require('./syncService');
const sourceRepository = dependencies.sources || require('../db').sources;
await syncService.refreshEnabledLiveSources({ rebuild: false });
await startRebuild(database, { analyzeAll: true }).promise;
await syncService.refreshEnabledEpgSources();
const epgSources = await sourceRepository.getByType('epg');
epgMatching.rebuildPersistedEpgMatches(database, epgSources);
return projectionState(database);
}
async function rebuildWithSignals(database = null, options = {}) {
database ||= defaultDatabase();
const items = loadProjectionItems(database);
@ -389,9 +401,7 @@ function startRebuild(database = null, options = {}) {
return { alreadyRunning: false, state: activeState, promise: activeRebuild };
}
function queueRebuild(database = null) {
return startRebuild(database, { mode: 'algo' }).promise;
}
const queueRebuild = (database = null) => startRebuild(database, { mode: 'algo' }).promise;
function parseData(value) {
try { return value ? JSON.parse(value) : {}; } catch { return {}; }
@ -435,10 +445,9 @@ function listChannel(channel) {
};
}
function getChannelPage({ sourceId = null, includeHidden = false, userId = null, q = '', languages = [], epgSources = [], page = 1, limit = DEFAULT_PAGE_LIMIT }, database = null, dependencies = {}) {
function getChannelPage({ sourceId = null, includeHidden = false, userId = null, q = '', languages = [], page = 1, limit = DEFAULT_PAGE_LIMIT }, database = null, dependencies = {}) {
database ||= defaultDatabase();
const standardCatalog = dependencies.iptvOrgCatalog || iptvOrgCatalog;
const epgMatcher = dependencies.epgMatching || epgMatching;
const params = [];
let sourceFilter = '';
if (sourceId !== null) {
@ -471,6 +480,24 @@ function getChannelPage({ sourceId = null, includeHidden = false, userId = null,
const filtered = serialized.filter(channel =>
(!languages.length || languages.includes(channel.language.code)) && matchesSearch(channel, q)
);
const persistedEpg = new Map(database.prepare(`
SELECT logical_channel_id, language_code, source_id, channel_id, match_confidence, data
FROM live_epg_matches
`).all().map(match => [`${match.logical_channel_id}\0${match.language_code}`, match]));
for (const channel of filtered) {
const match = persistedEpg.get(`${channel.logicalChannelId}\0${channel.language.code}`);
if (!match) continue;
let data = {};
try { data = match.data ? JSON.parse(match.data) : {}; } catch {}
channel.epg = {
...channel.epg,
...data,
sourceId: match.source_id,
channelId: match.channel_id,
matchConfidence: match.match_confidence,
language: channel.language.code
};
}
const bounds = pageBounds(page, limit);
const paged = filtered.slice(bounds.offset, bounds.offset + bounds.limit);
// Catalog loading must stay bounded: both enrichers perform synchronous SQLite work,
@ -483,8 +510,6 @@ function getChannelPage({ sourceId = null, includeHidden = false, userId = null,
channel.standardName = match.name;
channel.standard = { provider: 'iptv-org', channelId: match.channelId, country: match.country, confidence: match.confidence };
}
const epgMatch = epgMatcher.findBestMatch(database, channel, epgSources);
if (epgMatch) channel.epg = { ...channel.epg, ...epgMatch, language: channel.language.code };
return channel;
});
return {
@ -517,7 +542,7 @@ function getChannelVariants({ logicalChannelId, includeHidden = false, userId =
}
function checkNewStreams() {
return require('./syncService').syncAll();
return require('./syncService').refreshEnabledLiveSources({ rebuild: true });
}
function getChannels(options, database = null) {
@ -600,6 +625,7 @@ module.exports = {
collectOptionalIdentityHints,
displayNameForFingerprint,
checkNewStreams,
fullRebuildOrchestrator,
getChannelPage,
getChannelVariants,
getChannels,