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
@@ -5,12 +5,14 @@
pub mod auth;
pub mod client;
pub mod error;
pub mod stream_url;
pub mod types;
pub use auth::SubsonicCredentials;
pub use client::{
fingerprint_sample, SubsonicClient, SUBSONIC_API_VERSION, SUBSONIC_CLIENT_ID,
};
pub use stream_url::{build_stream_view_url, rest_base_from_url};
pub use error::SubsonicError;
pub use types::{
Album, AlbumSummary, ArtistIndex, ArtistRef, IndexBucket, ScanStatus, SearchResult, ServerInfo,
@@ -0,0 +1,61 @@
//! Subsonic `stream.view` URLs for native library analysis backfill.
use url::Url;
use super::auth::SubsonicCredentials;
use super::client::{SUBSONIC_API_VERSION, SUBSONIC_CLIENT_ID};
/// `{origin}/rest` — mirrors frontend `restBaseFromUrl`.
pub fn rest_base_from_url(server_url: &str) -> String {
let trimmed = server_url.trim().trim_end_matches('/');
let base = if trimmed.starts_with("http://") || trimmed.starts_with("https://") {
trimmed.to_string()
} else {
format!("http://{trimmed}")
};
format!("{base}/rest")
}
/// Authenticated `stream.view` URL for a library track id.
pub fn build_stream_view_url(
server_url: &str,
username: &str,
password: &str,
track_id: &str,
) -> String {
let creds = SubsonicCredentials::from_password(username, password);
let base = rest_base_from_url(server_url);
let mut url =
Url::parse(&format!("{base}/stream.view")).unwrap_or_else(|_| Url::parse("http://invalid/rest/stream.view").unwrap());
{
let mut q = url.query_pairs_mut();
q.append_pair("id", track_id);
q.append_pair("u", &creds.username);
q.append_pair("t", &creds.token);
q.append_pair("s", &creds.salt);
q.append_pair("v", SUBSONIC_API_VERSION);
q.append_pair("c", SUBSONIC_CLIENT_ID);
q.append_pair("f", "json");
}
url.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stream_url_contains_track_and_auth_params() {
let url = build_stream_view_url(
"https://music.example",
"alice",
"secret",
"tr-42",
);
assert!(url.contains("stream.view"));
assert!(url.contains("id=tr-42"));
assert!(url.contains("u=alice"));
assert!(url.contains("&t="));
assert!(url.contains("&s="));
}
}