refactor: extract psysonic-analysis crate (M2/7)

Moves analysis_cache + analysis_runtime out of the top crate into a new
psysonic-analysis crate, plus the runtime user-agent facade into
psysonic-core. The audio↔analysis dependency cycle is broken via a
PlaybackQueryHandle port registered as Tauri State.

  crates/psysonic-analysis/                new lib crate
    src/analysis_cache/{mod,store,compute} unchanged behaviour
    src/analysis_runtime.rs                + enqueue_analysis_seed (was
                                           in lib_commands/cache/offline)

  crates/psysonic-core/src/
    user_agent.rs                          subsonic_wire_user_agent +
                                           runtime/default helpers
                                           (was in top lib.rs)
    ports.rs                               PlaybackQueryHandle: closure
                                           wrapper, not Arc<dyn Trait>,
                                           so existing State<AudioEngine>
                                           callsites stay unchanged

The shell setup hook registers the real PlaybackQueryHandle once the
AppHandle is available; the closure captures it and re-resolves
AudioEngine via try_state at each call.

Top crate keeps `crate::analysis_cache`, `crate::analysis_runtime`,
`crate::subsonic_wire_user_agent`, and `crate::submit_analysis_cpu_seed`
working via re-exports — no audio/lib_commands callsite needed editing.

Behaviour preserving. Cargo check + clippy --workspace clean (only
pre-existing warnings carry over).
This commit is contained in:
Psychotoxical
2026-05-09 13:25:04 +02:00
parent 7718ac3ee5
commit ff456dd823
15 changed files with 195 additions and 124 deletions
+2 -1
View File
@@ -1,9 +1,10 @@
use crate::analysis_runtime::enqueue_analysis_seed;
use crate::audio;
use crate::subsonic_wire_user_agent;
use super::super::file_transfer::stream_to_file;
use super::downloads::{resolve_hot_cache_root, HotCacheDownloadResult};
use super::offline::{enqueue_analysis_seed, enqueue_analysis_seed_from_file};
use super::offline::enqueue_analysis_seed_from_file;
#[tauri::command]
pub(crate) async fn download_track_hot_cache(
-2
View File
@@ -15,5 +15,3 @@ pub(crate) use downloads::{
check_arch_linux, download_update, download_zip, fetch_netease_lyrics, get_embedded_lyrics,
open_folder,
};
// Internal helper consumed by analysis_runtime (used as analysis_runtime depends on it):
pub(crate) use offline::enqueue_analysis_seed;
+1 -32
View File
@@ -1,44 +1,13 @@
use tauri::Manager;
use crate::analysis_cache;
use crate::analysis_runtime::{analysis_backfill_is_current_track, submit_analysis_cpu_seed};
use crate::analysis_runtime::enqueue_analysis_seed;
use crate::DownloadSemaphore;
use super::super::file_transfer::{finalize_streamed_download, subsonic_http_client};
// ─── Offline Track Cache ──────────────────────────────────────────────────────
pub(crate) async fn enqueue_analysis_seed(app: &tauri::AppHandle, track_id: &str, bytes: &[u8]) -> Result<bool, String> {
if let Some(cache) = app.try_state::<analysis_cache::AnalysisCache>() {
if cache.cpu_seed_redundant_for_track(track_id).unwrap_or(false) {
return Ok(true);
}
}
let high = analysis_backfill_is_current_track(app, track_id);
let outcome = submit_analysis_cpu_seed(
app.clone(),
track_id.to_string(),
bytes.to_vec(),
high,
)
.await
.map_err(|e| {
crate::app_eprintln!("[analysis] failed to seed {}: {}", track_id, e);
e
})?;
let has_loudness = app
.try_state::<analysis_cache::AnalysisCache>()
.and_then(|cache| cache.get_latest_loudness_for_track(track_id).ok().flatten())
.is_some();
crate::app_deprintln!(
"[analysis] seed result track_id={} bytes={} has_loudness={} outcome={outcome:?}",
track_id,
bytes.len(),
has_loudness
);
Ok(has_loudness)
}
pub(crate) async fn enqueue_analysis_seed_from_file(
app: &tauri::AppHandle,
track_id: &str,