diff --git a/file-shuffler/shuffler-in-rust/.gitignore b/file-shuffler/shuffler-in-rust/.gitignore new file mode 100644 index 0000000..c41cc9e --- /dev/null +++ b/file-shuffler/shuffler-in-rust/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/file-shuffler/shuffler-in-rust/Cargo.lock b/file-shuffler/shuffler-in-rust/Cargo.lock new file mode 100644 index 0000000..764ece6 --- /dev/null +++ b/file-shuffler/shuffler-in-rust/Cargo.lock @@ -0,0 +1,614 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" + +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cc" +version = "1.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "clap" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "filetime" +version = "0.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +dependencies = [ + "cfg-if", + "libc", + "libredox", + "windows-sys", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags", + "libc", + "redox_syscall", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "shuffler-in-rust" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "filetime", + "rand", + "terminal_size", + "walkdir", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "terminal_size" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[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-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/file-shuffler/shuffler-in-rust/Cargo.toml b/file-shuffler/shuffler-in-rust/Cargo.toml new file mode 100644 index 0000000..b0ff470 --- /dev/null +++ b/file-shuffler/shuffler-in-rust/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "shuffler-in-rust" +version = "0.1.0" +edition = "2021" + +[dependencies] +walkdir = "2.3" +rand = "0.8" +filetime = "0.2" +terminal_size = "0.1" +chrono = "0.4" +clap = { version = "4.0", features = ["derive"] } \ No newline at end of file diff --git a/file-shuffler/shuffler-in-rust/src/main.rs b/file-shuffler/shuffler-in-rust/src/main.rs new file mode 100644 index 0000000..f8f83e4 --- /dev/null +++ b/file-shuffler/shuffler-in-rust/src/main.rs @@ -0,0 +1,683 @@ +/// File Shuffler +/// +/// This application shuffles files in a specified source directory at defined intervals. +/// +/// # Author +/// Joshua Olaoye + +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; +use rand::Rng; +use filetime::{FileTime, set_file_times}; +use std::time::{SystemTime, UNIX_EPOCH, Duration}; +use std::thread; +use std::str::FromStr; +use std::collections::HashSet; +use terminal_size::{Width, terminal_size}; +use chrono::{DateTime, FixedOffset, TimeZone, LocalResult}; +use clap::{Arg, Command}; + +/// Enum representing the time intervals for processing files. +/// +/// This enum defines the various intervals at which the application can process files in the specified source directory. +/// +/// # Variants +/// +/// - `Never`: +/// Indicates that the processing should occur only once and not repeat. +/// +/// - `EveryWeek`: +/// Indicates that the processing should occur once every week. +/// +/// - `Every30Seconds`: +/// Indicates that the processing should occur every 30 seconds. +#[derive(Debug)] +enum Interval { + Never, + EveryWeek, + Every30Seconds, +} + +/// Implementation of `FromStr` trait for `Interval` enum. +/// +/// This implementation allows for the creation of an `Interval` instance from a string representation. +/// +/// # Errors +/// +/// Returns an error if the input string does not match one of the defined interval options: +/// - "0" for `Interval::Never` +/// - "1" for `Interval::EveryWeek` +/// - "2" for `Interval::Every30Seconds` +/// +/// # Examples +/// +/// ``` +/// use std::str::FromStr; +/// +/// let interval = Interval::from_str("1").unwrap(); // Converts to `Interval::EveryWeek` +/// let invalid_interval = Interval::from_str("3"); // Returns an error +/// ``` +impl FromStr for Interval { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "0" => Ok(Interval::Never), + "1" => Ok(Interval::EveryWeek), + "2" => Ok(Interval::Every30Seconds), + _ => Err(()), + } + } +} + +/// Generates a random timestamp within the last 10 days from the current time. +/// +/// This function calculates a random offset in seconds, ranging from 0 to the total +/// number of seconds in 10 days (864,000 seconds). It subtracts this offset from the +/// current system time to produce a timestamp that is guaranteed to be within the last +/// 10 days. +/// +/// # Returns +/// +/// A `SystemTime` representing a random point in time within the last 10 days. +/// +/// # Examples +/// +/// ``` +/// let random_time = random_timestamp(); +/// println!("Random timestamp: {:?}", random_time); +/// ``` +fn random_timestamp() -> SystemTime { + let now = SystemTime::now(); + let ten_days = Duration::from_secs(10 * 24 * 60 * 60); // 10 days in seconds + let random_offset = rand::thread_rng().gen_range(0..=ten_days.as_secs()); + now - Duration::from_secs(random_offset) +} + +/// Renames all files in the specified directory by moving them to a temporary directory +/// with a unique random name. Hidden files (starting with '.') are removed. +/// +/// # Parameters +/// +/// - `dir`: A reference to a `Path` representing the directory containing the files to rename. +/// +/// # Returns +/// +/// Returns `Ok(())` on success, indicating that the files have been renamed and moved back +/// to the original directory. Otherwise, it returns an `io::Result` indicating the type of +/// error encountered. +/// +/// # Errors +/// +/// This function may return errors in the following cases: +/// +/// - [`std::io::Error`]: If an I/O operation fails, such as when creating the temporary +/// directory, reading from the directory, or renaming files. +/// +/// - The function will remove hidden files (e.g., `.DS_Store` on macOS) but does not +/// return errors for this operation; it simply skips these files. +/// +/// # Note +/// +/// After renaming, the function attempts to set the modified timestamp of the renamed files +/// using a random timestamp generated within the last 10 days. If needed, this logic can be +/// uncommented and implemented as per your requirements. +/// +/// # Example +/// +/// ```rust +/// let path = Path::new("/path/to/directory"); +/// rename_files_in_directory(&path).expect("Failed to rename files"); +/// ``` +fn rename_files_in_directory(dir: &Path) -> io::Result<()> { + let mut used_numbers = HashSet::new(); + let mut rng = rand::thread_rng(); + + // Create a temporary directory + let temp_dir = dir.join("temp"); + fs::create_dir_all(&temp_dir)?; + + // Count the number of files in the directory + let n = fs::read_dir(dir)?.count(); + + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + // Check for hidden files and delete them (.DS_Store on Mac) + if path.file_name().unwrap().to_str().unwrap().starts_with('.') { + fs::remove_file(&path)?; + continue; // Skip processing further for this entry + } + + if path.is_file() { + // Get last modified timestamp + let timestamp = get_formatted_modified_time(&path)?; + + println!("| Original file\n| File name: {:?}\n| Timestamp: {:?}", path, timestamp); + println!("•"); + // Generate a unique random number within the range of 1 to n + let mut random_number; + let mut new_path; + loop { + random_number = rng.gen_range(1..=n); + new_path = temp_dir.join(format!("file_{}.txt", random_number)); + if !used_numbers.contains(&random_number) && !new_path.exists() { + used_numbers.insert(random_number); + break; + } + } + + // Move the file to the temporary directory with the new name + fs::rename(&path, &new_path)?; + + // Uncomment and implement the timestamp update if needed + let random_time = FileTime::from_system_time(random_timestamp()); + set_file_times(&new_path, random_time, random_time)?; + + let modified_timestamp = get_formatted_modified_time(&new_path)?; + + println!("| Modified file\n| File name: {:?}\n| Timestamp: {:?}\n", new_path, modified_timestamp); + } + } + + // Move files back from the temporary directory to the original directory + for entry in fs::read_dir(&temp_dir)? { + let entry = entry?; + let temp_path = entry.path(); + let file_name = temp_path.file_name().unwrap(); + let final_path = dir.join(file_name); + fs::rename(&temp_path, &final_path)?; + } + + // Remove the temporary directory + fs::remove_dir(&temp_dir)?; + + // FIXME: One off error on mac + println!("Renamed {:?} file(s) in: {:?}", n, dir); + print_dashes(true); + Ok(()) +} + +/// Checks if a given directory contains any subdirectories. +/// +/// This function attempts to read the contents of the specified directory and +/// determines if any of the entries are subdirectories. It iterates through +/// the directory entries and returns `true` if at least one subdirectory is found; +/// otherwise, it returns `false`. +/// +/// # Parameters +/// +/// - `dir`: A reference to a `Path` representing the directory to be checked. +/// +/// # Returns +/// +/// - `true` if the directory contains at least one subdirectory, +/// - `false` if there are no subdirectories or if the directory cannot be read. +/// +/// # Examples +/// +/// ``` +/// let path = Path::new("/some/directory"); +/// if has_subdirectories(&path) { +/// println!("The directory contains subdirectories."); +/// } else { +/// println!("No subdirectories found."); +/// } +/// ``` +fn has_subdirectories(dir: &Path) -> bool { + fs::read_dir(dir) + .map(|mut entries| entries.any(|e| e.map(|e| e.path().is_dir()).unwrap_or(false))) + .unwrap_or(false) +} + +/// Moves all files from a specified directory to its parent directory and deletes the original directory. +/// +/// This function iterates through all entries in the specified directory and moves any files +/// it finds to the parent directory. If a file with the same name already exists in the parent +/// directory, a unique name is generated by appending "_copyN" to the original filename, where +/// N is a counter that increments until a unique name is found. Once all files have been moved, +/// the original directory is deleted. +/// +/// # Parameters +/// +/// - `dir`: A reference to a `Path` representing the directory containing files to be moved. +/// +/// # Returns +/// +/// This function returns an `io::Result<()>`, which will be: +/// - `Ok(())` if all files were successfully moved and the directory was deleted, +/// - An `io::Error` if an error occurs during reading, moving files, or deleting the directory. +/// +/// # Examples +/// +/// ``` +/// let path = Path::new("/some/directory"); +/// if let Err(e) = move_files_to_parent_and_delete(&path) { +/// eprintln!("Failed to move files: {}", e); +/// } +/// ``` +fn move_files_to_parent_and_delete(dir: &Path) -> io::Result<()> { + let parent_dir = dir.parent().unwrap(); + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + if path.is_file() { + let file_name = path.file_name().unwrap(); + let mut new_path = parent_dir.join(file_name); + + // Check for name conflicts and generate a unique name if necessary + let mut counter = 1; + while new_path.exists() { + let new_file_name = format!("{}_copy{}", + file_name.to_string_lossy(), // Original file name + counter // Append counter + ); + new_path = parent_dir.join(new_file_name); + counter += 1; + } + + // Move the file to the parent directory + fs::rename(&path, &new_path)?; + } + } + + // Delete the now-empty directory + fs::remove_dir(dir)?; + Ok(()) +} + +/// Recursively traverses directories, renaming files and managing directory structures. +/// +/// This function takes a directory and processes it recursively. It first explores all immediate +/// subdirectories, invoking itself on each one. After processing all subdirectories, it checks +/// if the current directory is a leaf (i.e., it contains no further subdirectories). +/// +/// If the directory is a leaf and its parent is the specified root directory, the function +/// renames the files within it. If the directory is not a leaf, it moves its files to the parent +/// directory and deletes the now-empty directory. +/// +/// # Parameters +/// +/// - `dir`: A reference to a `Path` representing the current directory being processed. +/// - `root_dir`: A reference to a `Path` representing the root directory from which the traversal +/// starts. +/// +/// # Returns +/// +/// This function returns an `io::Result<()>`, which will be: +/// - `Ok(())` if the processing is successful, +/// - An `io::Error` if an error occurs during directory traversal, file renaming, or moving files. +/// +/// # Examples +/// +/// ``` +/// let root_path = Path::new("/some/root/directory"); +/// if let Err(e) = process_directory_recursive(root_path, root_path) { +/// eprintln!("Error processing directory: {}", e); +/// } +/// ``` +fn process_directory_recursive(dir: &Path, root_dir: &Path) -> io::Result<()> { + + // Process only the immediate subdirectories + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + + // Process only directories + if path.is_dir() { + process_directory_recursive(&path, &root_dir)?; + } + } + + // After processing subdirectories, check if the current directory is now a leaf + if dir != root_dir && !has_subdirectories(dir) { + if dir.parent() == Some(root_dir) { + print_dashes(false); + println!("Currently processing {:?}\n", dir); + rename_files_in_directory(dir)?; + return Ok(()) + } + move_files_to_parent_and_delete(dir)?; + } + + Ok(()) +} + +/// Checks if a directory is at least two levels deep. +/// +/// A directory is considered "at least two levels deep" if it contains subdirectories, indicating +/// that there is at least one level of nesting within it. This function essentially verifies +/// whether the specified directory has any subdirectories. +/// +/// # Parameters +/// +/// - `dir`: A reference to a `Path` representing the directory to check. +/// +/// # Returns +/// +/// This function returns `true` if the directory contains subdirectories, indicating that it is +/// at least two levels deep; otherwise, it returns `false`. +/// +/// # Examples +/// +/// ``` +/// let path = Path::new("/some/directory"); +/// if is_at_least_two_levels_deep(&path) { +/// println!("The directory is at least two levels deep."); +/// } else { +/// println!("The directory is not at least two levels deep."); +/// } +/// ``` +fn is_at_least_two_levels_deep(dir: &Path) -> bool { + return has_subdirectories(dir); +} + +/// Prints a horizontal line of dashes in the terminal. +/// +/// This function determines the terminal's width and prints a line of dashes +/// (`-`) that spans the entire width. If the terminal size cannot be determined, +/// it defaults to a width of 80 characters. An optional newline can be printed +/// after the line of dashes based on the `add_new_line` parameter. +/// +/// # Parameters +/// +/// - `add_new_line`: If set to `true`, an additional newline is printed after +/// the line of dashes. If set to `false`, no extra newline is printed. +/// +/// # Example +/// +/// ``` +/// print_dashes(true); // Prints a line of dashes followed by a newline. +/// print_dashes(false); // Prints a line of dashes without an extra newline. +/// ``` +/// +fn print_dashes(add_new_line: bool) { + let width = if let Some((Width(w), _)) = terminal_size() { + w as usize + } else { + 80 // Default width if terminal size can't be determined + }; + + println!("{}", "-".repeat(width)); + + if add_new_line { + println!(); // Print an additional new line if specified + } +} + +/// Retrieves the formatted last modified time of a file. +/// +/// This function takes a file path and returns the last modified time formatted +/// as a string. It retrieves the file's metadata to access the modification time, +/// converts it to a Unix timestamp, and formats it for better readability. +/// +/// # Parameters +/// +/// - `path`: A reference to a `PathBuf` representing the file whose modified time +/// is to be retrieved. +/// +/// # Returns +/// +/// Returns an `std::io::Result` which: +/// - Contains the formatted date string on success. +/// - Returns an error if the file's metadata cannot be accessed or if the +/// modification time cannot be retrieved. +/// +/// # Example +/// +/// ``` +/// let path = PathBuf::from("example.txt"); +/// match get_formatted_modified_time(&path) { +/// Ok(formatted_time) => println!("Last modified: {}", formatted_time), +/// Err(e) => eprintln!("Error retrieving modified time: {}", e), +/// } +/// ``` +/// +fn get_formatted_modified_time(path: &PathBuf) -> std::io::Result { + let metadata = fs::metadata(path)?; + let modified_time = metadata.modified()?; + + let timestamp = modified_time_to_unix(&modified_time)?; + let formatted_date = format_timestamp(timestamp); + + Ok(formatted_date) +} + +/// Converts a `SystemTime` object representing a modified time to a UNIX timestamp. +/// +/// This function takes a reference to a `SystemTime` instance and calculates +/// the duration since the UNIX epoch (January 1, 1970). It returns the number of +/// seconds since that epoch as a `u64` value. +/// +/// # Parameters +/// +/// - `modified_time`: A reference to a `SystemTime` instance that represents +/// the time to be converted. +/// +/// # Returns +/// +/// Returns a `std::io::Result` which: +/// - Contains the UNIX timestamp in seconds on success. +/// - Returns an error if the provided `SystemTime` is earlier than the UNIX epoch. +/// +/// # Panics +/// +/// This function will panic if the provided `SystemTime` is in the past relative to +/// the UNIX epoch. To avoid this, ensure that the `modified_time` is a valid +/// `SystemTime` that is not earlier than January 1, 1970. +/// +/// # Example +/// +/// ``` +/// use std::time::{SystemTime, UNIX_EPOCH}; +/// +/// let modified_time = SystemTime::now(); // Current time +/// match modified_time_to_unix(&modified_time) { +/// Ok(timestamp) => println!("UNIX Timestamp: {}", timestamp), +/// Err(e) => eprintln!("Error converting to UNIX timestamp: {}", e), +/// } +/// ``` +/// +fn modified_time_to_unix(modified_time: &SystemTime) -> std::io::Result { + let duration_since_epoch = modified_time.duration_since(UNIX_EPOCH) + .expect("Time went backwards"); + Ok(duration_since_epoch.as_secs()) +} + +/// Formats a UNIX timestamp into a human-readable string representation. +/// +/// This function takes a UNIX timestamp (in seconds) and converts it into a +/// formatted string that represents the date and time in the Central Daylight Time +/// (CDT) zone (UTC-5). The output format is "Month Day, Year at Hour:Minute AM/PM". +/// +/// # Parameters +/// +/// - `timestamp`: A `u64` representing the UNIX timestamp (number of seconds +/// since January 1, 1970). +/// +/// # Returns +/// +/// Returns a `String` that contains the formatted date and time. If the provided +/// timestamp results in an invalid date, it returns "Invalid date". +/// +/// # Example +/// +/// ``` +/// let timestamp = 1633072800; // Example UNIX timestamp +/// let formatted_date = format_timestamp(timestamp); +/// println!("Formatted date: {}", formatted_date); +/// ``` +/// +/// # Note +/// +/// The function assumes that the input timestamp is in the UTC timezone and formats +/// it according to the Central Daylight Time (CDT) offset. Make sure to account +/// for daylight saving time changes as needed. +/// +/// # Panics +/// +/// This function will panic if the fixed offset cannot be created (which is unlikely +/// in practice). +/// +fn format_timestamp(timestamp: u64) -> String { + // Define a fixed offset for CDT (UTC-5) + let fixed_offset = FixedOffset::west_opt(5 * 3600).expect("Invalid offset"); // 5 hours behind UTC + + // Convert the timestamp to DateTime + let datetime: LocalResult> = fixed_offset.timestamp_opt(timestamp as i64, 0); + + // Extract the DateTime + match extract_datetime(datetime) { + Some(dt) => dt.format("%B %d, %Y at %I:%M %p").to_string(), + None => "Invalid date".to_string(), // Handle the None case as needed + } +} + +/// Extracts a `DateTime` from a `LocalResult>`. +/// +/// This function handles different cases of `LocalResult` and returns the appropriate +/// `DateTime`. If the `LocalResult` is `None`, it returns `None`. If it is +/// `Single`, it returns the contained `DateTime`. If it is `Ambiguous`, it returns one of +/// the potential `DateTime` values, allowing for further handling if necessary. +/// +/// # Parameters +/// +/// - `local_result`: A `LocalResult>` that may contain a single +/// `DateTime`, an ambiguous result, or no value at all. +/// +/// # Returns +/// +/// Returns an `Option>`. This will be: +/// - `Some(DateTime)` if the input is `Single` or `Ambiguous` (with the first +/// `DateTime` from `Ambiguous`). +/// - `None` if the input is `None`. +/// +/// # Example +/// +/// ``` +/// let result: LocalResult> = /* some result */; +/// if let Some(dt) = extract_datetime(result) { +/// println!("Extracted date and time: {}", dt); +/// } else { +/// println!("No valid date and time found."); +/// } +/// ``` +/// +/// # Note +/// +/// In the case of ambiguous results, the function currently returns the first `DateTime` +/// provided. +fn extract_datetime(local_result: LocalResult>) -> Option> { + match local_result { + LocalResult::Single(dt) => Some(dt), + LocalResult::Ambiguous(dt1, _) => Some(dt1), + LocalResult::None => None, + } +} + +/// The main entry point of the application. +/// +/// This program shuffles files in a specified source directory at defined intervals. +/// It can run continuously based on the specified interval or process the directory just once. +/// +/// # Usage +/// +/// To run the application, provide a source directory and an optional interval flag: +/// +/// ```bash +/// cargo run [--] [-i ] +/// ``` +/// +/// # Arguments +/// +/// - ``: +/// The path to the directory containing files to be processed. This argument is required. +/// +/// - `-i ` or `--interval `: +/// The time interval for processing the directory. Acceptable values are: +/// - `0`: Never repeat (process only once). +/// - `1`: Process every week. +/// - `2`: Process every 30 seconds. +/// If not provided, the default value is `0`. +/// +/// # Example +/// +/// To process files in the `example_dir` every 30 seconds: +/// +/// ```bash +/// cargo run example_dir -i 2 +/// ``` +/// +/// To process files in `example_dir` only once: +/// +/// ```bash +/// cargo run example_dir +/// ``` +/// +/// To process files in `example_dir` every week: +/// +/// ```bash +/// cargo run example_dir -i 1 +/// ``` +/// +/// # Errors +/// +/// This application will exit with an error message if: +/// - The provided source directory does not exist or is not a directory. +/// - The source directory is not at least two levels deep. +/// - The specified interval is not supported (values other than `0`, `1`, or `2`). +/// +/// # Notes +/// If the specified interval is not supported, the application will use `0` (never) by default. +fn main() -> io::Result<()> { + // Set up command-line argument parsing + let matches = Command::new("File Shuffler") + .version("1.0") + .author("Your Name") + .about("Shuffles files in a directory") + .arg(Arg::new("source_directory") + .help("The source directory to process") + .required(true) + .index(1)) + .arg(Arg::new("interval") + .short('i') // Define short flag '-i' + .long("interval") // Define long flag '--interval' + .help("Set the processing interval (0: never, 1: every week, 2: every 30 seconds)") + .default_value("0") // Default to "0" for 'never' + ).get_matches(); + + let source_dir = PathBuf::from(matches.get_one::("source_directory").unwrap()); + let interval: Interval = matches.get_one::("interval") + .unwrap() + .parse() + .unwrap_or(Interval::Never); // Default to Never if invalid + + if !source_dir.exists() || !source_dir.is_dir() { + eprintln!("Source directory not found or not a directory."); + std::process::exit(1); + } + + if !is_at_least_two_levels_deep(&source_dir) { + eprintln!("Source directory is not at least 1 levels deep."); + std::process::exit(1); + } + + match interval { + Interval::Never => { + process_directory_recursive(&source_dir, &source_dir)?; + } + Interval::EveryWeek => loop { + process_directory_recursive(&source_dir, &source_dir)?; + thread::sleep(Duration::from_secs(7 * 24 * 60 * 60)); // Sleep for 1 week + }, + Interval::Every30Seconds => loop { + process_directory_recursive(&source_dir, &source_dir)?; + thread::sleep(Duration::from_secs(30)); // Sleep for 30 seconds + }, + } + + Ok(()) +}