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]]
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"
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"
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"
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",
+]
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"
--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
--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
```
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
```
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;
.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")
#[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>,
}
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());
}
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") {
args.tls_cert = None;
args.tls_key = None;
}
+ println!("{args:?}");
Ok(args)
}
}
}
+#[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>,
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);
}
});
dir: &Path,
access_paths: AccessPaths,
hidden: &[String],
+ compression: Compression,
running: Arc<AtomicBool>,
) -> Result<()> {
let mut writer = ZipFileWriter::with_tokio(writer);
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?;
}
#[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!(