fix(analysis): native library backfill coordinator for advanced strategy (#881)

* feat(analysis): native library backfill coordinator for advanced strategy

Move advanced analytics scheduling from the webview loop into a Rust
background worker (configure + spawn_blocking batch/enqueue), matching
cover backfill. Scan hash+BPM gap tracks instead of the full library;
suppress low-priority analysis UI events; limit loudness refresh IPC to
the playback window.

* fix(analysis): flat backfill configure IPC and restore probe track-perf

Use flattened Tauri args like cover backfill so release/prod invoke works;
keep emitting analysis:track-perf for library low-priority work so Performance
Probe tpm/last-track stats update while waveform/enrichment UI events stay suppressed.

* chore(analysis): fix clippy and drop duplicate TS backfill policy

Allow too_many_arguments on library_analysis_backfill_configure for CI;
remove unused frontend batch API and TS watermark helpers now owned in Rust;
clarify analysis_emits_ui_events comment for low-priority track-perf.

* docs: CHANGELOG and credits for PR #881 native analysis backfill
This commit is contained in:
cucadmuh
2026-05-28 10:49:31 +03:00
committed by GitHub
parent df3533bb5a
commit b24a7fc5cb
21 changed files with 888 additions and 387 deletions
@@ -292,6 +292,37 @@ impl<'a> TrackRepository<'a> {
})
}
/// Tracks with `content_hash` and an analysis BPM fact — may still lack waveform/LUFS.
/// Confirmed per id via [`TrackAnalysisNeedsWorkQuery`].
pub fn list_analysis_hash_bpm_ids_after(
&self,
server_id: &str,
after_id: Option<&str>,
limit: usize,
) -> Result<Vec<String>, String> {
if limit == 0 {
return Ok(vec![]);
}
let limit = i64::try_from(limit).map_err(|e| e.to_string())?;
self.store.with_read_conn(|conn| {
let sql = "SELECT t.id FROM track t \
WHERE t.server_id = ?1 AND t.deleted = 0 \
AND (?2 IS NULL OR t.id > ?2) \
AND t.content_hash IS NOT NULL \
AND EXISTS ( \
SELECT 1 FROM track_fact f \
WHERE f.server_id = t.server_id \
AND f.track_id = t.id \
AND f.fact_kind = 'bpm' \
AND f.source_kind = 'analysis' \
) \
ORDER BY t.id ASC LIMIT ?3";
let mut stmt = conn.prepare(sql)?;
let rows = stmt.query_map(params![server_id, after_id, limit], |row| row.get(0))?;
rows.collect::<rusqlite::Result<Vec<String>>>()
})
}
/// Cheap SQL prefilter: tracks that never received a playback hash and/or
/// lack an oximedia BPM fact. Full analysis gaps are confirmed per id via
/// [`TrackAnalysisNeedsWorkQuery`] in the shell crate.