feat: add --compress
option (#319)
This commit is contained in:
parent
cd84dff87f
commit
3873f4794a
6 changed files with 135 additions and 10 deletions
49
Cargo.lock
generated
49
Cargo.lock
generated
|
@ -137,11 +137,13 @@ version = "0.3.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a"
|
checksum = "942c7cd7ae39e91bde4820d74132e9862e62c2f386c3aa90ccf55949f5bad63a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bzip2",
|
||||||
"flate2",
|
"flate2",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"memchr",
|
"memchr",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
|
"xz2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -271,6 +273,27 @@ version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
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]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.83"
|
version = "1.0.83"
|
||||||
|
@ -1016,6 +1039,17 @@ version = "0.4.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
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]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.10.6"
|
version = "0.10.6"
|
||||||
|
@ -1161,6 +1195,12 @@ version = "0.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pkg-config"
|
||||||
|
version = "0.3.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "port_check"
|
name = "port_check"
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
|
@ -2204,3 +2244,12 @@ name = "xml-rs"
|
||||||
version = "0.8.19"
|
version = "0.8.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
|
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xz2"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2"
|
||||||
|
dependencies = [
|
||||||
|
"lzma-sys",
|
||||||
|
]
|
||||||
|
|
|
@ -21,7 +21,7 @@ percent-encoding = "2.3"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
futures = "0.3"
|
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"
|
headers = "0.3"
|
||||||
mime_guess = "2.0"
|
mime_guess = "2.0"
|
||||||
if-addrs = "0.10.1"
|
if-addrs = "0.10.1"
|
||||||
|
|
|
@ -72,6 +72,7 @@ Options:
|
||||||
--render-spa Serve SPA(Single Page Application)
|
--render-spa Serve SPA(Single Page Application)
|
||||||
--assets <path> Set the path to the assets directory for overriding the built-in assets
|
--assets <path> Set the path to the assets directory for overriding the built-in assets
|
||||||
--log-format <format> Customize http log format
|
--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]
|
--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-cert <path> Path to an SSL/TLS certificate to serve with HTTPS
|
||||||
--tls-key <path> Path to the SSL/TLS certificate's private key
|
--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
|
--render-spa DUFS_RENDER_SPA=true
|
||||||
--assets <path> DUFS_ASSETS=/assets
|
--assets <path> DUFS_ASSETS=/assets
|
||||||
--log-format <format> DUFS_LOG_FORMAT=""
|
--log-format <format> DUFS_LOG_FORMAT=""
|
||||||
|
--compress <compress> DUFS_COMPRESS="low"
|
||||||
--tls-cert <path> DUFS_TLS_CERT=cert.pem
|
--tls-cert <path> DUFS_TLS_CERT=cert.pem
|
||||||
--tls-key <path> DUFS_TLS_KEY=key.pem
|
--tls-key <path> DUFS_TLS_KEY=key.pem
|
||||||
```
|
```
|
||||||
|
@ -361,6 +363,7 @@ render-try-index: true
|
||||||
render-spa: true
|
render-spa: true
|
||||||
assets: ./assets/
|
assets: ./assets/
|
||||||
log-format: '$remote_addr "$request" $status $http_user_agent'
|
log-format: '$remote_addr "$request" $status $http_user_agent'
|
||||||
|
compress: low
|
||||||
tls-cert: tests/data/cert.pem
|
tls-cert: tests/data/cert.pem
|
||||||
tls-key: tests/data/key_pkcs1.pem
|
tls-key: tests/data/key_pkcs1.pem
|
||||||
```
|
```
|
||||||
|
|
69
src/args.rs
69
src/args.rs
|
@ -1,6 +1,7 @@
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
use clap::builder::PossibleValuesParser;
|
use async_zip::Compression;
|
||||||
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
use clap::builder::{PossibleValue, PossibleValuesParser};
|
||||||
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command, ValueEnum};
|
||||||
use clap_complete::{generate, Generator, Shell};
|
use clap_complete::{generate, Generator, Shell};
|
||||||
use serde::{Deserialize, Deserializer};
|
use serde::{Deserialize, Deserializer};
|
||||||
use smart_default::SmartDefault;
|
use smart_default::SmartDefault;
|
||||||
|
@ -196,6 +197,15 @@ pub fn build_cli() -> Command {
|
||||||
.value_name("format")
|
.value_name("format")
|
||||||
.help("Customize http log 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(
|
||||||
Arg::new("completions")
|
Arg::new("completions")
|
||||||
.long("completions")
|
.long("completions")
|
||||||
|
@ -270,6 +280,7 @@ pub struct Args {
|
||||||
#[serde(deserialize_with = "deserialize_log_http")]
|
#[serde(deserialize_with = "deserialize_log_http")]
|
||||||
#[serde(rename = "log-format")]
|
#[serde(rename = "log-format")]
|
||||||
pub http_logger: HttpLogger,
|
pub http_logger: HttpLogger,
|
||||||
|
pub compress: Compress,
|
||||||
pub tls_cert: Option<PathBuf>,
|
pub tls_cert: Option<PathBuf>,
|
||||||
pub tls_key: Option<PathBuf>,
|
pub tls_key: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
@ -369,10 +380,6 @@ impl Args {
|
||||||
args.render_spa = matches.get_flag("render-spa");
|
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") {
|
if let Some(assets_path) = matches.get_one::<PathBuf>("assets") {
|
||||||
args.assets = Some(assets_path.clone());
|
args.assets = Some(assets_path.clone());
|
||||||
}
|
}
|
||||||
|
@ -381,6 +388,14 @@ impl Args {
|
||||||
args.assets = Some(Args::sanitize_assets_path(assets_path)?);
|
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")]
|
#[cfg(feature = "tls")]
|
||||||
{
|
{
|
||||||
if let Some(tls_cert) = matches.get_one::<PathBuf>("tls-cert") {
|
if let Some(tls_cert) = matches.get_one::<PathBuf>("tls-cert") {
|
||||||
|
@ -403,6 +418,7 @@ impl Args {
|
||||||
args.tls_cert = None;
|
args.tls_cert = None;
|
||||||
args.tls_key = None;
|
args.tls_key = None;
|
||||||
}
|
}
|
||||||
|
println!("{args:?}");
|
||||||
|
|
||||||
Ok(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>
|
fn deserialize_bind_addrs<'de, D>(deserializer: D) -> Result<Vec<BindAddr>, D::Error>
|
||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
|
|
|
@ -581,8 +581,18 @@ impl Server {
|
||||||
let path = path.to_owned();
|
let path = path.to_owned();
|
||||||
let hidden = self.args.hidden.clone();
|
let hidden = self.args.hidden.clone();
|
||||||
let running = self.running.clone();
|
let running = self.running.clone();
|
||||||
|
let compression = self.args.compress.to_compression();
|
||||||
tokio::spawn(async move {
|
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);
|
error!("Failed to zip {}, {}", path.display(), e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -1422,6 +1432,7 @@ async fn zip_dir<W: AsyncWrite + Unpin>(
|
||||||
dir: &Path,
|
dir: &Path,
|
||||||
access_paths: AccessPaths,
|
access_paths: AccessPaths,
|
||||||
hidden: &[String],
|
hidden: &[String],
|
||||||
|
compression: Compression,
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut writer = ZipFileWriter::with_tokio(writer);
|
let mut writer = ZipFileWriter::with_tokio(writer);
|
||||||
|
@ -1475,7 +1486,7 @@ async fn zip_dir<W: AsyncWrite + Unpin>(
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
let (datetime, mode) = get_file_mtime_and_mode(&zip_path).await?;
|
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)
|
.unix_permissions(mode)
|
||||||
.last_modification_date(ZipDateTime::from_chrono(&datetime));
|
.last_modification_date(ZipDateTime::from_chrono(&datetime));
|
||||||
let mut file = File::open(&zip_path).await?;
|
let mut file = File::open(&zip_path).await?;
|
||||||
|
|
|
@ -40,7 +40,12 @@ fn head_dir_404(server: TestServer) -> Result<(), Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[rstest]
|
#[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()))?;
|
let resp = reqwest::blocking::get(format!("{}?zip", server.url()))?;
|
||||||
assert_eq!(resp.status(), 200);
|
assert_eq!(resp.status(), 200);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
|
Loading…
Reference in a new issue