@@ -10,6 +10,14 @@ use std::{
1010 time:: Instant ,
1111} ;
1212
13+ #[ derive( Debug , Clone , Hash , PartialEq , Eq ) ]
14+ struct MetadataKey {
15+ artist : String ,
16+ album : String ,
17+ title : String ,
18+ track : Option < u16 > ,
19+ }
20+
1321#[ derive( Debug ) ]
1422pub struct OrganizeResult {
1523 pub moved : usize ,
@@ -32,13 +40,6 @@ pub fn organize_music_files(
3240 } ) ;
3341 }
3442
35- let thread_count = rayon:: current_num_threads ( ) ;
36- println ! (
37- " Organizing {} files using {} threads" ,
38- music_files. len( ) ,
39- thread_count
40- ) ;
41-
4243 let start_time = Instant :: now ( ) ;
4344
4445 let pb = Arc :: new ( ProgressBar :: new ( music_files. len ( ) as u64 ) ) ;
@@ -53,14 +54,24 @@ pub fn organize_music_files(
5354 let failed = Arc :: new ( Mutex :: new ( 0 ) ) ;
5455 let duplicates = Arc :: new ( Mutex :: new ( 0 ) ) ;
5556
56- let used_paths: Arc < Mutex < HashMap < PathBuf , PathBuf > > > = Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ;
57+ let used_metadata: Arc < Mutex < HashMap < MetadataKey , PathBuf > > > =
58+ Arc :: new ( Mutex :: new ( HashMap :: new ( ) ) ) ;
59+
60+ if let Ok ( existing_files) = crate :: scan:: scan_for_music ( output_dir) {
61+ let mut metadata_map = used_metadata. lock ( ) . unwrap ( ) ;
62+ for ( path, metadata) in existing_files {
63+ if let Some ( metadata_key) = create_metadata_key ( & metadata) {
64+ metadata_map. insert ( metadata_key, path) ;
65+ }
66+ }
67+ }
5768
5869 music_files. par_iter ( ) . for_each ( |( source_path, metadata) | {
5970 if let Some ( filename) = source_path. file_name ( ) {
6071 pb. set_message ( filename. to_string_lossy ( ) . to_string ( ) ) ;
6172 }
6273
63- match organize_single_file ( source_path, metadata, output_dir, config, & used_paths ) {
74+ match organize_single_file ( source_path, metadata, output_dir, config, & used_metadata ) {
6475 Ok ( result) => match result {
6576 FileResult :: Moved => {
6677 * moved. lock ( ) . unwrap ( ) += 1 ;
@@ -120,7 +131,7 @@ fn organize_single_file(
120131 metadata : & AudioMetadata ,
121132 output_dir : & PathBuf ,
122133 config : & Config ,
123- used_paths : & Arc < Mutex < HashMap < PathBuf , PathBuf > > > ,
134+ used_metadata : & Arc < Mutex < HashMap < MetadataKey , PathBuf > > > ,
124135) -> Result < FileResult , Box < dyn std:: error:: Error > > {
125136 let relative_path = match generate_target_path ( source_path, metadata, config) {
126137 Some ( path) => path,
@@ -136,19 +147,55 @@ fn organize_single_file(
136147 let target_path = output_dir. join ( & relative_path) ;
137148
138149 let final_target_path = {
139- let mut paths_map = used_paths. lock ( ) . unwrap ( ) ;
140- if paths_map. contains_key ( & target_path) {
141- match config. rules . handle_duplicates . as_str ( ) {
142- "skip" => {
143- return Ok ( FileResult :: Duplicate ) ;
150+ let metadata_key = create_metadata_key ( metadata) ;
151+ let mut metadata_map = used_metadata. lock ( ) . unwrap ( ) ;
152+
153+ if let Some ( metadata_key) = metadata_key {
154+ if metadata_map. contains_key ( & metadata_key) {
155+ match config. rules . handle_duplicates . as_str ( ) {
156+ "skip" => {
157+ return Ok ( FileResult :: Duplicate ) ;
158+ }
159+ "rename" => {
160+ handle_duplicate_rename ( & target_path, & metadata_key, & mut metadata_map)
161+ }
162+ "overwrite" => {
163+ if let Some ( old_path) = metadata_map. get ( & metadata_key) {
164+ let _ = fs:: remove_file ( old_path) ;
165+ }
166+ metadata_map. insert ( metadata_key, source_path. clone ( ) ) ;
167+ target_path
168+ }
169+ _ => target_path,
144170 }
145- "rename" => handle_duplicate_rename ( & target_path , & mut paths_map ) ,
146- "overwrite" => target_path ,
147- _ => target_path,
171+ } else {
172+ metadata_map . insert ( metadata_key , source_path . clone ( ) ) ;
173+ target_path
148174 }
149175 } else {
150- paths_map. insert ( target_path. clone ( ) , source_path. clone ( ) ) ;
151- target_path
176+ let fallback_key = MetadataKey {
177+ artist : "Unknown" . to_string ( ) ,
178+ album : "Unknown" . to_string ( ) ,
179+ title : source_path
180+ . file_stem ( )
181+ . unwrap_or_default ( )
182+ . to_string_lossy ( )
183+ . to_string ( ) ,
184+ track : None ,
185+ } ;
186+
187+ if metadata_map. contains_key ( & fallback_key) {
188+ match config. rules . handle_duplicates . as_str ( ) {
189+ "skip" => return Ok ( FileResult :: Duplicate ) ,
190+ "rename" => {
191+ handle_duplicate_rename ( & target_path, & fallback_key, & mut metadata_map)
192+ }
193+ _ => target_path,
194+ }
195+ } else {
196+ metadata_map. insert ( fallback_key, source_path. clone ( ) ) ;
197+ target_path
198+ }
152199 }
153200 } ;
154201
@@ -324,7 +371,8 @@ fn sanitize_path(path: &str, config: &Config) -> String {
324371
325372fn handle_duplicate_rename (
326373 target_path : & PathBuf ,
327- used_paths : & mut HashMap < PathBuf , PathBuf > ,
374+ metadata_key : & MetadataKey ,
375+ used_metadata : & mut HashMap < MetadataKey , PathBuf > ,
328376) -> PathBuf {
329377 let mut counter = 1 ;
330378 let stem = target_path
@@ -341,14 +389,36 @@ fn handle_duplicate_rename(
341389 let new_filename = format ! ( "{} ({}){}" , stem, counter, extension) ;
342390 let new_path = parent. join ( new_filename) ;
343391
344- if !used_paths. contains_key ( & new_path) {
345- used_paths. insert ( new_path. clone ( ) , target_path. clone ( ) ) ;
392+ let mut new_metadata_key = metadata_key. clone ( ) ;
393+ new_metadata_key. title = format ! ( "{} ({})" , metadata_key. title, counter) ;
394+
395+ if !used_metadata. contains_key ( & new_metadata_key) {
396+ used_metadata. insert ( new_metadata_key, new_path. clone ( ) ) ;
346397 return new_path;
347398 }
348399 counter += 1 ;
349400 }
350401}
351402
403+ fn create_metadata_key ( metadata : & AudioMetadata ) -> Option < MetadataKey > {
404+ let artist = if is_compilation ( metadata) {
405+ metadata. artist . as_ref ( )
406+ } else {
407+ metadata. album_artist . as_ref ( ) . or ( metadata. artist . as_ref ( ) )
408+ } ;
409+
410+ let artist = artist?;
411+ let album = metadata. album . as_ref ( ) ?;
412+ let title = metadata. title . as_ref ( ) ?;
413+
414+ Some ( MetadataKey {
415+ artist : artist. clone ( ) ,
416+ album : album. clone ( ) ,
417+ title : title. clone ( ) ,
418+ track : metadata. track ,
419+ } )
420+ }
421+
352422fn is_compilation ( metadata : & AudioMetadata ) -> bool {
353423 if let Some ( album_artist) = & metadata. album_artist {
354424 album_artist. to_lowercase ( ) == "various artists"
0 commit comments