this repo has no description
0
fork

Configure Feed

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

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