this repo has no description
0
fork

Configure Feed

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

at f7bbb7638e8ed85d5c92d23cfdcf38ed665b3b21 237 lines 8.3 kB view raw
1extern crate tar; 2 3use anyhow::bail; 4use clap::Args; 5use std::fs::File; 6use std::io::{self, Read, Seek, SeekFrom, Write}; 7use tar::{Archive, Builder}; 8use tempfile::tempfile; 9 10use crate::utils::*; 11 12#[derive(Args, Debug)] 13pub struct TarArgs { 14 #[clap(flatten)] 15 pub common_args: CommonArgs, 16} 17 18#[derive(Default)] 19pub struct Tar {} 20 21impl Tar { 22 pub fn new(_args: &TarArgs) -> Tar { 23 Tar {} 24 } 25} 26 27impl Compressor for Tar { 28 /// Full name for tar, also used for extension 29 fn name(&self) -> &str { 30 "tar" 31 } 32 33 /// Tar extracts to a directory by default 34 fn default_extracted_target(&self) -> ExtractedTarget { 35 ExtractedTarget::Directory 36 } 37 38 fn compress(&self, input: CmprssInput, output: CmprssOutput) -> Result { 39 match output { 40 CmprssOutput::Path(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(()) 55 } 56 CmprssOutput::Writer(mut writer) => { 57 let mut temp_file = tempfile()?; 58 self.compress_internal(input, Builder::new(&mut temp_file))?; 59 temp_file.seek(SeekFrom::Start(0))?; 60 io::copy(&mut temp_file, &mut writer)?; 61 Ok(()) 62 } 63 } 64 } 65 66 fn extract(&self, input: CmprssInput, output: CmprssOutput) -> Result { 67 match output { 68 CmprssOutput::Path(ref out_dir) => { 69 // Create the output directory if it doesn't exist 70 if !out_dir.exists() { 71 std::fs::create_dir_all(out_dir)?; 72 } else if !out_dir.is_dir() { 73 bail!("tar extraction output must be a directory"); 74 } 75 76 match input { 77 CmprssInput::Path(paths) => { 78 if paths.len() != 1 { 79 bail!("tar extraction expects a single archive file"); 80 } 81 let file = File::open(&paths[0])?; 82 let mut archive = Archive::new(file); 83 Ok(archive.unpack(out_dir)?) 84 } 85 CmprssInput::Pipe(mut pipe) => { 86 // Create a temporary file to store the tar content 87 let mut temp_file = tempfile()?; 88 89 // Copy from pipe to temporary file 90 io::copy(&mut pipe, &mut temp_file)?; 91 92 // Reset the file position to the beginning 93 temp_file.seek(SeekFrom::Start(0))?; 94 95 // Extract from the temporary file 96 let mut archive = Archive::new(temp_file); 97 Ok(archive.unpack(out_dir)?) 98 } 99 CmprssInput::Reader(reader) => { 100 let mut archive = Archive::new(reader.0); 101 archive.unpack(out_dir)?; 102 Ok(()) 103 } 104 } 105 } 106 CmprssOutput::Pipe(_) => bail!("tar extraction to stdout is not supported"), 107 CmprssOutput::Writer(mut writer) => match input { 108 CmprssInput::Path(paths) => { 109 if paths.len() != 1 { 110 bail!("tar extraction expects a single archive file"); 111 } 112 let mut file = File::open(&paths[0])?; 113 io::copy(&mut file, &mut writer)?; 114 Ok(()) 115 } 116 CmprssInput::Pipe(mut pipe) => { 117 io::copy(&mut pipe, &mut writer)?; 118 Ok(()) 119 } 120 CmprssInput::Reader(mut reader) => { 121 io::copy(&mut reader, &mut writer)?; 122 Ok(()) 123 } 124 }, 125 } 126 } 127 128 fn list(&self, input: CmprssInput) -> Result { 129 let reader: Box<dyn Read> = match input { 130 CmprssInput::Path(paths) => { 131 if paths.len() != 1 { 132 bail!("tar listing expects a single archive file"); 133 } 134 Box::new(File::open(&paths[0])?) 135 } 136 CmprssInput::Pipe(stdin) => Box::new(stdin), 137 CmprssInput::Reader(reader) => reader.0, 138 }; 139 let mut archive = Archive::new(reader); 140 let stdout = io::stdout(); 141 let mut out = stdout.lock(); 142 for entry in archive.entries()? { 143 let entry = entry?; 144 let path = entry.path()?; 145 writeln!(out, "{}", path.display())?; 146 } 147 Ok(()) 148 } 149} 150 151impl Tar { 152 /// Internal compress helper 153 fn compress_internal<W: Write>(&self, input: CmprssInput, mut archive: Builder<W>) -> Result { 154 match input { 155 CmprssInput::Path(paths) => { 156 for path in paths { 157 if path.is_file() { 158 archive.append_file( 159 path.file_name().unwrap(), 160 &mut File::open(path.as_path())?, 161 )?; 162 } else if path.is_dir() { 163 archive.append_dir_all(path.file_name().unwrap(), path.as_path())?; 164 } else { 165 bail!("unsupported file type for tar compression"); 166 } 167 } 168 } 169 CmprssInput::Pipe(mut pipe) => { 170 // For pipe input, we'll create a single file named "archive" 171 let mut temp_file = tempfile()?; 172 io::copy(&mut pipe, &mut temp_file)?; 173 temp_file.seek(SeekFrom::Start(0))?; 174 archive.append_file("archive", &mut temp_file)?; 175 } 176 CmprssInput::Reader(_) => { 177 bail!("Cannot tar a reader input directly"); 178 } 179 } 180 Ok(archive.finish()?) 181 } 182} 183 184#[cfg(test)] 185mod tests { 186 use super::*; 187 use crate::test_utils::*; 188 use assert_fs::prelude::*; 189 use predicates::prelude::*; 190 use std::path::PathBuf; 191 192 /// Test the basic interface of the Tar compressor 193 #[test] 194 fn test_tar_interface() { 195 let compressor = Tar::default(); 196 test_compressor_interface(&compressor, "tar", Some("tar")); 197 } 198 199 /// Test the default compression level 200 #[test] 201 fn test_tar_default_compression() -> Result { 202 let compressor = Tar::default(); 203 test_compression(&compressor) 204 } 205 206 /// Test tar-specific functionality: directory handling 207 #[test] 208 fn test_directory_handling() -> Result { 209 let compressor = Tar::default(); 210 let dir = assert_fs::TempDir::new()?; 211 let file_path = dir.child("file.txt"); 212 file_path.write_str("garbage data for testing")?; 213 let working_dir = assert_fs::TempDir::new()?; 214 let archive = working_dir.child("dir_archive.tar"); 215 archive.assert(predicate::path::missing()); 216 217 compressor.compress( 218 CmprssInput::Path(vec![dir.path().to_path_buf()]), 219 CmprssOutput::Path(archive.path().to_path_buf()), 220 )?; 221 archive.assert(predicate::path::is_file()); 222 223 let extract_dir = working_dir.child("extracted"); 224 std::fs::create_dir_all(extract_dir.path())?; 225 compressor.extract( 226 CmprssInput::Path(vec![archive.path().to_path_buf()]), 227 CmprssOutput::Path(extract_dir.path().to_path_buf()), 228 )?; 229 230 let dir_name: PathBuf = dir.path().file_name().unwrap().into(); 231 extract_dir 232 .child(dir_name) 233 .child("file.txt") 234 .assert(predicate::path::eq_file(file_path.path())); 235 Ok(()) 236 } 237}