//! `DirectBackend` analysis and rules: stored analysis results, batch saves, //! feature backfill queries, and rule application over one or many samples. use super::{ AnalysisBackend, AnalysisResult, BackendResult, ConfigBackend, DirectBackend, RulesBackend, SearchCommand, VfsId, instrument, vfs, }; use crate::backend::ConfigKey; impl AnalysisBackend for DirectBackend { // --- Analysis --- fn get_analysis(&self, hash: &str) -> BackendResult> { let db = self.db.lock(); Ok(audiofiles_core::analysis::load_analysis(&db, hash)) } fn save_analysis_batch(&self, results: &[AnalysisResult]) -> BackendResult<()> { if results.is_empty() { return Ok(()); } let db = self.db.lock(); audiofiles_core::analysis::save_analysis_batch(&db, results)?; // Invalidate the search worker's cached VP-trees once for the batch, new // analysis data changes normalization ranges and may add fingerprints, so // both indexes must rebuild on the next query. if let Some(w) = self.search_worker.lock().as_ref() { // Fire-and-forget cache drop; if the worker is gone it rebuilds // on next spawn, so a dropped Invalidate is harmless (no busy flag). let _ = w.send(SearchCommand::Invalidate); } Ok(()) } fn apply_rules_to_sample(&self, hash: &str) -> BackendResult { let db = self.db.lock(); Ok(audiofiles_core::rules::apply_rules_to_sample(&db, hash)?) } fn apply_rules_to_samples(&self, hashes: &[String]) -> BackendResult { let db = self.db.lock(); Ok(audiofiles_core::rules::apply_rules_to_samples(&db, hashes)?) } fn samples_needing_features(&self) -> BackendResult> { let db = self.db.lock(); Ok(audiofiles_core::analysis::samples_needing_features(&db)?) } } impl RulesBackend for DirectBackend { fn list_rules(&self) -> BackendResult> { let db = self.db.lock(); Ok(audiofiles_core::rules::list_rules(&db)?) } fn create_rule( &self, rule: audiofiles_core::rules::NewRule, ) -> BackendResult { let db = self.db.lock(); Ok(audiofiles_core::rules::create_rule(&db, rule)?) } fn update_rule(&self, rule: &audiofiles_core::rules::Rule) -> BackendResult<()> { let db = self.db.lock(); Ok(audiofiles_core::rules::update_rule(&db, rule)?) } fn delete_rule(&self, id: &str) -> BackendResult<()> { let db = self.db.lock(); Ok(audiofiles_core::rules::delete_rule(&db, id)?) } fn set_rule_enabled(&self, id: &str, enabled: bool) -> BackendResult<()> { let db = self.db.lock(); Ok(audiofiles_core::rules::set_rule_enabled(&db, id, enabled)?) } fn preview_rule_matches(&self, rule: &audiofiles_core::rules::Rule) -> BackendResult { let db = self.db.lock(); Ok(audiofiles_core::rules::preview_rule_matches(&db, rule)?) } fn apply_all_rules(&self) -> BackendResult { let db = self.db.lock(); Ok(audiofiles_core::rules::apply_all_rules(&db)?) } fn sample_tag_provenance( &self, hash: &str, ) -> BackendResult)>> { let db = self.db.lock(); Ok(audiofiles_core::rules::sample_tag_provenance(&db, hash)?) } } impl ConfigBackend for DirectBackend { // --- Config --- fn get_config(&self, key: ConfigKey) -> BackendResult> { let db = self.db.lock(); // A read error reads as "unset" the way the old direct query did, so a // transient DB hiccup falls back to the caller's default rather than // surfacing an error on a settings read. Ok(db.get_config(key).ok().flatten()) } #[instrument(skip(self, value))] fn set_config(&self, key: ConfigKey, value: &str) -> BackendResult<()> { let db = self.db.lock(); db.set_config(key, value) .map_err(audiofiles_core::error::CoreError::from)?; Ok(()) } fn delete_config(&self, key: ConfigKey) -> BackendResult<()> { let db = self.db.lock(); db.delete_config(key) .map_err(audiofiles_core::error::CoreError::from)?; Ok(()) } fn set_vfs_sync_files(&self, id: VfsId, enabled: bool) -> BackendResult<()> { let db = self.db.lock(); vfs::set_vfs_sync_files(&db, id, enabled)?; Ok(()) } fn get_vfs_sync_files(&self, id: VfsId) -> BackendResult { let db = self.db.lock(); Ok(vfs::get_vfs_sync_files(&db, id)?) } }