Foinse foilsithe
Íosluchtaigh foinse ZIP
CH-J Tools
Brabhsáil comhadlanna agus comhaid chun feidhmchlár áirithe a scaoileadh.
CH-J / Server Manager
1704 Comhaid
0.1.2-beta
Suimigh: · Tógtha:
1
//! Local archives of downloaded remote selections (0.1.0 used `archiver`: zip level 9,2
//! tar, tar.gz level 9). The archive contains the downloaded tree without a root prefix.4
use crate::core::error::{CoreError, CoreResult};5
use std::fs::File;6
use std::io::{BufWriter, Write};7
use std::path::Path;9
fn failed(error: impl std::fmt::Display) -> CoreError {10
CoreError::plain(format!("Archive creation failed: {error}"))11
}13
fn create_private(destination: &Path) -> CoreResult<File> {14
let mut options = std::fs::OpenOptions::new();15
options.write(true).create(true).truncate(true);16
#[cfg(unix)]17
{18
use std::os::unix::fs::OpenOptionsExt;19
options.mode(0o600);20
}21
options.open(destination).map_err(failed)22
}24
/// Sorted recursive walk (directories before their content), no symlinks.25
fn walk(root: &Path, relative: &Path, out: &mut Vec<(std::path::PathBuf, bool)>) -> CoreResult<()> {26
let mut entries: Vec<_> = std::fs::read_dir(root.join(relative)).map_err(failed)?.filter_map(Result::ok).collect();27
entries.sort_by_key(|e| e.file_name());28
for entry in entries {29
let kind = entry.file_type().map_err(failed)?;30
let rel = relative.join(entry.file_name());31
if kind.is_dir() {32
out.push((rel.clone(), true));33
walk(root, &rel, out)?;34
} else if kind.is_file() {35
out.push((rel, false));36
}37
}38
Ok(())39
}41
fn slash(path: &Path) -> String {42
path.components().map(|c| c.as_os_str().to_string_lossy().to_string()).collect::<Vec<_>>().join("/")43
}45
/// Creates the archive and returns its size in bytes.46
pub fn create(source: &Path, destination: &Path, format: &str) -> CoreResult<u64> {47
let mut entries = Vec::new();48
walk(source, Path::new(""), &mut entries)?;49
let file = create_private(destination)?;50
match format {51
"zip" => {52
let mut zip = zip::ZipWriter::new(BufWriter::new(file));53
let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Deflated).compression_level(Some(9)).large_file(true);54
for (rel, dir) in &entries {55
let name = slash(rel);56
if *dir {57
zip.add_directory(format!("{name}/"), options).map_err(failed)?;58
} else {59
zip.start_file(name, options).map_err(failed)?;60
std::io::copy(&mut File::open(source.join(rel)).map_err(failed)?, &mut zip).map_err(failed)?;61
}62
}63
let mut inner = zip.finish().map_err(failed)?;64
inner.flush().map_err(failed)?;65
}66
_ => {67
let writer: Box<dyn Write> = if format == "tar.gz" { Box::new(flate2::write::GzEncoder::new(BufWriter::new(file), flate2::Compression::new(9))) } else { Box::new(BufWriter::new(file)) };68
let mut tar = tar::Builder::new(writer);69
tar.follow_symlinks(false);70
for (rel, dir) in &entries {71
if *dir {72
tar.append_dir(rel, source.join(rel)).map_err(failed)?;73
} else {74
tar.append_path_with_name(source.join(rel), rel).map_err(failed)?;75
}76
}77
let mut inner = tar.into_inner().map_err(failed)?;78
inner.flush().map_err(failed)?;79
drop(inner);80
}81
}82
Ok(std::fs::metadata(destination).map_err(failed)?.len())83
}85
#[cfg(test)]86
mod tests {87
use super::*;89
#[test]90
fn creates_all_formats() {91
let dir = tempfile::tempdir().unwrap();92
let src = dir.path().join("src");93
std::fs::create_dir_all(src.join("a/b")).unwrap();94
std::fs::write(src.join("a/b/f.txt"), "hello").unwrap();95
std::fs::write(src.join("top.txt"), "x").unwrap();96
for format in ["zip", "tar", "tar.gz"] {97
let out = dir.path().join(format!("out.{format}"));98
assert!(create(&src, &out, format).unwrap() > 0);99
}100
let mut zip = zip::ZipArchive::new(File::open(dir.path().join("out.zip")).unwrap()).unwrap();101
let mut text = String::new();102
std::io::Read::read_to_string(&mut zip.by_name("a/b/f.txt").unwrap(), &mut text).unwrap();103
assert_eq!(text, "hello");104
let mut archive = tar::Archive::new(flate2::read::GzDecoder::new(File::open(dir.path().join("out.tar.gz")).unwrap()));105
let names: Vec<String> = archive.entries().unwrap().map(|e| e.unwrap().path().unwrap().to_string_lossy().to_string()).collect();106
assert!(names.contains(&"a/b/f.txt".to_string()) && names.contains(&"top.txt".to_string()));107
}108
}SHA-256
e8ec0a407157a5e6e951c84987cb3ca8d423f16470e298689e68e40a3f6f6baeCH-J Proprietary Software License 1.14 · Tá Foinse ar fáil faoin CH-J Proprietary Software License 1.14. Ní chuireann a infhaighteacht a athrú ar na téarmaí ceadúnais nó cearta breise a dheonú.
Cartlann SHA-256
ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f