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:
| M | Cargo.lock | | | 134 | ------------------------------------------------------------------------------- |
| M | Cargo.toml | | | 2 | -- |
| M | src/db.rs | | | 47 | ++++++++++++++++++----------------------------- |
| M | src/file_io.rs | | | 63 | +-------------------------------------------------------------- |
| M | src/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,