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

View File

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

View File

@ -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),
}

View File

@ -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);
}
}

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};
@ -14,10 +14,15 @@ pub fn perform_index_diff(source: &Path, index: &Path) -> io::Result<Vec<IndexCh
Ok(changes)
}
fn rec(
// location of source files
source: &Path,
// relative path used on this iteration
rel_path: &Path,
// location of the index
index_files: &Path,
// list of changes to be made
changes: &mut Vec<IndexChange>,
// 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::<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 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(())
}