this repo has no description
0
fork

Configure Feed

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

at fa6719dd365c14fa113526b45d0b8ca6cf4493bb 215 lines 7.5 kB view raw
1extern crate tar; 2 3use anyhow::bail; 4use clap::Args; 5use std::fs::File; 6use std::io::{self, 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 129impl Tar { 130 /// Internal compress helper 131 fn compress_internal<W: Write>(&self, input: CmprssInput, mut archive: Builder<W>) -> Result { 132 match input { 133 CmprssInput::Path(paths) => { 134 for path in paths { 135 if path.is_file() { 136 archive.append_file( 137 path.file_name().unwrap(), 138 &mut File::open(path.as_path())?, 139 )?; 140 } else if path.is_dir() { 141 archive.append_dir_all(path.file_name().unwrap(), path.as_path())?; 142 } else { 143 bail!("unsupported file type for tar compression"); 144 } 145 } 146 } 147 CmprssInput::Pipe(mut pipe) => { 148 // For pipe input, we'll create a single file named "archive" 149 let mut temp_file = tempfile()?; 150 io::copy(&mut pipe, &mut temp_file)?; 151 temp_file.seek(SeekFrom::Start(0))?; 152 archive.append_file("archive", &mut temp_file)?; 153 } 154 CmprssInput::Reader(_) => { 155 bail!("Cannot tar a reader input directly"); 156 } 157 } 158 Ok(archive.finish()?) 159 } 160} 161 162#[cfg(test)] 163mod tests { 164 use super::*; 165 use crate::test_utils::*; 166 use assert_fs::prelude::*; 167 use predicates::prelude::*; 168 use std::path::PathBuf; 169 170 /// Test the basic interface of the Tar compressor 171 #[test] 172 fn test_tar_interface() { 173 let compressor = Tar::default(); 174 test_compressor_interface(&compressor, "tar", Some("tar")); 175 } 176 177 /// Test the default compression level 178 #[test] 179 fn test_tar_default_compression() -> Result { 180 let compressor = Tar::default(); 181 test_compression(&compressor) 182 } 183 184 /// Test tar-specific functionality: directory handling 185 #[test] 186 fn test_directory_handling() -> Result { 187 let compressor = Tar::default(); 188 let dir = assert_fs::TempDir::new()?; 189 let file_path = dir.child("file.txt"); 190 file_path.write_str("garbage data for testing")?; 191 let working_dir = assert_fs::TempDir::new()?; 192 let archive = working_dir.child("dir_archive.tar"); 193 archive.assert(predicate::path::missing()); 194 195 compressor.compress( 196 CmprssInput::Path(vec![dir.path().to_path_buf()]), 197 CmprssOutput::Path(archive.path().to_path_buf()), 198 )?; 199 archive.assert(predicate::path::is_file()); 200 201 let extract_dir = working_dir.child("extracted"); 202 std::fs::create_dir_all(extract_dir.path())?; 203 compressor.extract( 204 CmprssInput::Path(vec![archive.path().to_path_buf()]), 205 CmprssOutput::Path(extract_dir.path().to_path_buf()), 206 )?; 207 208 let dir_name: PathBuf = dir.path().file_name().unwrap().into(); 209 extract_dir 210 .child(dir_name) 211 .child("file.txt") 212 .assert(predicate::path::eq_file(file_path.path())); 213 Ok(()) 214 } 215}