]> OzVa Git service - ozva-cloud/commitdiff
feat: add `--compress` option (#319)
authorsigoden <sigoden@gmail.com>
Thu, 14 Dec 2023 10:59:28 +0000 (18:59 +0800)
committerGitHub <noreply@github.com>
Thu, 14 Dec 2023 10:59:28 +0000 (18:59 +0800)
Cargo.lock
Cargo.toml
README.md
src/args.rs
src/server.rs
tests/http.rs

index 50f94e41fc8e20cb9686dfa0ae5ea1d6e4496269..4b77a4417acd5626120065c6db2bb7115c0949ba 100644 (file)
@@ -137,11 +137,13 @@ version = "0.3.15"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a"
 dependencies = [
+ "bzip2",
  "flate2",
  "futures-core",
  "futures-io",
  "memchr",
  "pin-project-lite",
+ "xz2",
 ]
 
 [[package]]
@@ -271,6 +273,27 @@ version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
 
+[[package]]
+name = "bzip2"
+version = "0.4.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
+dependencies = [
+ "bzip2-sys",
+ "libc",
+]
+
+[[package]]
+name = "bzip2-sys"
+version = "0.1.11+1.0.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
 [[package]]
 name = "cc"
 version = "1.0.83"
@@ -1016,6 +1039,17 @@ version = "0.4.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
 
+[[package]]
+name = "lzma-sys"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+]
+
 [[package]]
 name = "md-5"
 version = "0.10.6"
@@ -1161,6 +1195,12 @@ version = "0.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
 
+[[package]]
+name = "pkg-config"
+version = "0.3.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
+
 [[package]]
 name = "port_check"
 version = "0.1.5"
@@ -2204,3 +2244,12 @@ name = "xml-rs"
 version = "0.8.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
+
+[[package]]
+name = "xz2"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
+dependencies = [
+ "lzma-sys",
+]
index e2cd3b02892e98c7659e1dbd53d543f27195555c..93e7547a9936e47b9a81711f81ee85b086cfeb34 100644 (file)
@@ -21,7 +21,7 @@ percent-encoding = "2.3"
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
 futures = "0.3"
-async_zip = { version = "0.0.15", default-features = false, features = ["deflate", "chrono", "tokio"] }
+async_zip = { version = "0.0.15", default-features = false, features = ["deflate", "bzip2", "xz", "chrono", "tokio"] }
 headers = "0.3"
 mime_guess = "2.0"
 if-addrs = "0.10.1"
index a3c5fe6495d5076111cd96194bc3725501c079f9..25753673c1986e18525ef055299bb13ee35fee8e 100644 (file)
--- a/README.md
+++ b/README.md
@@ -72,6 +72,7 @@ Options:
       --render-spa           Serve SPA(Single Page Application)
       --assets <path>        Set the path to the assets directory for overriding the built-in assets
       --log-format <format>  Customize http log format
+      --compress <level>     Set zip compress level [default: low] [possible values: none, low, medium, high]
       --completions <shell>  Print shell completion script for <shell> [possible values: bash, elvish, fish, powershell, zsh]
       --tls-cert <path>      Path to an SSL/TLS certificate to serve with HTTPS
       --tls-key <path>       Path to the SSL/TLS certificate's private key
@@ -326,6 +327,7 @@ All options can be set using environment variables prefixed with `DUFS_`.
     --render-spa            DUFS_RENDER_SPA=true
     --assets <path>         DUFS_ASSETS=/assets
     --log-format <format>   DUFS_LOG_FORMAT=""
+    --compress <compress>   DUFS_COMPRESS="low"
     --tls-cert <path>       DUFS_TLS_CERT=cert.pem
     --tls-key <path>        DUFS_TLS_KEY=key.pem
 ```
@@ -361,6 +363,7 @@ render-try-index: true
 render-spa: true
 assets: ./assets/
 log-format: '$remote_addr "$request" $status $http_user_agent'
+compress: low
 tls-cert: tests/data/cert.pem
 tls-key: tests/data/key_pkcs1.pem
 ```
index c95941cb3c70d876fa5da95094ac9a5744691488..f2f80ff9dfcfa6bbc62b8ab0727c93a52e8e594e 100644 (file)
@@ -1,6 +1,7 @@
 use anyhow::{bail, Context, Result};
-use clap::builder::PossibleValuesParser;
-use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
+use async_zip::Compression;
+use clap::builder::{PossibleValue, PossibleValuesParser};
+use clap::{value_parser, Arg, ArgAction, ArgMatches, Command, ValueEnum};
 use clap_complete::{generate, Generator, Shell};
 use serde::{Deserialize, Deserializer};
 use smart_default::SmartDefault;
@@ -196,6 +197,15 @@ pub fn build_cli() -> Command {
                 .value_name("format")
                 .help("Customize http log format"),
         )
