Files
Psychotoxical-psysonic/src-tauri/crates/psysonic-core/src/ports.rs
T
Psychotoxical 97f06459f3 refactor: move analysis admin commands into psysonic-analysis (M6/7)
Reframed M6 from "extract psysonic-commands" to "place each domain's
Tauri commands in its own domain crate" — the original psysonic-commands
proposal would have been a thin shell with no clear domain ownership
because each prior milestone already kept its own commands inline:

  audio_*_commands           in psysonic-audio
  cache/sync commands        in psysonic-syncfs
  navidrome/discord/etc      in psysonic-integration

The leftover analysis admin commands (7 of them) logically belong to
the analysis domain. So they move there:

  src/lib_commands/app_api/analysis.rs   →
    crates/psysonic-analysis/src/commands.rs

  WaveformCachePayload + LoudnessCachePayload  moved out of top-crate
                                               lib.rs into commands.rs

PlaybackQueryHandle gets a second closure (`should_defer_backfill`) so
analysis_enqueue_seed_from_url can ask "is a ranged playback already
going to seed this track?" without depending on psysonic-audio.

Top crate keeps the shell-flavored commands (window/tray/mini-player,
greet/exit_app, mpris/global-shortcuts/check_dir_accessible, perf,
cli_bridge) for M7 to clean up.

Behaviour preserving. Cargo check + clippy --workspace clean.
2026-05-09 14:00:33 +02:00

52 lines
2.1 KiB
Rust

//! Cross-crate port handles.
//!
//! Exists to break the one back-edge in the audio↔analysis dependency:
//! `psysonic-analysis` needs to ask "is this track currently playing?", but
//! must not depend on `psysonic-audio` (which has the real dep on analysis,
//! not the other way around).
//!
//! Implementation note: ports are exposed as **closure handles** rather than
//! `Arc<dyn Trait>` — this avoids forcing every existing `State<AudioEngine>`
//! callsite to switch to `State<Arc<AudioEngine>>` (which Tauri State requires
//! for trait-object registration). The shell crate creates the handle by
//! capturing an `AppHandle` and looking up the audio engine at call time.
use std::sync::Arc;
/// Read-only queries about the live playback session, used by analysis-side
/// code to break the analysis→audio back-edge. The shell crate constructs an
/// instance with two closures (each capturing an `AppHandle`) and registers it
/// as Tauri State; `psysonic-analysis` looks it up via `try_state::<…>()`.
///
/// The closures are independent so each can be a no-op / always-false fallback
/// without coupling the other.
#[derive(Clone)]
pub struct PlaybackQueryHandle {
is_playing: Arc<dyn Fn(&str) -> bool + Send + Sync + 'static>,
should_defer_backfill: Arc<dyn Fn(&str) -> bool + Send + Sync + 'static>,
}
impl PlaybackQueryHandle {
pub fn new<P, D>(is_playing: P, should_defer_backfill: D) -> Self
where
P: Fn(&str) -> bool + Send + Sync + 'static,
D: Fn(&str) -> bool + Send + Sync + 'static,
{
Self {
is_playing: Arc::new(is_playing),
should_defer_backfill: Arc::new(should_defer_backfill),
}
}
/// `true` if `track_id` is the track currently being decoded/played.
pub fn is_track_currently_playing(&self, track_id: &str) -> bool {
(self.is_playing)(track_id)
}
/// `true` if a ranged HTTP playback for `track_id` is mid-flight and will
/// seed analysis on completion — the backfill enqueue should defer.
pub fn ranged_loudness_backfill_should_defer(&self, track_id: &str) -> bool {
(self.should_defer_backfill)(track_id)
}
}