From 0a0fa18bfa20a3c8e080e8710fd4ee3c86f5208a Mon Sep 17 00:00:00 2001 From: Connor Johnstone Date: Mon, 23 Mar 2026 18:37:45 -0400 Subject: [PATCH] redux of the worker queue --- src/lib.rs | 2 +- src/organizer.rs | 60 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 6577041..228168d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,5 +10,5 @@ pub mod sanitize; pub mod template; pub use error::{OrgError, OrgResult}; -pub use organizer::{OrgConfig, OrgStats, organize_from_db, organize_from_directory}; +pub use organizer::{OrgConfig, OrgStats, organize_from_db, organize_from_directory, organize_track}; pub use template::DEFAULT_FORMAT; diff --git a/src/organizer.rs b/src/organizer.rs index f71be80..eaafe84 100644 --- a/src/organizer.rs +++ b/src/organizer.rs @@ -131,6 +131,66 @@ fn cleanup_empty_dirs(dir: &Path, stop_at: &Path) { } } +/// Organize a single track by ID. Returns `Ok(true)` if moved, `Ok(false)` if skipped. +pub async fn organize_track( + conn: &DatabaseConnection, + track_id: i32, + config: &OrgConfig, +) -> OrgResult { + let track = queries::tracks::get_by_id(conn, track_id).await?; + let source = Path::new(&track.file_path); + + if !source.exists() { + tracing::warn!(path = %track.file_path, "source file does not exist"); + return Ok(false); + } + + let meta = TrackMetadata::from_db_track(&track); + let relative = template::render(&config.format, &meta); + let target = config.target_dir.join(&relative); + + // Canonicalize for comparison + let source_canon = source.canonicalize().unwrap_or_else(|_| source.to_owned()); + let target_parent = target.parent().unwrap_or(Path::new(".")); + if target_parent.exists() { + let target_canon = target_parent + .canonicalize() + .map(|p| p.join(target.file_name().unwrap_or_default())) + .unwrap_or_else(|_| target.clone()); + if source_canon == target_canon { + tracing::debug!(path = %track.file_path, "already in place, skipping"); + return Ok(false); + } + } + + let final_target = resolve_target(&target); + + if config.dry_run { + return Ok(true); + } + + let source_dir = source.parent().map(Path::to_owned); + move_or_copy(source, &final_target, config.copy)?; + + // Update file_path in DB + let new_path = final_target.to_string_lossy().to_string(); + let active = shanty_db::entities::track::ActiveModel { + id: Set(track.id), + file_path: Set(new_path), + ..Default::default() + }; + queries::tracks::update_metadata(conn, track.id, active).await?; + + // Cleanup empty source directory + if !config.copy + && let Some(dir) = source_dir + { + cleanup_empty_dirs(&dir, &dir); + } + + Ok(true) +} + /// Organize all tracks from the database. pub async fn organize_from_db( conn: &DatabaseConnection,