+        .arg(
+            Arg::new("compress")
+                .env("DUFS_COMPRESS")
+                .hide_env(true)
+                .value_parser(clap::builder::EnumValueParser::<Compress>::new())
+                .long("compress")
+                .value_name("level")
+                .help("Set zip compress level [default: low]")
+        )
         .arg(
             Arg::new("completions")
                 .long("completions")
@@ -270,6 +280,7 @@ pub struct Args {
     #[serde(deserialize_with = "deserialize_log_http")]
     #[serde(rename = "log-format")]
     pub http_logger: HttpLogger,
+    pub compress: Compress,
     pub tls_cert: Option<PathBuf>,
     pub tls_key: Option<PathBuf>,
 }
@@ -369,10 +380,6 @@ impl Args {
             args.render_spa = matches.get_flag("render-spa");
         }
 
-        if let Some(log_format) = matches.get_one::<String>("log-format") {
-            args.http_logger = log_format.parse()?;
-        }
-
         if let Some(assets_path) = matches.get_one::<PathBuf>("assets") {
             args.assets = Some(assets_path.clone());
         }
@@ -381,6 +388,14 @@ impl Args {
             args.assets = Some(Args::sanitize_assets_path(assets_path)?);
         }
 
+        if let Some(log_format) = matches.get_one::<String>("log-format") {
+            args.http_logger = log_format.parse()?;
+        }
+
+        if let Some(compress) = matches.get_one::<Compress>("compress") {
+            args.compress = *compress;
+        }
+
         #[cfg(feature = "tls")]
         {
             if let Some(tls_cert) = matches.get_one::<PathBuf>("tls-cert") {
@@ -403,6 +418,7 @@ impl Args {
             args.tls_cert = None;
             args.tls_key = None;
         }
+        println!("{args:?}");
 
         Ok(args)
     }
@@ -461,6 +477,47 @@ impl BindAddr {
     }
 }
 
+#[derive(Debug, Clone, Copy, PartialEq, Deserialize)]
+#[serde(rename_all = "lowercase")]
+pub enum Compress {
+    None,
+    Low,
+    Medium,
+    High,
+}
+
+impl Default for Compress {
+    fn default() -> Self {
+        Self::Low
+    }
+}
+
+impl ValueEnum for Compress {
+    fn value_variants<'a>() -> &'a [Self] {
+        &[Self::None, Self::Low, Self::Medium, Self::High]
+    }
+
+    fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
+        Some(match self {
+            Compress::None => PossibleValue::new("none"),
+            Compress::Low => PossibleValue::new("low"),
+            Compress::Medium => PossibleValue::new("medium"),
+            Compress::High => PossibleValue::new("high"),
+        })
+    }
+}
+
+impl Compress {
+    pub fn to_compression(self) -> Compression {
+        match self {
+            Compress::None => Compression::Stored,
+            Compress::Low => Compression::Deflate,
+            Compress::Medium => Compression::Bz,
+            Compress::High => Compression::Xz,
+        }
+    }
+}
+
 fn deserialize_bind_addrs<'de, D>(deserializer: D) -> Result<Vec<BindAddr>, D::Error>
 where
     D: Deserializer<'de>,
index 8d9a5e374c5582b1de974fcde25e1e94319d746a..306e22dc6affa867432a2944a9c71a94ab17206b 100644 (file)
@@ -581,8 +581,18 @@ impl Server {
         let path = path.to_owned();
         let hidden = self.args.hidden.clone();
         let running = self.running.clone();
+        let compression = self.args.compress.to_compression();
         tokio::spawn(async move {
-            if let Err(e) = zip_dir(&mut writer, &path, access_paths, &hidden, running).await {
+            if let Err(e) = zip_dir(
+                &mut writer,
+                &path,
+                access_paths,
+                &hidden,
+                compression,
+                running,
+            )
+            .await
+            {
                 error!("Failed to zip {}, {}", path.display(), e);
             }
         });
@@ -1422,6 +1432,7 @@ async fn zip_dir<W: AsyncWrite + Unpin>(
     dir: &Path,
     access_paths: AccessPaths,
     hidden: &[String],
+    compression: Compression,
     running: Arc<AtomicBool>,
 ) -> Result<()> {
     let mut writer = ZipFileWriter::with_tokio(writer);
@@ -1475,7 +1486,7 @@ async fn zip_dir<W: AsyncWrite + Unpin>(
             None => continue,
         };
         let (datetime, mode) = get_file_mtime_and_mode(&zip_path).await?;
-        let builder = ZipEntryBuilder::new(filename.into(), Compression::Deflate)
+        let builder = ZipEntryBuilder::new(filename.into(), compression)
             .unix_permissions(mode)
             .last_modification_date(ZipDateTime::from_chrono(&datetime));
         let mut file = File::open(&zip_path).await?;
index fc6583dab4a30d6305f5529859709e9c51147069..67fb328762021333d95b825cd26bdfec0fb24fc1 100644 (file)
@@ -40,7 +40,12 @@ fn head_dir_404(server: TestServer) -> Result<(), Error> {
 }
 
 #[rstest]
-fn get_dir_zip(#[with(&["-A"])] server: TestServer) -> Result<(), Error> {
+#[case(server(&["--allow-archive"] as &[&str]))]
+#[case(server(&["--allow-archive", "--compress", "none"]))]
+#[case(server(&["--allow-archive", "--compress", "low"]))]
+#[case(server(&["--allow-archive", "--compress", "medium"]))]
+#[case(server(&["--allow-archive", "--compress", "high"]))]
+fn get_dir_zip(#[case] server: TestServer) -> Result<(), Error> {
     let resp = reqwest::blocking::get(format!("{}?zip", server.url()))?;
     assert_eq!(resp.status(), 200);
     assert_eq!(