Compare commits

..

No commits in common. "5ac3d9aee092a1d061d1635a117feaefd4464333" and "ed090e5962317ffdf5ac4032a62de754d47c6e89" have entirely different histories.

5 changed files with 46 additions and 116 deletions

View File

@ -1,7 +1,7 @@
[package] [package]
name = "rembackup" name = "rembackup"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

View File

@ -1,5 +1,5 @@
use std::{ use std::{
fs, fs, io,
path::{Path, PathBuf}, path::{Path, PathBuf},
}; };
@ -12,25 +12,12 @@ pub fn apply_indexchanges(
source: &Path, source: &Path,
index: &Path, index: &Path,
target: &Option<PathBuf>, target: &Option<PathBuf>,
changes: &[IndexChange], changes: &Vec<IndexChange>,
gib_total: Option<f64>, gib_total: Option<f64>,
) -> usize { ) -> io::Result<()> {
// do symlinks last, as they cd, which can fail, let o = apply_indexchanges_int(source, index, target, changes, gib_total);
// and if it does, it would be fatal and stop the backup.
let (mut changes, symlink_additions) =
changes.into_iter().partition::<Vec<_>, _>(|c| match c {
IndexChange::AddDir(..)
| IndexChange::AddFile(..)
| IndexChange::RemoveFile(..)
| IndexChange::RemoveDir(..) => true,
IndexChange::AddSymlink(..) => false,
});
changes.extend(symlink_additions);
let mut failures = changes.len();
apply_indexchanges_int(source, index, target, &changes, gib_total, &mut failures);
eprintln!(); eprintln!();
failures o
} }
fn eprint_constants(changes_total: usize, gib_total: f64) -> (usize, usize, usize) { fn eprint_constants(changes_total: usize, gib_total: f64) -> (usize, usize, usize) {
@ -76,6 +63,7 @@ fn eprint_status(
let pending_prog = " ".repeat(rightpad); let pending_prog = " ".repeat(rightpad);
eprint!( eprint!(
"\r{changes_pad}{changes_applied}/{changes_total} | {gib_pad}{gib_transferred}/{gib_total:.1}GiB [{completed_prog_min}{completed_prog_max}>{pending_prog}]", "\r{changes_pad}{changes_applied}/{changes_total} | {gib_pad}{gib_transferred}/{gib_total:.1}GiB [{completed_prog_min}{completed_prog_max}>{pending_prog}]",
); );
} }
@ -83,10 +71,9 @@ pub fn apply_indexchanges_int(
source: &Path, source: &Path,
index: &Path, index: &Path,
target: &Option<PathBuf>, target: &Option<PathBuf>,
changes: &[&IndexChange], changes: &Vec<IndexChange>,
gib_total: Option<f64>, gib_total: Option<f64>,
failures: &mut usize, ) -> io::Result<()> {
) {
let changes_total = changes.len(); let changes_total = changes.len();
let gib_total = gib_total.unwrap_or_else(|| { let gib_total = gib_total.unwrap_or_else(|| {
changes changes
@ -100,7 +87,7 @@ pub fn apply_indexchanges_int(
}) })
.sum() .sum()
}); });
let (prog_width, changes_len_width, gib_len_width) = eprint_constants(changes_total, gib_total); let (prog_width, changes_len_width, gib_len_width) = eprint_constants(changes.len(), gib_total);
let mut gib_transferred = 0.0; let mut gib_transferred = 0.0;
eprint_status( eprint_status(
0, 0,
@ -127,14 +114,8 @@ pub fn apply_indexchanges_int(
true true
}; };
if ok { if ok {
*failures -= 1; fs::create_dir_all(&index.join(dir))?;
let t = index.join(dir);
if let Err(e) = fs::create_dir_all(&t) {
eprintln!("\n[warn] couldn't create index directory {t:?}: {e}");
}
} }
} else {
*failures -= 1;
} }
} }
IndexChange::AddFile(file, index_file) => { IndexChange::AddFile(file, index_file) => {
@ -152,42 +133,25 @@ pub fn apply_indexchanges_int(
true true
}; };
if ok { if ok {
*failures -= 1; fs::write(&index.join(file), index_file.save())?;
let t = index.join(file);
if let Err(e) = fs::write(&t, index_file.save()) {
eprintln!("\n[warn] couldn't save index file {t:?}: {e}");
}
} }
} }
IndexChange::AddSymlink(file, link_target) => { IndexChange::AddSymlink(file, link_target) => {
let cwd = match std::env::current_dir() { let cwd = std::env::current_dir()?;
Ok(cwd) => cwd,
Err(e) => {
eprintln!("\n[err] fatal: couldn't get cwd: {e}");
return;
}
};
let ok = if let Some(target) = target { let ok = if let Some(target) = target {
let t = target.join(file); let t = target.join(file);
if let Some(p) = t.parent() { if let Some(p) = t.parent() {
let t = t.file_name().expect("a file should always have a filename"); let t = t.file_name().expect("a file should always have a filename");
if let Err(e) = std::env::set_current_dir(&p) { std::env::set_current_dir(&p)?;
eprintln!("\n[warn] couldn't cd to {p:?}: {e}"); let _ = std::fs::remove_file(t);
false if let Err(e) = std::os::unix::fs::symlink(&link_target, t) {
} else { eprintln!(
let _ = std::fs::remove_file(t);
if let Err(e) = std::os::unix::fs::symlink(&link_target, t) {
eprintln!(
"\n[warn] couldn't set file {t:?} to be a symlink to {link_target:?}: {e}" "\n[warn] couldn't set file {t:?} to be a symlink to {link_target:?}: {e}"
); );
false false
} else { } else {
if let Err(e) = std::env::set_current_dir(&cwd) { std::env::set_current_dir(&cwd)?;
eprintln!("\n[err] fatal: couldn't cd back to {cwd:?}: {e}"); true
return;
}
true
}
} }
} else { } else {
eprintln!("\n[warn] symlink path was empty"); eprintln!("\n[warn] symlink path was empty");
@ -197,42 +161,27 @@ pub fn apply_indexchanges_int(
true true
}; };
if ok { if ok {
*failures -= 1;
let index_file = index.join(file); let index_file = index.join(file);
if let Some(p) = index_file.parent() { if let Some(p) = index_file.parent() {
if let Err(e) = std::env::set_current_dir(&p) { std::env::set_current_dir(&p)?;
eprintln!("\n[warn] couldn't cd to {p:?}: {e}"); std::os::unix::fs::symlink(
} else { link_target,
let index_file_name = index_file index_file
.file_name() .file_name()
.expect("a file should always have a filename"); .expect("a file should always have a filename"),
let _ = std::fs::remove_file(&index_file_name); )?;
if let Err(e) = std::os::unix::fs::symlink(link_target, index_file_name)
{
eprintln!(
"\n[warn] couldn't set index file {index_file:?} to be a symlink to {link_target:?}: {e}"
);
}
}
} else { } else {
eprintln!( eprintln!("\n[warn] couldn't get parent for index file's path, so could not create the symlink");
"\n[warn] couldn't get parent for index file's path, so could not create the symlink"
);
} }
} }
if let Err(e) = std::env::set_current_dir(&cwd) { std::env::set_current_dir(&cwd)?;
eprintln!("\n[err] fatal: couldn't cd back to {cwd:?}: {e}");
return;
}
} }
IndexChange::RemoveFile(file) => { IndexChange::RemoveFile(file) => {
let i = index.join(file); let i = index.join(file);
let ok = if let Some(target) = target { let ok = if let Some(target) = target {
let t = target.join(file); let t = target.join(file);
if let Err(e) = fs::remove_file(&t) { if let Err(e) = fs::remove_file(&t) {
eprintln!( 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.");
"\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."
);
false false
} else { } else {
true true
@ -241,10 +190,7 @@ pub fn apply_indexchanges_int(
true true
}; };
if ok { if ok {
*failures -= 1; fs::remove_file(i)?;
if let Err(e) = fs::remove_file(&i) {
eprintln!("\n[warn] couldn't remove index file {i:?}: {e:?}");
}
} }
} }
IndexChange::RemoveDir(dir) => { IndexChange::RemoveDir(dir) => {
@ -252,9 +198,7 @@ pub fn apply_indexchanges_int(
let ok = if let Some(target) = target { let ok = if let Some(target) = target {
let t = target.join(dir); let t = target.join(dir);
if let Err(e) = fs::remove_dir_all(&t) { if let Err(e) = fs::remove_dir_all(&t) {
eprintln!( 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.");
"\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."
);
false false
} else { } else {
true true
@ -263,17 +207,14 @@ pub fn apply_indexchanges_int(
true true
}; };
if ok { if ok {
*failures -= 1; fs::remove_dir_all(i)?;
if let Err(e) = fs::remove_dir_all(&i) {
eprintln!("\n[warn] couldn't remove index directory {i:?}: {e:?}");
}
} }
} }
} }
{ {
eprint_status( eprint_status(
i + 1, i + 1,
changes_total, changes.len(),
gib_transferred, gib_transferred,
gib_total, gib_total,
prog_width, prog_width,
@ -282,4 +223,5 @@ pub fn apply_indexchanges_int(
); );
} }
} }
Ok(())
} }

View File

@ -4,11 +4,6 @@ use clap::Parser;
use crate::update_index::Settings; use crate::update_index::Settings;
/// rembackup,
/// a simple backup tool for local or remote backups.
/// run with --help for more help.
///
/// rembackup copies files from <source> to <target> using and storing information in <index>.
#[derive(Parser)] #[derive(Parser)]
#[command(author, version)] #[command(author, version)]
pub struct Args { pub struct Args {

View File

@ -17,8 +17,7 @@ mod update_index;
const EXIT_IGNORE_FAILED: u8 = 200; const EXIT_IGNORE_FAILED: u8 = 200;
const EXIT_DIFF_FAILED: u8 = 20; const EXIT_DIFF_FAILED: u8 = 20;
const EXIT_APPLY_FAILED_ONE: u8 = 100; const EXIT_APPLY_FAILED: u8 = 30;
const EXIT_APPLY_FAILED_ALL: u8 = 200;
fn main() { fn main() {
// get args // get args
@ -28,7 +27,7 @@ fn main() {
let cwd = match std::env::current_dir() { let cwd = match std::env::current_dir() {
Ok(v) => Some(v), Ok(v) => Some(v),
Err(e) => { Err(e) => {
eprintln!("[warn] Couldn't get current directory (CWD): {e}"); eprintln!("[WARN] Couldn't get current directory (CWD): {e}");
None None
} }
}; };
@ -179,7 +178,7 @@ fn main() {
if !args.noconfirm { if !args.noconfirm {
loop { loop {
if args.target.is_none() { if args.target.is_none() {
eprintln!("[warn] You didn't set a `target` directory!\n[warn] Be careful not to update your index without actually applying the changes to the `target` filesystem!\nType 'Ok' and press enter to continue."); eprintln!("[WARN] You didn't set a `target` directory!\n[WARN] Be careful not to update your index without actually applying the changes to the `target` filesystem!\nType 'Ok' and press enter to continue.");
} else { } else {
eprintln!("Exclude unwanted directories/files using --ignore,\nor press enter to apply the changes."); eprintln!("Exclude unwanted directories/files using --ignore,\nor press enter to apply the changes.");
} }
@ -196,19 +195,18 @@ fn main() {
} }
} }
} }
let failure_count = apply_indexchanges( match apply_indexchanges(
&args.source, &args.source,
&args.index, &args.index,
&args.target, &args.target,
&changes, &changes,
Some(add_file_total_size_gib), Some(add_file_total_size_gib),
); ) {
eprintln!("[info] encountered {failure_count} failures"); Ok(()) => {}
if failure_count > 0 { Err(e) => {
exit( eprintln!("Failed to apply: {e}");
(EXIT_APPLY_FAILED_ONE as u64 + failure_count.ilog2() as u64) exit(EXIT_APPLY_FAILED as _);
.min(EXIT_APPLY_FAILED_ALL as u64) as _, }
);
} }
} }
} }

View File

@ -14,15 +14,10 @@ use crate::{
#[derive(Clone, Default, Args)] #[derive(Clone, Default, Args)]
pub struct Settings { pub struct Settings {
/// don't sort the changes that form the backup /// don't sort the changes that form the backup. disables sort options.
///
/// disables sort options.
/// symlinks will still be created last.
#[arg(long)] #[arg(long)]
pub dont_sort: bool, pub dont_sort: bool,
/// start with smaller directories rather than larger ones /// start with smaller directories rather than larger ones
///
/// symlinks will still be created last.
#[arg(long)] #[arg(long)]
pub smallest_first: bool, pub smallest_first: bool,
/// show changes in the order in which they will be applied, not reversed /// show changes in the order in which they will be applied, not reversed