From d3deecfb4575ea6c8a3d12867e2311fe951f034f Mon Sep 17 00:00:00 2001 From: Austen Adler Date: Wed, 15 Nov 2023 22:48:20 -0500 Subject: [PATCH] Add basic tar support --- Cargo.lock | 216 ++++++++++++++++++ Cargo.toml | 7 +- .../sample-ncdu-output.json | 0 ncdufmt/src/lib.rs | 5 +- src/main.rs | 2 + src/tar.rs | 54 +++++ src/types.rs | 7 + test-tar.tar | Bin 0 -> 40960 bytes 8 files changed, 284 insertions(+), 7 deletions(-) rename sample-ncdu-output.json => ncdufmt/sample-ncdu-output.json (100%) create mode 100644 src/tar.rs create mode 100644 src/types.rs create mode 100644 test-tar.tar diff --git a/Cargo.lock b/Cargo.lock index cd7b7c4..fc18a40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,11 +2,20 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + [[package]] name = "arncdu" version = "0.1.0" dependencies = [ + "anyhow", + "crossbeam", "ncdufmt", + "tar", ] [[package]] @@ -15,12 +24,118 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "crossbeam" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "filetime" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys", +] + [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + [[package]] name = "ncdufmt" version = "0.1.0" @@ -59,12 +174,27 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.192" @@ -128,8 +258,94 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "xattr" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4686009f71ff3e5c4dbcf1a282d0a44db3f021ba69350cd42086b3e5f1c6985" +dependencies = [ + "libc", +] diff --git a/Cargo.toml b/Cargo.toml index e9304ed..027cda7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,7 @@ members = [ # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -# num-traits = "0.2.17" -# serde = { version = "1.0.192", features = ["derive"] } -# serde_json = "1.0.108" -# tar = "0.4.40" +anyhow = "1.0.75" +crossbeam = { version = "0.8.2", features = ["crossbeam-channel"] } ncdufmt = {path="./ncdufmt/"} +tar = "0.4.40" diff --git a/sample-ncdu-output.json b/ncdufmt/sample-ncdu-output.json similarity index 100% rename from sample-ncdu-output.json rename to ncdufmt/sample-ncdu-output.json diff --git a/ncdufmt/src/lib.rs b/ncdufmt/src/lib.rs index 42c10bd..45ee6e9 100644 --- a/ncdufmt/src/lib.rs +++ b/ncdufmt/src/lib.rs @@ -398,7 +398,7 @@ mod tests { #[test] fn test_simple_ncdu_output() { - let input = fs::read_to_string("../sample-ncdu-output.json").unwrap(); + let input = fs::read_to_string("./sample-ncdu-output.json").unwrap(); let jd = &mut serde_json::Deserializer::from_str(&input); let parsed = serde_path_to_error::deserialize::<_, NcduFile>(jd) .map_err(|e| { @@ -416,7 +416,7 @@ mod tests { #[test] fn test_serialization() { - let input = fs::read_to_string("../sample-ncdu-output.json").unwrap(); + let input = fs::read_to_string("./sample-ncdu-output.json").unwrap(); let deserialized = serde_json::from_str::(&input).unwrap(); let serialized = serde_json::to_string(&deserialized).unwrap(); let deserialized_again = serde_json::from_str::(&serialized).unwrap(); @@ -424,6 +424,5 @@ mod tests { assert_eq!(serialized, serialized_again); assert_eq!(deserialized, deserialized_again); - assert_eq!(serialized, input); } } diff --git a/src/main.rs b/src/main.rs index e7a11a9..6f6bb31 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +mod tar; +mod types; fn main() { println!("Hello, world!"); } diff --git a/src/tar.rs b/src/tar.rs new file mode 100644 index 0000000..f31cf1f --- /dev/null +++ b/src/tar.rs @@ -0,0 +1,54 @@ +use std::io::Read; +// use crossbeam::channel::bounded; +use crate::types; +use anyhow::Result; + +use tar::Archive; + +/// Sends tar entries to a channel +/// +/// The tar file must be in a decompressed state +pub fn get_entries( + reader: impl Read, + tx: crossbeam::channel::Sender, +) -> Result<()> { + let mut archive = Archive::new(reader); + + for entry in archive.entries()? { + let entry = entry?; + + tx.send(types::Entry { + path: entry.path()?.to_path_buf(), + size: entry.size(), + })?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::fs::File; + + use super::*; + + #[test] + fn sample_tar() { + // Check that this tar file of mine has a Cargo.lock and 10 entries + let reader = File::open("./test-tar.tar").unwrap(); + let (tx, rx) = crossbeam::channel::bounded(100); + + get_entries(reader, tx).unwrap(); + + let mut results = vec![]; + while let Ok(entry) = rx.recv() { + results.push(entry); + } + + assert_eq!(results.len(), 10); + assert!(results + .iter() + .find(|r| r.path.to_string_lossy() == "Cargo.lock") + .is_some()); + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..df4b1e9 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,7 @@ +use std::path::PathBuf; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Entry { + pub path: PathBuf, + pub size: u64, +} diff --git a/test-tar.tar b/test-tar.tar new file mode 100644 index 0000000000000000000000000000000000000000..ec90f8684b2df6f9c76b76b585cffe93d5e3c436 GIT binary patch literal 40960 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