diff --git a/README.md b/README.md index ff56397..a04d77b 100755 --- a/README.md +++ b/README.md @@ -49,7 +49,3 @@ Note 2: `~/index` and `/mnt/backup` don't need to exist yet - they will be creat If this is the first backup, you can try to maximize the speed of `/mnt/backup`. If you want remote backups, you should probably connect the server's disk directly to your computer. The backups after the initial one will be a lot faster, so you can switch to remote backups after this. - -## TODO - -detect files that have been removed diff --git a/src/apply_indexchanges.rs b/src/apply_indexchanges.rs index 306e542..f92f881 100755 --- a/src/apply_indexchanges.rs +++ b/src/apply_indexchanges.rs @@ -53,6 +53,24 @@ pub fn apply_indexchanges_int( } fs::write(&index.join(file), index_file.save())?; } + IndexChange::RemoveFile(file) => { + let i = index.join(file); + let t = target.join(file); + if let Err(e) = fs::remove_file(&t) { + eprintln!("\n[warn] couldn't remove file {t:?}, keeping index file {i:?}: {e:?}\n If this error keeps appearing, check if the file was deleted on the target system but still exists in the index. if yes, consider manually deleting it."); + } else { + fs::remove_file(i)?; + } + } + IndexChange::RemoveDir(dir) => { + let i = index.join(dir); + let t = target.join(dir); + if let Err(e) = fs::remove_dir_all(&t) { + eprintln!("\n[warn] couldn't remove directory {t:?}, keeping index files under {i:?}: {e:?}\n If this error keeps appearing, check if the directory was deleted on the target system but still exists in the index. if yes, consider manually deleting it."); + } else { + fs::remove_dir_all(i)?; + } + } } { let i = i + 1; diff --git a/src/indexchanges.rs b/src/indexchanges.rs index a77508d..09e2844 100755 --- a/src/indexchanges.rs +++ b/src/indexchanges.rs @@ -8,4 +8,8 @@ pub enum IndexChange { AddDir(PathBuf), /// Add or update a file AddFile(PathBuf, IndexFile), + /// Remove a file + RemoveFile(PathBuf), + /// Remove a directory (recursively) + RemoveDir(PathBuf), } diff --git a/src/main.rs b/src/main.rs index 96702d8..966a830 100755 --- a/src/main.rs +++ b/src/main.rs @@ -29,25 +29,28 @@ fn main() { if changes.is_empty() { eprintln!("done! found no changes."); } else { - eprintln!("done! found {} changes.", changes.len()); + eprintln!("done! found {} changes:", changes.len()); // display the changes - eprintln!(" - - - - -"); for change in &changes { match change { - IndexChange::AddDir(v) => eprintln!(" - Add the directory {v:?}"), - IndexChange::AddFile(v, _) => eprintln!(" - Add the file {v:?}"), + IndexChange::AddDir(v) => eprintln!(" >> {}", v.display()), + IndexChange::AddFile(v, _) => eprintln!(" + {}", v.display()), + IndexChange::RemoveFile(v) => eprintln!(" - {}", v.display()), + IndexChange::RemoveDir(v) => eprintln!(" [-] {}", v.display()), } } - eprintln!( - "Press Enter to add these {} changes to the backup.", - changes.len() - ); + eprintln!(" - - - - -"); + eprintln!(" >> add directory"); + eprintln!(" + add/update file"); + eprintln!(" - remove file"); + eprintln!(" [-] remove directory (and all contents!)"); + eprintln!("Press Enter to to apply these actions."); // apply changes if std::io::stdin().read_line(&mut String::new()).is_ok() { match apply_indexchanges(&args.source, &args.index, &args.target, &changes) { Ok(()) => {} Err(e) => { - eprintln!("Failed to apply index changes: {e}"); + eprintln!("Failed to apply: {e}"); exit(30); } } diff --git a/src/update_index.rs b/src/update_index.rs index 4ed051f..03def2c 100755 --- a/src/update_index.rs +++ b/src/update_index.rs @@ -1,4 +1,4 @@ -use std::{fs, io, path::Path}; +use std::{collections::HashMap, fs, io, path::Path}; use crate::{indexchanges::IndexChange, indexfile::IndexFile}; @@ -14,10 +14,15 @@ pub fn perform_index_diff(source: &Path, index: &Path) -> io::Result, + // if the index is part of `source`, where exactly is it? inner_index: Option<&Path>, ) -> Result<(), io::Error> { if let Some(ii) = &inner_index { @@ -27,13 +32,31 @@ fn rec( } } - if !index_files.join(rel_path).try_exists()? { - changes.push(IndexChange::AddDir(rel_path.to_path_buf())); - } - for entry in fs::read_dir(source.join(rel_path))? { + // 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()) + .map(|v| Ok((v.file_name(), v.file_type()?.is_dir()))) + .collect::>()?, + }; + // compare source files with index + let source_files = fs::read_dir(source.join(rel_path))?.collect::>(); + // find changes/adds + for entry in source_files { let entry = entry?; let metadata = entry.metadata()?; + let in_index_and_is_dir = index_entries.remove(&entry.file_name()); if metadata.is_dir() { + if let Some(false) = in_index_and_is_dir { + // is dir, but was file -> remove file + changes.push(IndexChange::RemoveFile(rel_path.join(entry.file_name()))); + } rec( source, &rel_path.join(entry.file_name()), @@ -42,6 +65,10 @@ fn rec( inner_index, )?; } else { + if let Some(true) = in_index_and_is_dir { + // is file, but was dir -> remove dir + changes.push(IndexChange::RemoveDir(rel_path.join(entry.file_name()))); + } let newif = IndexFile::new_from_metadata(&metadata); let oldif = IndexFile::from_path(&index_files.join(rel_path).join(entry.file_name())); match oldif { @@ -53,5 +80,13 @@ fn rec( } } } + // 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)) + }); + } Ok(()) }