detect file/directory removal and replacing a file with a directory or a directory with a file

also change changes/actions print style for brevity
This commit is contained in:
Mark 2023-10-05 15:39:33 +02:00
parent 90f0a8ded4
commit f4cd0851dd
5 changed files with 74 additions and 18 deletions

View File

@ -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 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. 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. 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

View File

@ -53,6 +53,24 @@ pub fn apply_indexchanges_int(
} }
fs::write(&index.join(file), index_file.save())?; 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; let i = i + 1;

View File

@ -8,4 +8,8 @@ pub enum IndexChange {
AddDir(PathBuf), AddDir(PathBuf),
/// Add or update a file /// Add or update a file
AddFile(PathBuf, IndexFile), AddFile(PathBuf, IndexFile),
/// Remove a file
RemoveFile(PathBuf),
/// Remove a directory (recursively)
RemoveDir(PathBuf),
} }

View File

@ -29,25 +29,28 @@ fn main() {
if changes.is_empty() { if changes.is_empty() {
eprintln!("done! found no changes."); eprintln!("done! found no changes.");
} else { } else {
eprintln!("done! found {} changes.", changes.len()); eprintln!("done! found {} changes:", changes.len());
// display the changes // display the changes
eprintln!(" - - - - -");
for change in &changes { for change in &changes {
match change { match change {
IndexChange::AddDir(v) => eprintln!(" - Add the directory {v:?}"), IndexChange::AddDir(v) => eprintln!(" >> {}", v.display()),
IndexChange::AddFile(v, _) => eprintln!(" - Add the file {v:?}"), IndexChange::AddFile(v, _) => eprintln!(" + {}", v.display()),
IndexChange::RemoveFile(v) => eprintln!(" - {}", v.display()),
IndexChange::RemoveDir(v) => eprintln!(" [-] {}", v.display()),
} }
} }
eprintln!( eprintln!(" - - - - -");
"Press Enter to add these {} changes to the backup.", eprintln!(" >> add directory");
changes.len() eprintln!(" + add/update file");
); eprintln!(" - remove file");
eprintln!(" [-] remove directory (and all contents!)");
eprintln!("Press Enter to to apply these actions.");
// apply changes // apply changes
if std::io::stdin().read_line(&mut String::new()).is_ok() { if std::io::stdin().read_line(&mut String::new()).is_ok() {
match apply_indexchanges(&args.source, &args.index, &args.target, &changes) { match apply_indexchanges(&args.source, &args.index, &args.target, &changes) {
Ok(()) => {} Ok(()) => {}
Err(e) => { Err(e) => {
eprintln!("Failed to apply index changes: {e}"); eprintln!("Failed to apply: {e}");
exit(30); exit(30);
} }
} }

View File

@ -1,4 +1,4 @@
use std::{fs, io, path::Path}; use std::{collections::HashMap, fs, io, path::Path};
use crate::{indexchanges::IndexChange, indexfile::IndexFile}; use crate::{indexchanges::IndexChange, indexfile::IndexFile};
@ -14,10 +14,15 @@ pub fn perform_index_diff(source: &Path, index: &Path) -> io::Result<Vec<IndexCh
Ok(changes) Ok(changes)
} }
fn rec( fn rec(
// location of source files
source: &Path, source: &Path,
// relative path used on this iteration
rel_path: &Path, rel_path: &Path,
// location of the index
index_files: &Path, index_files: &Path,
// list of changes to be made
changes: &mut Vec<IndexChange>, changes: &mut Vec<IndexChange>,
// if the index is part of `source`, where exactly is it?
inner_index: Option<&Path>, inner_index: Option<&Path>,
) -> Result<(), io::Error> { ) -> Result<(), io::Error> {
if let Some(ii) = &inner_index { if let Some(ii) = &inner_index {
@ -27,13 +32,31 @@ fn rec(
} }
} }
if !index_files.join(rel_path).try_exists()? { // used to find removals
changes.push(IndexChange::AddDir(rel_path.to_path_buf())); let index_rel_path = index_files.join(rel_path);
} let mut index_entries = match fs::read_dir(&index_rel_path) {
for entry in fs::read_dir(source.join(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::<Result<_, io::Error>>()?,
};
// compare source files with index
let source_files = fs::read_dir(source.join(rel_path))?.collect::<Vec<_>>();
// find changes/adds
for entry in source_files {
let entry = entry?; let entry = entry?;
let metadata = entry.metadata()?; let metadata = entry.metadata()?;
let in_index_and_is_dir = index_entries.remove(&entry.file_name());
if metadata.is_dir() { 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( rec(
source, source,
&rel_path.join(entry.file_name()), &rel_path.join(entry.file_name()),
@ -42,6 +65,10 @@ fn rec(
inner_index, inner_index,
)?; )?;
} else { } 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 newif = IndexFile::new_from_metadata(&metadata);
let oldif = IndexFile::from_path(&index_files.join(rel_path).join(entry.file_name())); let oldif = IndexFile::from_path(&index_files.join(rel_path).join(entry.file_name()));
match oldif { 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(()) Ok(())
} }