this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: improve tar with pipe support and more

+101 -46
+101 -46
src/tar.rs
··· 2 2 3 3 use clap::Args; 4 4 use std::fs::File; 5 - use std::io::{self, Read, Write}; 5 + use std::io::{self, Seek, SeekFrom, Write}; 6 6 use std::path::Path; 7 7 use tar::{Archive, Builder}; 8 + use tempfile::tempfile; 8 9 9 10 use crate::utils::*; 10 11 ··· 36 37 37 38 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 38 39 match output { 39 - CmprssOutput::Pipe(pipe) => self.compress_internal(input, Builder::new(pipe)), 40 40 CmprssOutput::Path(path) => { 41 - self.compress_internal(input, Builder::new(File::create(path)?)) 41 + let file = File::create(path)?; 42 + self.compress_internal(input, Builder::new(file)) 43 + } 44 + CmprssOutput::Pipe(mut pipe) => { 45 + // Create a temporary file to write the tar to 46 + let mut temp_file = tempfile()?; 47 + self.compress_internal(input, Builder::new(&mut temp_file))?; 48 + 49 + // Reset the file position to the beginning 50 + temp_file.seek(SeekFrom::Start(0))?; 51 + 52 + // Copy the temporary file to the pipe 53 + io::copy(&mut temp_file, &mut pipe)?; 54 + Ok(()) 42 55 } 43 56 } 44 57 } 45 58 46 59 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result<(), io::Error> { 47 - match input { 48 - CmprssInput::Path(paths) => { 49 - if paths.len() > 1 { 50 - return cmprss_error("only 1 archive can be extracted at a time"); 60 + match output { 61 + CmprssOutput::Path(ref out_dir) => { 62 + // Create the output directory if it doesn't exist 63 + if !out_dir.exists() { 64 + std::fs::create_dir_all(out_dir)?; 65 + } else if !out_dir.is_dir() { 66 + return cmprss_error("tar extraction output must be a directory"); 51 67 } 52 - self.extract_internal(Archive::new(File::open(paths[0].as_path())?), output) 68 + 69 + match input { 70 + CmprssInput::Path(paths) => { 71 + if paths.len() != 1 { 72 + return cmprss_error("tar extraction expects a single archive file"); 73 + } 74 + let file = File::open(&paths[0])?; 75 + let mut archive = Archive::new(file); 76 + archive.unpack(out_dir) 77 + } 78 + CmprssInput::Pipe(mut pipe) => { 79 + // Create a temporary file to store the tar content 80 + let mut temp_file = tempfile()?; 81 + 82 + // Copy from pipe to temporary file 83 + io::copy(&mut pipe, &mut temp_file)?; 84 + 85 + // Reset the file position to the beginning 86 + temp_file.seek(SeekFrom::Start(0))?; 87 + 88 + // Extract from the temporary file 89 + let mut archive = Archive::new(temp_file); 90 + archive.unpack(out_dir) 91 + } 92 + } 53 93 } 54 - CmprssInput::Pipe(pipe) => self.extract_internal(Archive::new(pipe), output), 94 + CmprssOutput::Pipe(_) => cmprss_error("tar extraction to stdout is not supported"), 55 95 } 56 96 } 57 97 } 58 98 59 99 impl Tar { 60 - /// Internal extract helper 61 - fn extract_internal<R: Read>( 62 - &self, 63 - mut archive: Archive<R>, 64 - output: CmprssOutput, 65 - ) -> Result<(), io::Error> { 66 - let out_path = match output { 67 - CmprssOutput::Pipe(_) => { 68 - return cmprss_error("error: tar does not support stdout as extract output") 69 - } 70 - CmprssOutput::Path(path) => path, 71 - }; 72 - if !out_path.is_dir() { 73 - return cmprss_error("error: tar can only extract to a directory"); 74 - } 75 - archive.unpack(out_path) 76 - } 77 - 78 100 /// Internal compress helper 79 101 fn compress_internal<W: Write>( 80 102 &self, 81 103 input: CmprssInput, 82 104 mut archive: Builder<W>, 83 105 ) -> Result<(), io::Error> { 84 - let input_files = match input { 85 - CmprssInput::Path(paths) => paths, 86 - CmprssInput::Pipe(_) => { 87 - return cmprss_error("error: tar does not support stdin as input") 106 + match input { 107 + CmprssInput::Path(paths) => { 108 + for path in paths { 109 + if path.is_file() { 110 + archive.append_file( 111 + path.file_name().unwrap(), 112 + &mut File::open(path.as_path())?, 113 + )?; 114 + } else if path.is_dir() { 115 + archive.append_dir_all(path.file_name().unwrap(), path.as_path())?; 116 + } else { 117 + return cmprss_error("unsupported file type for tar compression"); 118 + } 119 + } 88 120 } 89 - }; 90 - for in_file in input_files { 91 - if in_file.is_file() { 92 - archive.append_file( 93 - in_file.file_name().unwrap(), 94 - &mut File::open(in_file.as_path())?, 95 - )?; 96 - } else if in_file.is_dir() { 97 - archive.append_dir_all(in_file.file_name().unwrap(), in_file.as_path())?; 98 - } else { 99 - return Err(io::Error::new( 100 - io::ErrorKind::InvalidInput, 101 - "unknown file type", 102 - )); 121 + CmprssInput::Pipe(mut pipe) => { 122 + // For pipe input, we'll create a single file named "archive" 123 + let mut temp_file = tempfile()?; 124 + io::copy(&mut pipe, &mut temp_file)?; 125 + temp_file.seek(SeekFrom::Start(0))?; 126 + archive.append_file("archive", &mut temp_file)?; 103 127 } 104 128 } 105 129 archive.finish() 106 130 } 107 131 } 108 132 109 - // TODO: Tests will be largely the same for all Compressors, should be able to combine 110 133 #[cfg(test)] 111 134 mod tests { 112 135 use super::*; 113 136 use assert_fs::prelude::*; 114 137 use predicates::prelude::*; 138 + use std::path::PathBuf; 115 139 116 140 #[test] 117 141 fn roundtrip() -> Result<(), Box<dyn std::error::Error>> { ··· 139 163 .child("test.txt") 140 164 .assert(predicate::path::eq_file(file.path())); 141 165 166 + Ok(()) 167 + } 168 + 169 + #[test] 170 + fn roundtrip_directory() -> Result<(), Box<dyn std::error::Error>> { 171 + let compressor = Tar::default(); 172 + let dir = assert_fs::TempDir::new()?; 173 + let file_path = dir.child("file.txt"); 174 + file_path.write_str("garbage data for testing")?; 175 + let working_dir = assert_fs::TempDir::new()?; 176 + let archive = working_dir.child("dir_archive.tar"); 177 + archive.assert(predicate::path::missing()); 178 + 179 + compressor.compress( 180 + CmprssInput::Path(vec![dir.path().to_path_buf()]), 181 + CmprssOutput::Path(archive.path().to_path_buf()), 182 + )?; 183 + archive.assert(predicate::path::is_file()); 184 + 185 + let extract_dir = working_dir.child("extracted"); 186 + std::fs::create_dir_all(extract_dir.path())?; 187 + compressor.extract( 188 + CmprssInput::Path(vec![archive.path().to_path_buf()]), 189 + CmprssOutput::Path(extract_dir.path().to_path_buf()), 190 + )?; 191 + 192 + let dir_name: PathBuf = dir.path().file_name().unwrap().into(); 193 + extract_dir 194 + .child(dir_name) 195 + .child("file.txt") 196 + .assert(predicate::path::eq_file(file_path.path())); 142 197 Ok(()) 143 198 } 144 199 }