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 {
2024-12-24 01:37:18 +01:00
/// don't sort the changes that form the backup. disables sort options.
#[ arg(long) ]
pub dont_sort : bool ,
/// start with smaller directories rather than larger ones
#[ arg(long) ]
pub smallest_first : bool ,
/// show changes in the order in which they will be applied, not reversed
#[ arg(long) ]
pub dont_reverse_output : bool ,
2024-03-17 13:45:03 +01:00
/// 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-12-24 01:37:18 +01:00
sort_by_size_largest : Option < bool > ,
) -> Result < ( u64 , Vec < IndexChange > ) , ( String , PathBuf , io ::Error ) > {
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
}
2024-12-24 01:37:18 +01:00
let ( total_size , changes ) = rec (
2023-09-13 01:16:47 +02:00
source . as_ref ( ) ,
Path ::new ( " " ) ,
index ,
2024-06-27 10:59:04 +02:00
& ignore ,
2024-03-17 13:45:03 +01:00
settings ,
2024-12-24 01:37:18 +01:00
sort_by_size_largest ,
2023-09-13 01:16:47 +02:00
) ? ;
2024-12-24 01:37:18 +01:00
Ok ( ( total_size , changes ) )
2023-09-13 01:16:47 +02:00
}
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 ,
2024-06-27 10:59:04 +02:00
ignore : & Ignore ,
2024-03-17 13:45:03 +01:00
settings : & Settings ,
2024-12-24 01:37:18 +01:00
sort_by_size_largest : Option < bool > ,
) -> Result < ( u64 , Vec < IndexChange > ) , ( String , PathBuf , io ::Error ) > {
let mut removals = vec! [ ] ;
let mut ichanges = vec! [ ] ;
let mut total_size = 0 ;
2023-10-05 15:39:33 +02:00
// used to find removals
let index_rel_path = index_files . join ( rel_path ) ;
2024-12-24 02:02:51 +01:00
let ( mut index_entries , dir_is_new ) = match fs ::read_dir ( & index_rel_path ) {
Err ( _ ) = > ( HashMap ::new ( ) , true ) ,
Ok ( e ) = > (
e . into_iter ( )
. filter_map ( | v | v . ok ( ) )
. 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 ) > > ( ) ? ,
false ,
) ,
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
2024-12-24 01:37:18 +01:00
removals . push ( IndexChange ::RemoveFile ( rel_path . clone ( ) ) ) ;
2023-10-05 15:39:33 +02:00
}
2024-12-24 01:37:18 +01:00
let ( rec_size , rec_changes ) = rec (
source ,
& rel_path ,
index_files ,
ignore ,
settings ,
sort_by_size_largest ,
) ? ;
total_size + = rec_size ;
ichanges . push ( ( rec_size , rec_changes ) ) ;
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
2024-12-24 01:37:18 +01:00
removals . 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 ) = > { }
2024-12-24 01:37:18 +01:00
_ = > {
total_size + = newif . size ;
ichanges . push ( ( newif . size , vec! [ 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 {
2024-12-24 01:37:18 +01:00
removals . push ( if is_dir {
2023-10-05 15:39:33 +02:00
IndexChange ::RemoveDir ( rel_path . join ( removed_file ) )
} else {
IndexChange ::RemoveFile ( rel_path . join ( removed_file ) )
} ) ;
}
2024-12-24 01:37:18 +01:00
// sorting
if let Some ( sort_largest_first ) = sort_by_size_largest {
if sort_largest_first {
ichanges . sort_by ( | a , b | b . 0. cmp ( & a . 0 ) ) ;
} else {
ichanges . sort_by_key ( | v | v . 0 ) ;
}
}
// combine everything
2024-12-24 02:02:51 +01:00
let changes = [ IndexChange ::AddDir (
rel_path . to_path_buf ( ) ,
dir_is_new ,
total_size ,
) ]
. into_iter ( )
. chain ( removals . into_iter ( ) )
. chain ( ichanges . into_iter ( ) . flat_map ( | ( _ , v ) | v ) )
. collect ( ) ;
2024-12-24 01:37:18 +01:00
Ok ( ( total_size , changes ) )
2023-09-13 01:16:47 +02:00
}