CH-J ToolsSSH によるサーバー管理
メニュー
公開済みのソース

CH-J Tools

特定のアプリケーションリリースのディレクトリとファイルを閲覧できます。

ソースの ZIP をダウンロード
CH-J / Server Manager 1704 ファイル
0.1.2-beta 追加日時: · ビルド日時:
archive.rs 追加日時: 日付の記録なし 4.5 KB · 108 行
コード ファイルをダウンロード
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.
4use crate::core::error::{CoreError, CoreResult};
5use std::fs::File;
6use std::io::{BufWriter, Write};
7use std::path::Path;
9fn failed(error: impl std::fmt::Display) -> CoreError {
10 CoreError::plain(format!("Archive creation failed: {error}"))
13fn 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)
24/// Sorted recursive walk (directories before their content), no symlinks.
25fn 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(())
41fn slash(path: &Path) -> String {
42 path.components().map(|c| c.as_os_str().to_string_lossy().to_string()).collect::<Vec<_>>().join("/")
45/// Creates the archive and returns its size in bytes.
46pub 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())
85#[cfg(test)]
86mod 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 }
SHA-256e8ec0a407157a5e6e951c84987cb3ca8d423f16470e298689e68e40a3f6f6bae

CH-J Proprietary Software License 1.14 · ソースは CH-J Proprietary Software License 1.14 に基づいて提供されています。公開されていても、ライセンス条項が変わったり、追加の権利が付与されたりすることはありません。

アーカイブの SHA-256ac8fe790f4ba60fc098f8c985fc2656cfcc6cdc21fb34b30e308a2b54e62258f
ファイル