2024-06-29 15:36:48 +02:00
use std ::{
collections ::HashMap ,
fs , io ,
path ::{ Path , PathBuf } ,
} ;
2023-09-13 01:16:47 +02:00
2024-03-17 13:45:03 +01:00
use clap ::Args ;
2024-06-27 10:59:04 +02:00
use crate ::{
config ::{ FsEntry , Ignore , Match , Specifier } ,
indexchanges ::IndexChange ,
indexfile ::IndexFile ,
} ;
2023-09-13 01:16:47 +02:00
2024-03-17 13:45:03 +01:00
#[ derive(Clone, Default, Args) ]
pub struct Settings {
/// don't update files just because their timestamp is different
#[ arg(long) ]
pub ignore_timestamp : bool ,
/// keep *newer* files when the files in the source are *older*
#[ arg(long) ]
pub dont_replace_newer : bool ,
/// replace files if their timestamp is unknown in both source and index
#[ arg(long) ]
pub replace_if_timestamp_unknown : bool ,
/// replace files if their timestamp is unknown in source but known in index
#[ arg(long) ]
pub replace_if_timestamp_lost : bool ,
/// don't replace files if their timestamp is known in source but unknown in index
#[ arg(long) ]
pub dont_replace_if_timestamp_found : bool ,
}
2023-11-04 15:11:20 +01:00
pub fn perform_index_diff < ' a > (
source : & Path ,
index : & ' a Path ,
2024-06-27 10:59:04 +02:00
target : Option < & ' a Path > ,
mut ignore : Ignore ,
2024-03-17 13:45:03 +01:00
settings : & Settings ,
2024-06-29 15:36:48 +02:00
) -> Result < Vec < IndexChange > , ( String , PathBuf , io ::Error ) > {
2023-09-13 01:16:47 +02:00
let mut changes = Vec ::new ( ) ;
2023-11-04 15:11:20 +01:00
if let Ok ( inner_index ) = index . strip_prefix ( source ) {
2024-06-27 10:59:04 +02:00
eprintln! ( " [info] source contains index at {inner_index:?} , but index will not be part of the backup. " ) ;
ignore . 0. push ( Specifier ::InDir {
dir : Match ::Eq ( inner_index . to_owned ( ) ) ,
inner : Ignore ( vec! [ ] ) ,
} ) ;
}
if let Some ( target ) = target {
if let Ok ( inner_target ) = target . strip_prefix ( source ) {
eprintln! ( " [info] source contains target at {inner_target:?} , but target will not be part of the backup. " ) ;
ignore . 0. push ( Specifier ::InDir {
dir : Match ::Eq ( inner_target . to_owned ( ) ) ,
inner : Ignore ( vec! [ ] ) ,
} ) ;
}
2023-11-04 15:11:20 +01:00
}
2023-09-13 01:16:47 +02:00
rec (
source . as_ref ( ) ,
Path ::new ( " " ) ,
index ,
& mut changes ,
2024-06-27 10:59:04 +02:00
& ignore ,
2024-03-17 13:45:03 +01:00
settings ,
2023-09-13 01:16:47 +02:00
) ? ;
Ok ( changes )
}
fn rec (
2023-10-05 15:39:33 +02:00
// location of source files
2023-09-13 01:16:47 +02:00
source : & Path ,
2023-10-05 15:39:33 +02:00
// relative path used on this iteration
2023-09-13 01:16:47 +02:00
rel_path : & Path ,
2023-10-05 15:39:33 +02:00
// location of the index
2023-09-13 01:16:47 +02:00
index_files : & Path ,
2023-10-05 15:39:33 +02:00
// list of changes to be made
2023-09-13 01:16:47 +02:00
changes : & mut Vec < IndexChange > ,
2024-06-27 10:59:04 +02:00
ignore : & Ignore ,
2024-03-17 13:45:03 +01:00
settings : & Settings ,
2024-06-29 15:36:48 +02:00
) -> Result < ( ) , ( String , PathBuf , io ::Error ) > {
2023-10-05 15:39:33 +02:00
// used to find removals
let index_rel_path = index_files . join ( rel_path ) ;
let mut index_entries = match fs ::read_dir ( & index_rel_path ) {
Err ( _ ) = > {
changes . push ( IndexChange ::AddDir ( rel_path . to_path_buf ( ) ) ) ;
HashMap ::new ( )
}
Ok ( e ) = > e
. into_iter ( )
. filter_map ( | v | v . ok ( ) )
2024-06-29 15:36:48 +02:00
. map ( | v | {
Ok ( (
v . file_name ( ) ,
v . file_type ( )
. map_err ( | e | ( " getting file type " . to_owned ( ) , v . path ( ) , e ) ) ?
. is_dir ( ) ,
) )
} )
. collect ::< Result < _ , ( String , PathBuf , io ::Error ) > > ( ) ? ,
2023-10-05 15:39:33 +02:00
} ;
// compare source files with index
2024-06-29 15:36:48 +02:00
let source_files_path = source . join ( rel_path ) ;
let source_files = fs ::read_dir ( & source_files_path )
. map_err ( | e | ( " getting entries " . to_owned ( ) , source_files_path . clone ( ) , e ) ) ?
. collect ::< Vec < _ > > ( ) ;
2023-10-05 15:39:33 +02:00
// find changes/adds
for entry in source_files {
2024-06-29 15:36:48 +02:00
let entry = entry . map_err ( | e | {
(
" error with an entry within this directory " . to_owned ( ) ,
source_files_path . clone ( ) ,
e ,
)
} ) ? ;
2023-11-04 15:44:22 +01:00
let rel_path = rel_path . join ( entry . file_name ( ) ) ;
2024-06-29 15:36:48 +02:00
let metadata = entry . metadata ( ) ;
2024-06-27 10:59:04 +02:00
2023-11-04 15:44:22 +01:00
// ignore entries
2024-06-27 10:59:04 +02:00
let fs_entry = FsEntry {
path : & rel_path ,
2024-06-29 15:36:48 +02:00
is_directory : metadata . as_ref ( ) . ok ( ) . map ( | v | v . is_dir ( ) ) ,
2024-06-27 10:59:04 +02:00
} ;
if ignore . matches_or_default ( & fs_entry ) {
2023-11-04 15:44:22 +01:00
continue ;
}
2024-06-29 15:36:48 +02:00
let metadata = metadata . map_err ( | e | ( " getting metadata (you have to ignore this using a * pattern instead of + or /, because we don't know if it's a directory or not) " . to_owned ( ) , entry . path ( ) , e ) ) ? ;
2023-10-05 15:39:33 +02:00
let in_index_and_is_dir = index_entries . remove ( & entry . file_name ( ) ) ;
2023-09-13 01:16:47 +02:00
if metadata . is_dir ( ) {
2023-10-05 15:39:33 +02:00
if let Some ( false ) = in_index_and_is_dir {
// is dir, but was file -> remove file
2023-11-04 15:44:22 +01:00
changes . push ( IndexChange ::RemoveFile ( rel_path . clone ( ) ) ) ;
2023-10-05 15:39:33 +02:00
}
2024-06-27 10:59:04 +02:00
rec ( source , & rel_path , index_files , changes , ignore , settings ) ? ;
2023-09-13 01:16:47 +02:00
} else {
2023-10-05 15:39:33 +02:00
if let Some ( true ) = in_index_and_is_dir {
// is file, but was dir -> remove dir
2023-11-04 15:44:22 +01:00
changes . push ( IndexChange ::RemoveDir ( rel_path . clone ( ) ) ) ;
2023-10-05 15:39:33 +02:00
}
2023-09-13 01:16:47 +02:00
let newif = IndexFile ::new_from_metadata ( & metadata ) ;
2023-11-04 15:44:22 +01:00
let oldif = IndexFile ::from_path ( & index_files . join ( & rel_path ) ) ;
2023-09-13 01:16:47 +02:00
match oldif {
2024-03-17 13:45:03 +01:00
Ok ( Ok ( oldif ) ) if ! newif . should_be_updated ( & oldif , settings ) = > { }
2023-11-04 15:44:22 +01:00
_ = > changes . push ( IndexChange ::AddFile ( rel_path , newif ) ) ,
2023-09-13 01:16:47 +02:00
}
}
}
2023-10-05 15:39:33 +02:00
// removals
for ( removed_file , is_dir ) in index_entries {
changes . push ( if is_dir {
IndexChange ::RemoveDir ( rel_path . join ( removed_file ) )
} else {
IndexChange ::RemoveFile ( rel_path . join ( removed_file ) )
} ) ;
}
2023-09-13 01:16:47 +02:00
Ok ( ( ) )
}