//! Off-thread Sample Forge import plumbing: the in-flight forge context, the //! GUI-ready outcome, and the WAL-connection import jobs. Driven by the //! `ForgeBackend` impl in direct.rs; split out to keep that impl delegation-only. use std::path::Path; use audiofiles_core::db::Database; use audiofiles_core::store::SampleStore; use audiofiles_core::{NodeId, VfsId}; /// Import context for the in-flight forge operation. The worker does the CPU /// work (decode/slice/conform/encode → temp files); this carries the VFS /// destination so the GUI thread can import the staged files when the worker /// signals completion. pub(super) enum ForgePending { Chop { vfs_id: VfsId, parent_id: Option, }, Conform { vfs_id: VfsId, parent_id: Option, sample_rate: u32, bit_depth: u16, }, } /// GUI-ready result of an off-thread forge import job. The import (blob copy + DB /// writes) runs on its own WAL connection so it never holds the GUI's `db.lock()`; /// `poll_workers` drains this and emits the matching `BackendEvent` on a later frame. pub(super) enum ForgeImportOutcome { Chop { slice_count: usize, }, Conform { sample_rate: u32, bit_depth: u16, overshoot: Option, }, Error { error: String, }, } /// Open a fresh WAL connection + store and import chop slices. Runs on a job /// thread (the established per-worker connection pattern) so the GUI's db.lock() /// is never held during the blob copies + row writes. pub(super) fn forge_import_chop( data_dir: &Path, root: &Path, vfs_id: VfsId, parent_id: Option, staging: audiofiles_core::forge::ChopStaging, ) -> ForgeImportOutcome { let db = match Database::open(data_dir.join("audiofiles.db")) { Ok(d) => d, Err(e) => { return ForgeImportOutcome::Error { error: e.to_string(), }; } }; let store = match SampleStore::new(root) { Ok(s) => s, Err(e) => { return ForgeImportOutcome::Error { error: e.to_string(), }; } }; match audiofiles_core::forge::import_chop(&store, &db, vfs_id, parent_id, staging) { Ok(r) => ForgeImportOutcome::Chop { slice_count: r.slice_count, }, Err(e) => ForgeImportOutcome::Error { error: e.to_string(), }, } } /// Off-thread counterpart for conform import (see [`forge_import_chop`]). Maps the /// core overshoot result into the GUI-facing `ForgeOvershoot` here so the outcome /// is ready to emit. #[allow(clippy::too_many_arguments)] pub(super) fn forge_import_conform( data_dir: &Path, root: &Path, vfs_id: VfsId, parent_id: Option, sample_rate: u32, bit_depth: u16, staging: audiofiles_core::forge::ConformStaging, ) -> ForgeImportOutcome { let db = match Database::open(data_dir.join("audiofiles.db")) { Ok(d) => d, Err(e) => { return ForgeImportOutcome::Error { error: e.to_string(), }; } }; let store = match SampleStore::new(root) { Ok(s) => s, Err(e) => { return ForgeImportOutcome::Error { error: e.to_string(), }; } }; match audiofiles_core::forge::import_conform(&store, &db, vfs_id, parent_id, staging) { Ok(r) => { let overshoot = r.overshoot.map(|o| match o.action { audiofiles_core::forge::OvershootAction::Trimmed { gain_db } => { super::ForgeOvershoot::Trimmed { peak_dbfs: o.peak_dbfs, gain_db, } } audiofiles_core::forge::OvershootAction::Flagged => { super::ForgeOvershoot::Flagged { peak_dbfs: o.peak_dbfs, } } }); ForgeImportOutcome::Conform { sample_rate, bit_depth, overshoot, } } Err(e) => ForgeImportOutcome::Error { error: e.to_string(), }, } }