├── .gitignore ├── Cargo.toml ├── src ├── main.rs ├── lib.rs ├── cli.rs └── directives.rs └── Cargo.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore .DS_Store files for Apple devices 2 | *.DS_Store 3 | 4 | # Ignore Rust build directory 5 | target/ 6 | **/*.rs.bk 7 | 8 | # Ignore any test directories or log files 9 | *.log 10 | 11 | # Ignore Vim temp files 12 | *.swp 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tarsplit" 3 | version = "0.1.0" 4 | authors = ["All The Music, LLC"] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | clap = "2.33.0" 9 | tar = "0.4.26" 10 | flate2 = "1.0.9" 11 | 12 | [dev-dependencies] 13 | galvanic-assert = "0.8.7" 14 | galvanic-test = "0.2.0" 15 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // main.rs 2 | // 3 | // Copyright (c) 2020 All The Music, LLC 4 | // 5 | // This work is licensed under the Creative Commons Attribution 4.0 International License. 6 | // To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send 7 | // a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 8 | 9 | fn main() { 10 | // Parse command line arguments and run program 11 | tarsplit::cli::Cli::new().run(); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // lib.rs 2 | // 3 | // Copyright (c) 2020 All The Music, LLC 4 | // 5 | // This work is licensed under the Creative Commons Attribution 4.0 International License. 6 | // To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send 7 | // a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 8 | 9 | extern crate clap; 10 | extern crate tar; 11 | 12 | #[cfg(test)] 13 | #[macro_use] 14 | extern crate galvanic_assert; 15 | #[cfg(test)] 16 | #[macro_use] 17 | extern crate galvanic_test; 18 | 19 | #[doc(hidden)] 20 | pub mod cli; 21 | #[doc(hidden)] 22 | pub mod directives; 23 | -------------------------------------------------------------------------------- /src/cli.rs: -------------------------------------------------------------------------------- 1 | // cli.rs 2 | // 3 | // Copyright (c) 2020 All The Music, LLC 4 | // 5 | // This work is licensed under the Creative Commons Attribution 4.0 International License. 6 | // To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send 7 | // a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 8 | 9 | pub struct Cli<'a, 'b> { 10 | pub app: clap::App<'a, 'b>, 11 | } 12 | 13 | impl<'a, 'b> Cli<'a, 'b> { 14 | fn initialize_parser() -> clap::App<'a, 'b> { 15 | // Command line app 16 | clap::App::new("tarsplit") 17 | .version(env!("CARGO_PKG_VERSION")) 18 | .author("All The Music, LLC") 19 | .about("Tool for splitting tar archives into chunks along file boundaries.") 20 | .arg(clap::Arg::with_name("CHUNK_SIZE") 21 | .short("c") 22 | .long("chunk-size") 23 | .takes_value(true) 24 | .help("Approximate size of output chunks in bytes (incompatible with NUM_CHUNKS)")) 25 | .arg(clap::Arg::with_name("NUM_CHUNKS") 26 | .short("n") 27 | .long("num-chunks") 28 | .takes_value(true) 29 | .help("Number of ouptut chunks (incompatible with CHUNK_SIZE)")) 30 | .arg(clap::Arg::with_name("PREFIX") 31 | .short("p") 32 | .long("prefix") 33 | .takes_value(true) 34 | .default_value("split") 35 | .help("Prefix to apply to filename of each output chunk")) 36 | .arg(clap::Arg::with_name("SOURCE") 37 | .takes_value(true) 38 | .required(true) 39 | .help("Path to source TAR archive")) 40 | .arg(clap::Arg::with_name("TARGET") 41 | .takes_value(true) 42 | .required(true) 43 | .help("File output path (directory must exist)")) 44 | } 45 | 46 | pub fn new() -> Cli<'a, 'b> { 47 | Cli { 48 | app: Cli::initialize_parser(), 49 | } 50 | } 51 | 52 | pub fn run(self) { 53 | let matches = self.app.get_matches(); 54 | crate::directives::tarsplit(crate::directives::TarsplitDirectiveArgs::from(&matches)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "adler32" 5 | version = "1.0.4" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | 8 | [[package]] 9 | name = "ansi_term" 10 | version = "0.11.0" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | dependencies = [ 13 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 14 | ] 15 | 16 | [[package]] 17 | name = "atty" 18 | version = "0.2.14" 19 | source = "registry+https://github.com/rust-lang/crates.io-index" 20 | dependencies = [ 21 | "hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 22 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 23 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 24 | ] 25 | 26 | [[package]] 27 | name = "bitflags" 28 | version = "1.2.1" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | 31 | [[package]] 32 | name = "cfg-if" 33 | version = "0.1.10" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | 36 | [[package]] 37 | name = "clap" 38 | version = "2.33.0" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | dependencies = [ 41 | "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 42 | "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 43 | "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", 44 | "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", 45 | "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", 46 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 47 | "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", 48 | ] 49 | 50 | [[package]] 51 | name = "crc32fast" 52 | version = "1.2.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | dependencies = [ 55 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 56 | ] 57 | 58 | [[package]] 59 | name = "filetime" 60 | version = "0.2.8" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | dependencies = [ 63 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 64 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 65 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 66 | "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 67 | ] 68 | 69 | [[package]] 70 | name = "flate2" 71 | version = "1.0.13" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | dependencies = [ 74 | "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", 75 | "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 76 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 77 | "miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 78 | ] 79 | 80 | [[package]] 81 | name = "galvanic-assert" 82 | version = "0.8.7" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | 85 | [[package]] 86 | name = "galvanic-test" 87 | version = "0.2.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | 90 | [[package]] 91 | name = "hermit-abi" 92 | version = "0.1.6" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | dependencies = [ 95 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 96 | ] 97 | 98 | [[package]] 99 | name = "libc" 100 | version = "0.2.66" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | 103 | [[package]] 104 | name = "miniz_oxide" 105 | version = "0.3.5" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | dependencies = [ 108 | "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", 109 | ] 110 | 111 | [[package]] 112 | name = "redox_syscall" 113 | version = "0.1.56" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | 116 | [[package]] 117 | name = "strsim" 118 | version = "0.8.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | 121 | [[package]] 122 | name = "tar" 123 | version = "0.4.26" 124 | source = "registry+https://github.com/rust-lang/crates.io-index" 125 | dependencies = [ 126 | "filetime 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 127 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 128 | "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", 129 | "xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 130 | ] 131 | 132 | [[package]] 133 | name = "tarsplit" 134 | version = "0.1.0" 135 | dependencies = [ 136 | "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", 137 | "flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", 138 | "galvanic-assert 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)", 139 | "galvanic-test 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 140 | "tar 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)", 141 | ] 142 | 143 | [[package]] 144 | name = "textwrap" 145 | version = "0.11.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | dependencies = [ 148 | "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", 149 | ] 150 | 151 | [[package]] 152 | name = "unicode-width" 153 | version = "0.1.7" 154 | source = "registry+https://github.com/rust-lang/crates.io-index" 155 | 156 | [[package]] 157 | name = "vec_map" 158 | version = "0.8.1" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | 161 | [[package]] 162 | name = "winapi" 163 | version = "0.3.8" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | dependencies = [ 166 | "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 167 | "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", 168 | ] 169 | 170 | [[package]] 171 | name = "winapi-i686-pc-windows-gnu" 172 | version = "0.4.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | 175 | [[package]] 176 | name = "winapi-x86_64-pc-windows-gnu" 177 | version = "0.4.0" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | 180 | [[package]] 181 | name = "xattr" 182 | version = "0.2.2" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | dependencies = [ 185 | "libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)", 186 | ] 187 | 188 | [metadata] 189 | "checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" 190 | "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 191 | "checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 192 | "checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 193 | "checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 194 | "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" 195 | "checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" 196 | "checksum filetime 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1ff6d4dab0aa0c8e6346d46052e93b13a16cf847b54ed357087c35011048cc7d" 197 | "checksum flate2 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f" 198 | "checksum galvanic-assert 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "3afb916c829538b4f18402c9d0be7484a6f5539442e19ed67ddfb7782604bb40" 199 | "checksum galvanic-test 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6e6ec93b2f388dcd1102437036da399ae305e9c6cdd4e213260f5ddb6005907a" 200 | "checksum hermit-abi 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "eff2656d88f158ce120947499e971d743c05dbcbed62e5bd2f38f1698bbc3772" 201 | "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" 202 | "checksum miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625" 203 | "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 204 | "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 205 | "checksum tar 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)" = "b3196bfbffbba3e57481b6ea32249fbaf590396a52505a2615adbb79d9d826d3" 206 | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 207 | "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 208 | "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" 209 | "checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" 210 | "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 211 | "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 212 | "checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c" 213 | -------------------------------------------------------------------------------- /src/directives.rs: -------------------------------------------------------------------------------- 1 | // directives.rs 2 | // 3 | // Copyright (c) 2020 All The Music, LLC 4 | // 5 | // This work is licensed under the Creative Commons Attribution 4.0 International License. 6 | // To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send 7 | // a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 8 | 9 | use std::io::Read; 10 | 11 | /**************************/ 12 | /***** Main Directive *****/ 13 | /**************************/ 14 | 15 | /// Minimum required size of tar archive/calculated chunks 16 | const MIN_ARCHIVE_SIZE: u64 = 1024; 17 | /// Minimum user-provided chunks number 18 | const MIN_NUM_CHUNKS: u32 = 2; 19 | 20 | #[derive(Debug)] 21 | pub struct TarsplitDirectiveArgs { 22 | pub chunk_size: Option, 23 | pub num_chunks: Option, 24 | pub prefix: String, 25 | pub source: String, 26 | pub target: String, 27 | } 28 | 29 | impl<'a> From<&clap::ArgMatches<'a>> for TarsplitDirectiveArgs { 30 | fn from(matches: &clap::ArgMatches<'a>) -> TarsplitDirectiveArgs { 31 | // Parse chunk size argument 32 | let chunk_size = match matches.value_of("CHUNK_SIZE") { 33 | None => None, 34 | Some(chunk_size) => { 35 | let chunk_size = chunk_size.parse::().unwrap(); 36 | if chunk_size < MIN_ARCHIVE_SIZE { 37 | panic!("Chunk size must be at least {}", MIN_ARCHIVE_SIZE); 38 | } 39 | Some(chunk_size) 40 | }, 41 | }; 42 | // Parse number of chunks argument 43 | let num_chunks = match matches.value_of("NUM_CHUNKS") { 44 | None => { 45 | if chunk_size == None { 46 | panic!("Must provide either chunk size or number of chunks"); 47 | } else { None } 48 | }, 49 | Some(num_chunks) => { 50 | let num_chunks = num_chunks.parse::().unwrap(); 51 | if num_chunks == MIN_NUM_CHUNKS { 52 | panic!("Number of chunks must be greater than {}", MIN_NUM_CHUNKS); 53 | } 54 | Some(num_chunks) 55 | }, 56 | }; 57 | // Parse prefix argument 58 | let prefix = match matches.value_of("PREFIX") { 59 | None => String::from("split"), 60 | Some(prefix) => String::from(prefix), 61 | }; 62 | // Parse source argument 63 | let source = matches.value_of("SOURCE").unwrap().to_string(); 64 | // Parse target path argument 65 | let target = matches.value_of("TARGET").unwrap().to_string(); 66 | 67 | TarsplitDirectiveArgs { 68 | chunk_size, 69 | num_chunks, 70 | prefix, 71 | source, 72 | target, 73 | } 74 | } 75 | } 76 | 77 | #[doc(hidden)] 78 | fn gen_chunk_size(chunk_size: &Option, num_chunks: &Option, source_size: &u64) -> u64 { 79 | // Calculate output chunks (maximum) size 80 | match chunk_size { 81 | // If num_chunks specified, calculate maximum size of each chunk 82 | // as source_size / num_chunks 83 | None => { 84 | let max_chunk_size = ((*source_size as f64) / (num_chunks.unwrap() as f64)).round() as u64; 85 | // Panic if chunk size is zero 86 | if max_chunk_size < MIN_ARCHIVE_SIZE { 87 | panic!("Calculated chunk size must be at least {} bytes, try providing \ 88 | a lower number of chunks (<{})", MIN_ARCHIVE_SIZE, num_chunks.unwrap()); 89 | } 90 | max_chunk_size 91 | }, 92 | // Otherwise use the user-provided chunk size 93 | Some(max_chunk_size) => { 94 | // Panic if chunk_size greater than size of source archive 95 | if max_chunk_size >= source_size { 96 | panic!( 97 | "Chunk size must be less than source archive size ({} >= {})", 98 | max_chunk_size, 99 | source_size 100 | ); 101 | } 102 | *max_chunk_size 103 | }, 104 | } 105 | } 106 | 107 | #[doc(hidden)] 108 | fn gen_chunk_filename(prefix: &str, filename_base: &str, chunk_count: u32) -> String { 109 | format!("{}_{}_{}.tar", prefix, filename_base, chunk_count) 110 | } 111 | 112 | #[doc(hidden)] 113 | fn gen_chunk_archive( 114 | target: &std::path::Path, 115 | prefix: &str, 116 | filename_base: &str, 117 | chunk_count: u32 118 | ) -> tar::Builder> { 119 | let filepath = gen_chunk_filename(prefix, filename_base, chunk_count); 120 | let filepath = target.join(&filepath); 121 | tar::Builder::new( 122 | std::io::BufWriter::new( 123 | std::fs::File::create(filepath.as_path()).unwrap() 124 | ) 125 | ) 126 | } 127 | 128 | pub fn tarsplit(args: TarsplitDirectiveArgs) { 129 | // Ensure source is file and exists 130 | let source = std::path::Path::new(&args.source); 131 | if !source.is_file() { 132 | panic!("Source must point to an existing archive"); 133 | } 134 | 135 | // Ensure target is existing directory 136 | let target = std::path::Path::new(&args.target); 137 | if !target.is_dir() { 138 | panic!("Target must point to an existing directory"); 139 | } 140 | 141 | // Read size of source archive 142 | let source_size = source.metadata().unwrap().len(); 143 | // Panic if source is less than 144 | if source_size < MIN_ARCHIVE_SIZE { panic!("::: ERROR: Source archive is less than {} bytes", MIN_ARCHIVE_SIZE); } 145 | println!("::: INFO: Source archive is {} bytes", source_size); 146 | 147 | // Calculate output chunks (maximum) size 148 | let chunk_maximum_size = gen_chunk_size(&args.chunk_size, &args.num_chunks, &source_size); 149 | println!("::: INFO: Maximum chunk size will be {} bytes", chunk_maximum_size); 150 | 151 | // Generate output archives base filename from source archive file stem 152 | let chunk_filename_base = source.file_stem().unwrap().to_str().unwrap(); 153 | 154 | // Read source as TAR archive 155 | let source = std::fs::File::open(source).unwrap(); 156 | let mut source = tar::Archive::new(source); 157 | 158 | // Initialize loop variable state 159 | let mut current_chunk_size: u64 = 0; 160 | let mut chunk_count: u32 = 0; 161 | let mut archive_chunk = gen_chunk_archive( 162 | &target, 163 | &args.prefix, 164 | chunk_filename_base, 165 | chunk_count 166 | ); 167 | 168 | // For each entry in the source archive 169 | for entry in source.entries().unwrap() { 170 | // Unwrap archive entry 171 | let mut entry = entry.unwrap(); 172 | // Copy header and check entry size 173 | let mut entry_header = entry.header().clone(); 174 | let entry_size = entry_header.entry_size().unwrap(); 175 | 176 | // If adding entry would make chunk large than maximum chunk size 177 | // TODO: If entry_size itself is larger than chunk_maximum_size, 178 | // write entry alone to separate tar file. 179 | if current_chunk_size + entry_size > chunk_maximum_size { 180 | println!("::: INFO: Reached chunk boundary, writing chunk {}", chunk_count); 181 | // Flush current chunk to disk 182 | archive_chunk.finish().unwrap(); 183 | // Increment chunk count 184 | chunk_count = chunk_count + 1; 185 | // Generate new archive 186 | archive_chunk = gen_chunk_archive( 187 | &target, 188 | &args.prefix, 189 | chunk_filename_base, 190 | chunk_count 191 | ); 192 | // Reset current chunk size 193 | current_chunk_size = 0; 194 | } 195 | 196 | // Extract entry path 197 | let entry_path = entry.path().unwrap().to_path_buf(); 198 | // Add entry to archive chunk 199 | archive_chunk.append_data( 200 | &mut entry_header, 201 | entry_path, 202 | entry.by_ref() 203 | ).unwrap(); 204 | 205 | // Increment current chunk size by size of header plus entry size 206 | // (each aligned to 512 bytes). 207 | current_chunk_size = current_chunk_size + 512 + (if entry_size > 512 {entry_size} else {512}); 208 | } 209 | 210 | // Flush final chunk to disk 211 | println!("::: INFO: Writing final chunk"); 212 | archive_chunk.finish().unwrap(); 213 | } 214 | 215 | #[cfg(test)] 216 | mod tests { 217 | use super::*; 218 | use galvanic_assert::assert_that; 219 | use galvanic_test::test_suite; 220 | 221 | test_suite! { 222 | name tarsplit_test_suite; 223 | 224 | // Panics: 225 | // 1) Calculated chunk size rounds to less than MIN_ARCHIVE_SIZE 226 | // 2) User-provided chunk size greater than source archive size 227 | fixture fixture_gen_chunk_size( 228 | expected: u64, 229 | should_panic: bool, 230 | chunk_size: Option, 231 | num_chunks: Option, 232 | source_size: u64 233 | ) -> () { 234 | params { 235 | vec![ 236 | (0, true, None, Some(3), 1024), 237 | (0, true, Some(2048), None, 1024), 238 | (15044193919, false, Some(15044193919), None, 16634133390), 239 | (37629135304, false, Some(37629135304), None, 46228510722), 240 | (24684425755, false, Some(24684425755), None, 29434577089), 241 | (17016020886, false, Some(17016020886), None, 51155793224), 242 | (3538808281, false, None, Some(10), 35388082811), 243 | (2302398318, false, Some(2302398318), None, 3528792383), 244 | (7119293162, false, Some(7119293162), None, 9264872871), 245 | (7199108631, false, None, Some(2), 14398217261), 246 | (515418526, false, None, Some(12), 6185022310), 247 | (122409328, false, Some(122409328), None, 4963699455), 248 | (9151011641, false, None, Some(5), 45755058207), 249 | (13559357808, false, Some(13559357808), None, 24230229168), 250 | (2966266890, false, None, Some(10), 29662668896), 251 | (32775332, false, None, Some(17), 557180652), 252 | (8565004352, false, Some(8565004352), None, 22489160338), 253 | (26025676716, false, Some(26025676716), None, 37848167206), 254 | (16166691602, false, Some(16166691602), None, 18969499869), 255 | (1233997522, false, None, Some(18), 22211955399), 256 | (7216833632, false, Some(7216833632), None, 26792632238), 257 | (15195086225, false, Some(15195086225), None, 47609272141), 258 | ].into_iter() 259 | } 260 | setup(&mut self) { () } 261 | } 262 | 263 | test test_gen_chunk_size(fixture_gen_chunk_size) { 264 | if *fixture_gen_chunk_size.params.should_panic { 265 | assert_that!(crate::directives::gen_chunk_size( 266 | &fixture_gen_chunk_size.params.chunk_size, 267 | &fixture_gen_chunk_size.params.num_chunks, 268 | &fixture_gen_chunk_size.params.source_size, 269 | ), panics); 270 | } else { 271 | assert_eq!(*fixture_gen_chunk_size.params.expected, crate::directives::gen_chunk_size( 272 | &fixture_gen_chunk_size.params.chunk_size, 273 | &fixture_gen_chunk_size.params.num_chunks, 274 | &fixture_gen_chunk_size.params.source_size, 275 | )); 276 | } 277 | } 278 | } 279 | } 280 | --------------------------------------------------------------------------------