diff --git a/src/json_parsing.rs b/src/json_parsing.rs index a9dfc60..02d975f 100644 --- a/src/json_parsing.rs +++ b/src/json_parsing.rs @@ -1,7 +1,7 @@ use crate::lrclib; use serde_json::Value; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use std::fs; use std::io::{self, Write}; use std::path::Path; @@ -95,3 +95,36 @@ pub fn add_song(directory: &str, filename: &str, status: &str) -> io::Result<()> save_config(&config) } + +pub fn return_all_processed_files(path: &Path) -> io::Result> { + let config = load_config()?; + let dir_str = path + .to_str() + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, "Invalid directory path"))?; + + let filenames = config + .get(dir_str) // get inner HashMap for the directory + .map(|inner| inner.keys().cloned().collect()) // collect keys into HashSet + .unwrap_or_else(HashSet::new); // empty if directory not found + + Ok(filenames) +} + +// pub fn list_already_processed() + +// pub fn list_directory_entries>(path: P) -> io::Result> { +// let mut entries = Vec::new(); + +// for entry in fs::read_dir(path)? { +// let entry = entry?; +// let file_type = entry.file_type()?; + +// if file_type.is_file() +// && let Some(name) = entry.file_name().to_str() +// { +// entries.push(name.to_string()); +// } +// } + +// Ok(entries) +// } diff --git a/src/main.rs b/src/main.rs index 7caab24..fd13719 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ use std::path::{Path, PathBuf}; use std::thread; use std::time::Duration; +use crate::music::get_all_music_files; + mod json_parsing; mod lrclib; mod music; @@ -46,7 +48,7 @@ fn main() { } } else if let Some(directory) = &args.directory { if !args.watch { - if let Err(e) = music::get_all_music_files(directory) { + if let Err(e) = music::get_all_music_files(directory, false) { eprintln!("Failed to process directory {}: {e}", directory.display()); } } else { @@ -58,6 +60,7 @@ fn main() { fn watch(path: &Path, wait: Duration) { loop { + get_all_music_files(path, true); thread::sleep(wait); } } diff --git a/src/music.rs b/src/music.rs index b42b4df..82b90e7 100644 --- a/src/music.rs +++ b/src/music.rs @@ -1,8 +1,10 @@ use crate::json_parsing::{self, add_song}; use audiotags::Tag; +use std::collections::HashSet; use std::fs; -use std::path::Path; +use std::io; +use std::path::{Path, PathBuf}; use std::time::Duration; use thiserror::Error; @@ -42,10 +44,10 @@ pub fn get_song_file(path: &Path) -> Result<(), MusicError> { let absolute_path = fs::canonicalize(&pathbuf).unwrap(); let info = get_song_info(&absolute_path)?; - println!("Track: {}", info.track_name); - println!("Artist: {}", info.artist_name); - println!("Album: {}", info.album_name); - println!("Duration: {:?}\n", info.duration); + println!( + "Track: {}, Artist: {}, Album: {}", + info.track_name, info.artist_name, info.album_name + ); match json_parsing::get_lyrics_text( &info.artist_name, @@ -71,6 +73,7 @@ pub fn get_song_file(path: &Path) -> Result<(), MusicError> { json_parsing::LyricsResult::Synced(lyrics) | json_parsing::LyricsResult::Plain(lyrics) => { json_parsing::save_lyrics_file(absolute_path, lyrics); + println!("Downloaded lyrics") } json_parsing::LyricsResult::Instrumental => { println!("Instrumental track — no lyrics available"); @@ -98,18 +101,42 @@ pub fn get_song_file(path: &Path) -> Result<(), MusicError> { Ok(()) } -pub fn get_all_music_files(directory: &Path) -> Result<(), MusicError> { +pub fn get_all_music_files(directory: &Path, watch: bool) -> Result<(), MusicError> { if !directory.is_dir() { return Ok(()); } - for entry in fs::read_dir(directory)? { - let entry = entry?; - let path = entry.path(); + let absolute = fs::canonicalize(directory).unwrap(); - if entry.file_type()?.is_dir() { - get_all_music_files(&path)?; - } else if let Err(e) = get_song_file(&path) { + let already_processed_files: HashSet = + json_parsing::return_all_processed_files(&absolute).unwrap(); + + let files_to_process: Vec = if watch { + get_new_files(&absolute)? + } else { + let mut all_files = Vec::new(); + for entry in fs::read_dir(&absolute)? { + let entry = entry?; + let path = entry.path(); + if entry.file_type()?.is_dir() { + get_all_music_files(&path, watch)?; // recursive call for subdirectories + } else { + all_files.push(path); + } + } + all_files + }; + + // Process files + for path in files_to_process { + let path_string = path.file_name().unwrap().to_string_lossy().to_string(); + + // Skip already processed files in full-run mode + if !watch && already_processed_files.contains(&path_string) { + continue; + } + + if let Err(e) = get_song_file(&path) { eprintln!("Skipping file {}: {}", path.display(), e); } } @@ -141,3 +168,22 @@ pub fn get_song_info(path: &Path) -> Result { pub fn get_song_length(path: &Path) -> Result { mp3_duration::from_path(path).map_err(|_| MusicError::DurationRead(path.display().to_string())) } + +pub fn get_new_files(path: &Path) -> io::Result> { + // Get the already processed files + let processed: HashSet = json_parsing::return_all_processed_files(path)?; + + // Read all files in the directory + let mut new_files = Vec::new(); + for entry in fs::read_dir(path)? { + let entry = entry?; + let file_name = entry.file_name().to_string_lossy().to_string(); + + // Only include files not in processed set + if !processed.contains(&file_name) && entry.path().is_file() { + new_files.push(entry.path()); + } + } + + Ok(new_files) +}