fix(live): expose full refresh busy and phase progress

Ensure full refresh marks the live projection as building before source refresh awaits, and expose phase-specific counters so language detection progress is determinate.

Covers #40 and #41.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-08 16:20:36 +02:00
parent 09bdaa3ec1
commit ddd7a3f295
7 changed files with 139 additions and 25 deletions

View File

@ -9,6 +9,7 @@ const router = express.Router();
function serializeState(state) {
const emptyProjectionWithLiveInput = state.total_count > 0 && state.projected_count === 0;
const isLayaPhase = state.phase === 'laya_identity' || state.phase === 'laya_language';
return {
status: state.status,
stale: state.status !== 'success' || emptyProjectionWithLiveInput,
@ -21,6 +22,8 @@ function serializeState(state) {
total: state.total_count,
layaProcessed: state.laya_processed_count,
layaTotal: state.laya_total_count,
phaseProcessed: isLayaPhase ? state.laya_processed_count : state.processed_count,
phaseTotal: isLayaPhase ? state.laya_total_count : state.total_count,
error: state.error
};
}

View File

@ -296,7 +296,7 @@ function updateProjectionProgress(database, phase, progress = {}) {
`).run(phase, progress.layaProcessed ?? null, progress.layaTotal ?? null);
}
function markProjectionBuilding(database = null) {
function markProjectionBuilding(database = null, phase = 'preparing') {
database ||= defaultDatabase();
const total = database.prepare("SELECT COUNT(*) AS count FROM playlist_items WHERE type = 'live'").get().count;
const startedAt = Date.now();
@ -304,12 +304,12 @@ function markProjectionBuilding(database = null) {
INSERT INTO catalog_projection_state
(projection, status, started_at, completed_at, input_hash, projected_count, phase,
processed_count, total_count, laya_processed_count, laya_total_count, error)
VALUES ('live', 'building', ?, NULL, NULL, 0, 'preparing', 0, ?, 0, 0, NULL)
VALUES ('live', 'building', ?, NULL, NULL, 0, ?, 0, ?, 0, 0, NULL)
ON CONFLICT(projection) DO UPDATE SET status = 'building', started_at = excluded.started_at,
completed_at = NULL, phase = 'preparing', processed_count = 0,
completed_at = NULL, phase = excluded.phase, processed_count = 0,
total_count = excluded.total_count, laya_processed_count = 0,
laya_total_count = 0, error = NULL
`).run(startedAt, total);
`).run(startedAt, phase, total);
return projectionState(database);
}
@ -392,8 +392,9 @@ async function fullRebuildOrchestrator(database = null, dependencies = {}) {
database ||= defaultDatabase();
const syncService = dependencies.syncService || require('./syncService');
const sourceRepository = dependencies.sources || require('../db').sources;
markProjectionBuilding(database, 'refreshing_live_sources');
await syncService.refreshEnabledLiveSources({ rebuild: false });
await startRebuild(database, { analyzeAll: true }).promise;
await startRebuild(database, { analyzeAll: true, stateAlreadyBuilding: true }).promise;
await syncService.refreshEnabledEpgSources();
const epgSources = await sourceRepository.getByType('epg');
epgMatching.rebuildPersistedEpgMatches(database, epgSources);
@ -441,9 +442,10 @@ function startRebuild(database = null, options = {}) {
mark = markProjectionBuilding,
runner = rebuildWithSignals,
schedule = setImmediate,
stateAlreadyBuilding = false,
...rebuildOptions
} = options;
activeState = mark(database);
activeState = stateAlreadyBuilding ? projectionState(database) : mark(database);
activeRebuild = new Promise((resolve, reject) => {
schedule(() => {
Promise.resolve()