virittaa

Log | Files | Refs | README | LICENSE

commit e75cff3b66887e210a690a05183719b9ff2ce0bc
parent b15c402ae352767c978b2637ab0b152054a6df38
Author: mtmn <miro@haravara.org>
Date:   Thu, 14 May 2026 13:49:09 +0200

fix: report code leftovers; search fix

Diffstat:
MCargo.lock | 134-------------------------------------------------------------------------------
MCargo.toml | 2--
Msrc/db.rs | 47++++++++++++++++++-----------------------------
Msrc/file_io.rs | 63+--------------------------------------------------------------
Msrc/main.rs | 24++++++++++++++++--------
5 files changed, 35 insertions(+), 235 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -24,15 +24,6 @@ dependencies = [ ] [[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - -[[package]] name = "anes" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -240,19 +231,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] -name = "chrono" -version = "0.4.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" -dependencies = [ - "iana-time-zone", - "js-sys", - "num-traits", - "wasm-bindgen", - "windows-link", -] - -[[package]] name = "ciborium" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -324,12 +302,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] name = "crc32fast" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -406,27 +378,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] -name = "csv" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52cd9d68cf7efc6ddfaaee42e7288d3a99d613d4b50f76ce9827ae0c6e14f938" -dependencies = [ - "csv-core", - "itoa", - "ryu", - "serde_core", -] - -[[package]] -name = "csv-core" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "704a3c26996a80471189265814dbc2c257598b96b8a7feae2d31ace646bb9782" -dependencies = [ - "memchr", -] - -[[package]] name = "data-encoding" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -674,30 +625,6 @@ dependencies = [ ] [[package]] -name = "iana-time-zone" -version = "0.1.65" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "log", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] name = "indexmap" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1373,12 +1300,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] -name = "ryu" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" - -[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1605,9 +1526,7 @@ dependencies = [ "anyhow", "bliss-audio", "bliss-audio-aubio-rs", - "chrono", "clap", - "csv", "dirs", "lofty", "ndarray 0.17.2", @@ -1744,65 +1663,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] -name = "windows-core" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" -dependencies = [ - "windows-implement", - "windows-interface", - "windows-link", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-implement" -version = "0.60.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "windows-interface" -version = "0.59.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] -name = "windows-result" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" -dependencies = [ - "windows-link", -] - -[[package]] -name = "windows-strings" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" -dependencies = [ - "windows-link", -] - -[[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/Cargo.toml b/Cargo.toml @@ -12,8 +12,6 @@ clap = { version = "4.5.35", features = ["cargo"] } walkdir = "2.5" lofty = "0.24.0" rayon = "1.10" -chrono = "0.4.43" -csv = "1.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" sled = "0.34" diff --git a/src/db.rs b/src/db.rs @@ -93,9 +93,7 @@ impl LibraryDb { .context("Failed to open artist tree")?; let key_tree = db.open_tree("key").context("Failed to open key tree")?; let bpm_tree = db.open_tree("bpm").context("Failed to open bpm tree")?; - let time_tree = db - .open_tree("time") - .context("Failed to open time tree")?; + let time_tree = db.open_tree("time").context("Failed to open time tree")?; Ok(Self { db, artist_tree, @@ -111,18 +109,6 @@ impl LibraryDb { /// /// Returns an error if serialization or any database write fails. pub fn save(&self, key: &str, report: &TrackReport) -> Result<()> { - // Remove old time index entry if re-saving an existing record - let old = self.db.get(key).context("Failed to read existing record")?; - if let Some(old_bytes) = old - && let Ok(old_report) = serde_json::from_slice::<TrackReport>(&old_bytes) - && let Some(old_ts) = old_report.timestamp - { - let old_time_key = time_index_key(old_ts, key.as_bytes()); - self.time_tree - .remove(&old_time_key) - .context("Failed to remove old time index")?; - } - #[allow(clippy::cast_possible_wrap)] let now = std::time::SystemTime::now() .duration_since(std::time::UNIX_EPOCH) @@ -139,10 +125,22 @@ impl LibraryDb { let value = serde_json::to_vec(&report).context("Failed to serialize track report")?; - self.db + let old = self + .db .insert(key.as_bytes(), value) .context("Failed to write to database")?; + // Remove old time index entry if overwriting an existing record + if let Some(old_bytes) = old + && let Ok(old_report) = serde_json::from_slice::<TrackReport>(&old_bytes) + && let Some(old_ts) = old_report.timestamp + { + let old_time_key = time_index_key(old_ts, key.as_bytes()); + self.time_tree + .remove(&old_time_key) + .context("Failed to remove old time index")?; + } + // Artist index let artist_lower = report.artist.as_deref().unwrap_or("unknown").to_lowercase(); let idx_key = prefix_index_key(artist_lower.as_bytes(), key.as_bytes()); @@ -288,27 +286,18 @@ impl LibraryDb { // Fallback: entries without a time index (migrated from old format) if results.len() < effective_limit { - let time_keys: Vec<_> = self.time_tree.iter().collect::<Result<Vec<_>, _>>().unwrap_or_default(); - let indexed_keys: std::collections::HashSet<Vec<u8>> = time_keys - .iter() - .filter_map(|(k, _)| { - let pos = k.iter().position(|&b| b == SEP)?; - Some(k[pos + 1..].to_vec()) - }) - .collect(); - for item in self.db.iter() { if results.len() >= effective_limit { break; } let (key, value) = item.context("Failed to read iterator entry")?; - if indexed_keys.contains(&key.to_vec()) { - continue; + let report: TrackReport = + serde_json::from_slice(&value).context("Failed to deserialize track report")?; + if report.timestamp.is_some() { + continue; // already included via time index } let key_str = String::from_utf8(key.to_vec()).context("Invalid UTF-8 key in database")?; - let report: TrackReport = - serde_json::from_slice(&value).context("Failed to deserialize track report")?; results.push((key_str, report)); } } diff --git a/src/file_io.rs b/src/file_io.rs @@ -1,14 +1,12 @@ use anyhow::Result; -use chrono::Local; use lofty::config::WriteOptions; use lofty::prelude::*; use lofty::probe::Probe; use lofty::tag::{ItemKey, Tag}; -use std::borrow::Cow; use std::path::Path; use crate::audio_processing::{calculate_bpm, calculate_key}; -use crate::types::{TrackError, TrackInfo}; +use crate::types::TrackInfo; use bliss_audio::decoder::Decoder as DecoderTrait; use bliss_audio::decoder::ffmpeg::FFmpegDecoder as Decoder; @@ -101,65 +99,6 @@ fn read_tags(path: &Path) -> (FileMetadata, Option<(f32, String)>) { } } -#[derive(serde::Serialize)] -#[serde(rename_all = "PascalCase")] -struct ReportRecord<'a> { - path: Cow<'a, str>, - artist: Option<&'a str>, - album: Option<&'a str>, - track: Option<&'a str>, - label: Option<&'a str>, - #[serde(rename = "BPM")] - bpm: Option<String>, - key: Option<&'a str>, - status: &'a str, - error: Option<&'a str>, -} - -/// Write a CSV report of processed tracks. -/// -/// # Errors -/// -/// Returns an error if the CSV file cannot be created or written. -pub fn write_report(results: &[TrackInfo], errors: &[TrackError]) -> Result<()> { - let timestamp = Local::now().format("%Y-%m-%dT%H-%M-%S").to_string(); - let filename = format!("virittaa-report-{timestamp}.csv"); - - let mut wtr = csv::Writer::from_path(&filename)?; - - for track in results { - wtr.serialize(ReportRecord { - path: track.path.to_string_lossy(), - artist: track.artist.as_deref(), - album: track.album.as_deref(), - track: track.track.as_deref(), - label: track.label.as_deref(), - bpm: Some(format!("{:.1}", track.bpm)), - key: Some(&track.key), - status: "Success", - error: None, - })?; - } - - for error in errors { - wtr.serialize(ReportRecord { - path: error.path.to_string_lossy(), - artist: None, - album: None, - track: None, - label: None, - bpm: None, - key: None, - status: "Error", - error: Some(&error.reason), - })?; - } - - wtr.flush()?; - println!("Report written to: {filename}"); - Ok(()) -} - /// Write BPM and key metadata to a file. /// /// # Errors diff --git a/src/main.rs b/src/main.rs @@ -6,7 +6,7 @@ pub mod import; pub mod types; pub mod utils; -use anyhow::Result; +use anyhow::{Context, Result}; use clap::{Arg, Command}; use rayon::prelude::*; use regex::Regex; @@ -132,12 +132,19 @@ fn main() -> Result<()> { let key_prefix = matches.get_one::<String>("key").map(String::as_str); let import_mixxx_path = matches.get_one::<String>("import-mixxx"); let limit = *matches.get_one::<usize>("limit").unwrap(); - let bpm_range: Option<(f64, f64)> = matches.get_many::<String>("bpm").map(|vals| { - let v: Vec<&String> = vals.collect(); - let min = v[0].parse::<f64>().expect("Invalid BPM min value"); - let max = v[1].parse::<f64>().expect("Invalid BPM max value"); - (min, max) - }); + let bpm_range: Option<(f64, f64)> = match matches.get_many::<String>("bpm") { + Some(vals) => { + let v: Vec<&String> = vals.collect(); + let min = v[0] + .parse::<f64>() + .with_context(|| format!("Invalid BPM min value: {}", v[0]))?; + let max = v[1] + .parse::<f64>() + .with_context(|| format!("Invalid BPM max value: {}", v[1]))?; + Some((min, max)) + } + None => None, + }; let db_path = if let Some(p) = matches.get_one::<String>("db-path") { p.clone() } else { @@ -192,10 +199,11 @@ fn main() -> Result<()> { ); } else if let Some(pattern) = search_pattern { let re = Regex::new(pattern)?; - let entries = db.list(effective_limit)?; + let entries = db.list(0)?; // search all entries regardless of limit let matches_found: Vec<_> = entries .into_iter() .filter(|(key, report)| re.is_match(key) || re.is_match(&report.to_string())) + .take(effective_limit) .collect(); print_entries( &matches_found,