Compare commits

..

4 Commits

Author SHA1 Message Date
sigoden
06d2b81824 fix: optimize download zip 2022-05-28 22:28:49 +08:00
sigoden
3673a64ec7 feat: aware RUST_LOG 2022-05-28 20:43:20 +08:00
sigoden
d9a917176a fix: cannot upload in root 2022-05-28 20:40:49 +08:00
sigoden
cdb7b5fc87 docs: improve readme 2022-05-28 19:48:54 +08:00
7 changed files with 50 additions and 24 deletions

2
Cargo.lock generated
View File

@@ -258,7 +258,7 @@ dependencies = [
[[package]] [[package]]
name = "duf" name = "duf"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"async-walkdir", "async-walkdir",
"async_zip", "async_zip",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "duf" name = "duf"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
authors = ["sigoden <sigoden@gmail.com>"] authors = ["sigoden <sigoden@gmail.com>"]
description = "Duf is a simple file server." description = "Duf is a simple file server."

View File

@@ -36,16 +36,19 @@ You can run this command to start serving your current working directory on 127.
duf duf
``` ```
...or specify which folder you want to serve: ...or specify which folder you want to serve.
``` ```
duf folder_name duf folder_name
``` ```
Only serve static files, disable upload and delete operations
```
duf --static
```
Finally, run this command to see a list of all available option Finally, run this command to see a list of all available option
### Curl ### Curl
Download a file Download a file

View File

@@ -31,7 +31,7 @@ fn app() -> clap::Command<'static> {
let arg_static = Arg::new("static") let arg_static = Arg::new("static")
.long("static") .long("static")
.help("Only serve static files, not allowed to upload or delete file"); .help("Only serve static files, disable upload and delete operations");
let arg_auth = Arg::new("auth") let arg_auth = Arg::new("auth")
.short('a') .short('a')

View File

@@ -45,20 +45,21 @@
class Uploader { class Uploader {
idx = 0; idx = 0;
file; file;
path;
$elem; $elem;
constructor(idx, file) { constructor(idx, file) {
this.idx = idx; this.idx = idx;
this.file = file; this.file = file;
this.path = location.pathname + "/" + file.name;
} }
upload() { upload() {
const { file, idx, path } = this; const { file, idx } = this;
let url = location.href.split('?')[0];
if (!url.endsWith("/")) url += "/";
url += encodeURI(file.name);
$uploaders.insertAdjacentHTML("beforeend", ` $uploaders.insertAdjacentHTML("beforeend", `
<div class="uploader path"> <div class="uploader path">
<div><svg height="16" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg></div> <div><svg height="16" viewBox="0 0 12 16" width="12"><path fill-rule="evenodd" d="M6 5H2V4h4v1zM2 8h7V7H2v1zm0 2h7V9H2v1zm0 2h7v-1H2v1zm10-7.5V14c0 .55-.45 1-1 1H1c-.55 0-1-.45-1-1V2c0-.55.45-1 1-1h7.5L12 4.5zM11 5L8 2H1v12h10V5z"></path></svg></div>
<a href="${path}" id="file${idx}">${file.name} (0%)</a> <a href="${url}" id="file${idx}">${file.name} (0%)</a>
</div>`); </div>`);
this.$elem = document.getElementById(`file${idx}`); this.$elem = document.getElementById(`file${idx}`);
@@ -67,7 +68,7 @@
ajax.addEventListener("load", e => this.complete(e), false); ajax.addEventListener("load", e => this.complete(e), false);
ajax.addEventListener("error", e => this.fail(e), false); ajax.addEventListener("error", e => this.fail(e), false);
ajax.addEventListener("abort", e => this.fail(e), false); ajax.addEventListener("abort", e => this.fail(e), false);
ajax.open("PUT", path); ajax.open("PUT", url);
ajax.send(file); ajax.send(file);
} }

View File

@@ -25,14 +25,18 @@ async fn main() {
async fn run() -> BoxResult<()> { async fn run() -> BoxResult<()> {
let args = Args::parse(matches())?; let args = Args::parse(matches())?;
let level = if args.log { if std::env::var("RUST_LOG").is_ok() {
LevelFilter::Info simple_logger::init()?;
} else { } else {
LevelFilter::Error let level = if args.log {
}; LevelFilter::Info
simple_logger::SimpleLogger::default() } else {
.with_level(level) LevelFilter::Error
.init()?; };
simple_logger::SimpleLogger::default()
.with_level(level)
.init()?;
}
serve(args).await serve(args).await
} }

View File

@@ -5,6 +5,7 @@ use async_zip::write::{EntryOptions, ZipFileWriter};
use async_zip::Compression; use async_zip::Compression;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use futures::TryStreamExt; use futures::TryStreamExt;
use hyper::body::Bytes;
use hyper::header::HeaderValue; use hyper::header::HeaderValue;
use hyper::service::{make_service_fn, service_fn}; use hyper::service::{make_service_fn, service_fn};
use hyper::{Body, Method, StatusCode}; use hyper::{Body, Method, StatusCode};
@@ -15,10 +16,9 @@ use std::path::{Path, PathBuf};
use std::sync::Arc; use std::sync::Arc;
use std::time::SystemTime; use std::time::SystemTime;
use tokio::fs::File; use tokio::fs::File;
use tokio::io::AsyncWrite; use tokio::io::{AsyncReadExt, AsyncWrite};
use tokio::{fs, io}; use tokio::{fs, io};
use tokio_util::codec::{BytesCodec, FramedRead}; use tokio_util::codec::{BytesCodec, FramedRead};
use tokio_util::io::ReaderStream;
use tokio_util::io::StreamReader; use tokio_util::io::StreamReader;
type Request = hyper::Request<Body>; type Request = hyper::Request<Body>;
@@ -35,6 +35,7 @@ macro_rules! status_code {
const INDEX_HTML: &str = include_str!("index.html"); const INDEX_HTML: &str = include_str!("index.html");
const INDEX_CSS: &str = include_str!("index.css"); const INDEX_CSS: &str = include_str!("index.css");
const BUF_SIZE: usize = 1024 * 16;
pub async fn serve(args: Args) -> BoxResult<()> { pub async fn serve(args: Args) -> BoxResult<()> {
let address = args.address()?; let address = args.address()?;
@@ -187,10 +188,27 @@ impl InnerService {
} }
async fn handle_send_dir_zip(&self, path: &Path) -> BoxResult<Response> { async fn handle_send_dir_zip(&self, path: &Path) -> BoxResult<Response> {
let (mut writer, reader) = tokio::io::duplex(65536); let (mut tx, body) = Body::channel();
dir_zip(&mut writer, path).await?; let (mut writer, mut reader) = tokio::io::duplex(BUF_SIZE);
let stream = ReaderStream::new(reader); let path = path.to_owned();
let body = Body::wrap_stream(stream); tokio::spawn(async move {
if let Err(e) = dir_zip(&mut writer, &path).await {
error!("Fail to zip {}, {}", path.display(), e.to_string());
}
});
tokio::spawn(async move {
// Reuse this buffer
let mut buf = [0_u8; BUF_SIZE];
loop {
let n = reader.read(&mut buf).await.unwrap();
if n == 0 {
break;
}
if (tx.send_data(Bytes::from(buf[..n].to_vec())).await).is_err() {
break;
}
}
});
Ok(Response::new(body)) Ok(Response::new(body))
} }