2023-10-05 15:39:33 +02:00
|
|
|
use std::{collections::HashMap, fs, io, path::Path};
|
2023-09-13 01:16:47 +02:00
|
|
|
|
2024-03-17 13:45:03 +01:00
|
|
|
use clap::Args;
|
|
|
|
|
2023-09-13 01:16:47 +02:00
|
|
|
use crate::{indexchanges::IndexChange, indexfile::IndexFile};
|
|
|
|
|
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,
|
2023-11-04 15:44:22 +01:00
|
|
|
mut ignore_paths: Vec<&'a Path>,
|
2024-03-17 13:45:03 +01:00
|
|
|
settings: &Settings,
|
2023-11-04 15:11:20 +01:00
|
|
|
) -> io::Result<Vec<IndexChange>> {
|
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) {
|
|
|
|
eprintln!("[info] source contains index, but index will not be part of the backup.");
|
2023-11-04 15:44:22 +01:00
|
|
|
ignore_paths.push(inner_index);
|
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,
|
2023-11-04 15:44:22 +01:00
|
|
|
&ignore_paths,
|
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>,
|
2023-11-04 15:44:22 +01:00
|
|
|
ignore_paths: &Vec<&Path>,
|
2024-03-17 13:45:03 +01:00
|
|
|
settings: &Settings,
|
2023-09-13 01:16:47 +02:00
|
|
|
) -> Result<(), 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())
|
|
|
|
.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 {
|
2023-09-13 01:16:47 +02:00
|
|
|
let entry = entry?;
|
2023-11-04 15:44:22 +01:00
|
|
|
let rel_path = rel_path.join(entry.file_name());
|
|
|
|
// ignore entries
|
|
|
|
if ignore_paths.iter().any(|ii| &rel_path == ii) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-09-13 01:16:47 +02:00
|
|
|
let metadata = entry.metadata()?;
|
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-03-17 13:45:03 +01:00
|
|
|
rec(
|
|
|
|
source,
|
|
|
|
&rel_path,
|
|
|
|
index_files,
|
|
|
|
changes,
|
|
|
|
ignore_paths,
|
|
|
|
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(())
|
|
|
|
}
|