commit 2179a13aa63d2c6e15257273e035e5626dd0e777 Author: raute <> Date: Sat Jun 27 15:27:52 2020 +0200 Initial commit. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..eb5a316 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..932f868 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,209 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bekape" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "regex", +] + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits", + "time", +] + +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "hermit-abi" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..07093fe --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "bekape" +version = "0.1.0" +authors = ["raute"] +edition = "2018" + +[dependencies] +chrono = "0.4" +clap = "2.33" +regex = "1.3" diff --git a/README.md b/README.md new file mode 100644 index 0000000..c064e7c --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Bekape + +I want to produce a very simple backup program that can be used by people who know nothing about their computer. +It will need an easy GUI that lets you choose the folder to backup and an external drive and start the backup. + +For now there is no GUI, just a first try to to incremental backups using hardlinks. + +Pre-alpha. You really don't want to use this yet. \ No newline at end of file diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0fe09fe --- /dev/null +++ b/src/main.rs @@ -0,0 +1,127 @@ +use clap::{App, Arg}; +use regex::Regex; +use std::fs; +use std::time::SystemTime; +use std::vec::Vec; + +fn browse_recursively(dir: &str) -> (Vec, Vec) { + let mut content = Vec::new(); + let mut errors = Vec::new(); + let ls = fs::read_dir(dir); + if let Ok(ls) = ls { + for entry in ls { + if let Ok(entry) = entry { + let full_path = entry.path().to_string_lossy().to_string(); + if let Ok(file_type) = entry.file_type() { + if file_type.is_dir() { + let mut result = browse_recursively(&full_path); + content.append(&mut result.0); + errors.append(&mut result.1); + } else { + content.push(full_path); + } + } else { + errors.push(full_path); + } + } + } + } else { + errors.push(dir.to_string()); + } + (content, errors) +} + +fn get_file_metadata(file: &str) -> (SystemTime, u64) { + if let Ok(metadata) = fs::metadata(file) { + let time = metadata.modified().unwrap_or(std::time::UNIX_EPOCH); + let size = metadata.len(); + return (time, size); + }; + (std::time::UNIX_EPOCH, 0) +} + +fn backup_element(path: &str, src_root: &str, bkp_root: &str, prev_bkp_root: &Option) { + let src_file = format!("{}{}", src_root, path); + let bkp_file = format!("{}{}", bkp_root, path); + let p = bkp_file.rfind('/').unwrap(); // TODO: Is unwrap okay here? + let bkp_folder = bkp_file[0..p].to_string(); + let _ = fs::create_dir_all(&bkp_folder); + let mut backup_done = false; + if let Some(prev_bkp_root) = prev_bkp_root { + let prev_bkp_file = format!("{}{}", prev_bkp_root, path); + if std::path::Path::new(&prev_bkp_file).exists() { + let prev_bkp_metadata = get_file_metadata(&prev_bkp_file); + let src_metadata = get_file_metadata(&src_file); + backup_done = prev_bkp_metadata.0 > src_metadata.0 + && prev_bkp_metadata.1 == src_metadata.1 + && fs::hard_link(&prev_bkp_file, &bkp_file).is_ok(); + } + } + + if !backup_done && fs::copy(&src_file, &bkp_file).is_err() { + eprintln!("Could not copy: {} -> {}", src_file, bkp_file); + } +} + +fn main() { + let matches = App::new("bekape") + .about("Simple backup program.") + .arg( + Arg::with_name("source") + .help("The folder to backup") + .required(true), + ) + .arg( + Arg::with_name("destination") + .help("The folder to store the backup") + .required(true), + ) + .get_matches(); + let bkp_src = matches.value_of("source").unwrap(); + let bkp_dst = matches.value_of("destination").unwrap(); + + let dt = chrono::offset::Utc::now().format("%Y-%m-%d_%H-%M-%S"); + let backup_folder = format!("{}/backup_{}_wip", bkp_dst, dt); + if fs::create_dir(&backup_folder).is_err() { + eprintln!("Could not create backup directory: {}", &backup_folder); + std::process::exit(1); + } + + // find previous backup + let backup_regex = Regex::new(r"backup_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$").unwrap(); + let ls = fs::read_dir(bkp_dst); + let prev_backup = if let Ok(ls) = ls { + let mut ls = ls + .filter_map(|s| s.ok()) + .map(|s| s.path().to_string_lossy().trim().to_owned()) + .filter(|s| backup_regex.is_match(&s)) + .collect::>(); + if ls.is_empty() { + None + } else { + ls.sort(); + ls.last().cloned() + } + } else { + None + }; + + let result = browse_recursively(&bkp_src); + let content = result.0.iter().map(|s| { + let l = bkp_src.len(); + s[l..].to_string() + }); + + for err in result.1 { + eprintln!("Could not read {}.", err); + } + + for elem in content { + backup_element(&elem, &bkp_src, &backup_folder, &prev_backup); + } + + let new_name = backup_folder.replace("_wip", ""); + if fs::rename(&backup_folder, &new_name).is_err() { + eprintln!("Could not rename folder."); + } +}