From 1d29c1368f36b0fc6f7c4c66906e39d85bb63b9c Mon Sep 17 00:00:00 2001 From: cfinn Date: Sun, 15 Feb 2026 17:22:10 -0600 Subject: Initial commit Signed-off-by: cfinn --- .gitignore | 1 + Cargo.lock | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 +++++ src/main.rs | 74 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..72eddf6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,100 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "linkdedup" +version = "0.1.0" +dependencies = [ + "pathdiff", + "sha2", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..3d40618 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "linkdedup" +version = "0.1.0" +edition = "2024" + +[dependencies] +sha2 = "0.10.9" +pathdiff = "0.2.3" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..53d505b --- /dev/null +++ b/src/main.rs @@ -0,0 +1,74 @@ +use pathdiff::diff_paths; +use sha2::{Digest, Sha256}; +use std::fs::File; +use std::io::Read; +use std::path::{Path, PathBuf}; + +fn get_hash(fname: &str) -> Vec { + let result1 = File::open(fname); + if result1.is_err() { + return vec![]; + } + let mut result = result1.unwrap(); + let mut hasher = Sha256::new(); + loop { + let mut bif = [0; 1024]; + let ur = result.read(&mut bif); + if ur.is_err() { + return vec![]; + } + hasher.update(&bif); + if ur.unwrap() < 1024 { + break; + } + } + + hasher.finalize().to_vec() +} + +fn find_files(path: &Path) -> Vec { + let mut ret = vec![]; + let fss = std::fs::read_dir(path).unwrap(); + for fs in fss { + let fs = fs.unwrap(); + if fs.file_type().unwrap().is_file() { + ret.push(fs.path()); + } else if fs.file_type().unwrap().is_dir() { + let mut p = PathBuf::from(path); + p.push(fs.file_name().to_str().unwrap()); + ret.extend(find_files(p.as_path())); + } + } + ret +} + +fn linkdup(path: &Path) { + let mut fhtable = vec![]; + for z in find_files(path) { + let pth = z; + let hsh = get_hash(pth.to_str().unwrap()); + fhtable.push((pth.to_str().unwrap().to_string(), hsh)); + } + fhtable.sort_by(|x, y| Ord::cmp(&x.0, &y.0)); + let mut fable: Vec<(String, Vec)> = vec![]; + for f in fhtable { + let prstr = fable + .iter() + .filter_map(|x| if x.1 == f.1 { Some(x.0.clone()) } else { None }) + .next(); + if prstr.is_some() { + // let f2m = std::fs::metadata(f.0.clone()).unwrap(); + std::fs::remove_file(f.0.clone()).unwrap(); + let mut zz = PathBuf::from(f.0.clone()); + zz.pop(); + std::os::unix::fs::symlink(diff_paths(prstr.unwrap(), zz).unwrap(), f.0.clone()) + .unwrap(); + } else { + fable.push(f); + } + } +} + +fn main() { + linkdup(PathBuf::from(".").as_path()); +} -- cgit 1.4.1-2-gfad0