Some small changes to make it work for my uses
This commit is contained in:
parent
ba6c98a314
commit
1a5cee437c
8 changed files with 108 additions and 215 deletions
79
Cargo.lock
generated
79
Cargo.lock
generated
|
@ -563,28 +563,6 @@ version = "1.0.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "filetime"
|
||||
version = "0.2.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.4.1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.30"
|
||||
|
@ -795,7 +773,6 @@ dependencies = [
|
|||
"futures",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"tar",
|
||||
"v_htmlescape",
|
||||
]
|
||||
|
||||
|
@ -876,15 +853,9 @@ checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.153"
|
||||
version = "0.2.161"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
|
||||
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||
|
||||
[[package]]
|
||||
name = "local-channel"
|
||||
|
@ -1007,7 +978,7 @@ checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall 0.5.1",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
@ -1102,15 +1073,6 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.1"
|
||||
|
@ -1164,19 +1126,6 @@ dependencies = [
|
|||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f"
|
||||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
|
@ -1311,17 +1260,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar"
|
||||
version = "0.4.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b16afcea1f22891c49a00c751c7b63b2233284064f11a200fc624137c51e2ddb"
|
||||
dependencies = [
|
||||
"filetime",
|
||||
"libc",
|
||||
"xattr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
|
@ -1674,17 +1612,6 @@ version = "0.52.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0"
|
||||
|
||||
[[package]]
|
||||
name = "xattr"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8da84f1a25939b27f6820d92aed108f83ff920fdf11a7b19366c27c4cda81d4f"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"rustix",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.34"
|
||||
|
|
|
@ -15,7 +15,6 @@ clap = { version = "3.2.20", features = ["cargo"] }
|
|||
env_logger = "*"
|
||||
log = "*"
|
||||
futures = "0.3.24"
|
||||
tar = "0.4.38"
|
||||
percent-encoding = "2.2"
|
||||
v_htmlescape = "0.15"
|
||||
|
||||
|
|
|
@ -27,46 +27,56 @@ pub fn directory_listing(
|
|||
let mut body = String::new();
|
||||
let base = Path::new(req.path());
|
||||
|
||||
for entry in dir.path.read_dir()? {
|
||||
if dir.is_visible(&entry) {
|
||||
let entry = entry.unwrap();
|
||||
let p = match entry.path().strip_prefix(&dir.path) {
|
||||
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace('\\', "/"),
|
||||
Ok(p) => base.join(p).to_string_lossy().into_owned(),
|
||||
Err(_) => continue,
|
||||
};
|
||||
let mut paths: Vec<_> = dir.path.read_dir()?.map(|r| r.unwrap()).collect();
|
||||
paths.sort_by(|a, b| {
|
||||
let (oan, obn) = (a.file_name(), b.file_name());
|
||||
let (an, bn) = (oan.to_str().unwrap(), obn.to_str().unwrap());
|
||||
|
||||
// if file is a directory, add '/' to the end of the name
|
||||
if let Ok(metadata) = entry.metadata() {
|
||||
if metadata.is_dir() {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<tr><td>📂 <a href='{}/'>{}/</a></td> <td><small>[<a href='{}.tar'>.tar</a>]</small></td></tr>",
|
||||
encode_file_url!(p),
|
||||
encode_file_name!(entry),
|
||||
encode_file_url!(p),
|
||||
);
|
||||
} else {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<tr><td>🗎 <a href='{}'>{}</a></td> <td>{}</td></tr>",
|
||||
encode_file_url!(p),
|
||||
encode_file_name!(entry),
|
||||
metadata.len(),
|
||||
);
|
||||
}
|
||||
if let (Ok(am), Ok(bm)) = (a.metadata(), b.metadata()) {
|
||||
return bm.is_dir().cmp(&am.is_dir()).then(an.cmp(bn));
|
||||
} else {
|
||||
return an.cmp(bn);
|
||||
}
|
||||
});
|
||||
|
||||
for entry in paths {
|
||||
let p = match entry.path().strip_prefix(&dir.path) {
|
||||
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace('\\', "/"),
|
||||
Ok(p) => base.join(p).to_string_lossy().into_owned(),
|
||||
Err(_) => continue,
|
||||
};
|
||||
|
||||
if !base.has_root() {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<tr><td><a href='../'>../</a></td><td>Size</td></tr>\n"
|
||||
);
|
||||
}
|
||||
|
||||
// if file is a directory, add '/' to the end of the name
|
||||
if let Ok(metadata) = entry.metadata() {
|
||||
if metadata.is_dir() {
|
||||
let _ = write!(
|
||||
body,
|
||||
"<tr><td><a href='{}/'>{}/</a></td> <td>dir</td></tr>",
|
||||
encode_file_url!(p),
|
||||
encode_file_name!(entry),
|
||||
);
|
||||
} else {
|
||||
continue;
|
||||
let _ = write!(
|
||||
body,
|
||||
"<tr><td><a href='{}'>{}</a></td> <td>{}</td></tr>",
|
||||
encode_file_url!(p),
|
||||
encode_file_name!(entry),
|
||||
metadata.len(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let header = format!(
|
||||
"<h1>Index of {}/</h1>\n\
|
||||
<small>[<a href='{}.tar'>.tar</a> of whole directory]</small>",
|
||||
index_of,
|
||||
if index_of.is_empty() { "_" } else { index_of }
|
||||
);
|
||||
let header = format!("<h1>Index of {}/</h1>\n", index_of);
|
||||
|
||||
let footer = format!(
|
||||
r#"<footer><a href="{}">{} {}</a></footer>"#,
|
||||
|
@ -81,11 +91,10 @@ pub fn directory_listing(
|
|||
"<!DOCTYPE html>\n\
|
||||
<html>\n\
|
||||
<head>\n\
|
||||
<title>Index of {}</title>\n\
|
||||
<title>Index of {}/</title>\n\
|
||||
<style>\n{}</style></head>\n\
|
||||
<body>\n{}\n\
|
||||
<table>\n\
|
||||
<tr><td>📁 <a href='../'>../</a></td><td>Size</td></tr>\n\
|
||||
{}\
|
||||
</table>\n\
|
||||
{}\
|
||||
|
|
BIN
src/favicon.png
BIN
src/favicon.png
Binary file not shown.
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 135 KiB |
|
@ -1,5 +1,4 @@
|
|||
mod directory_listing;
|
||||
mod threaded_archiver;
|
||||
mod web;
|
||||
|
||||
#[actix_web::main]
|
||||
|
|
|
@ -1,14 +1,35 @@
|
|||
body {background-color: #282f34; color: #aeb9a6; font-family:monospace; font-size:10.5pt;}
|
||||
a { color: #adad9d; }
|
||||
footer a {color: darkgray; text-decoration:none; font-size:smaller}
|
||||
h1 {margin-bottom: 0}
|
||||
table td:nth-child(2) {text-align:right}
|
||||
body {
|
||||
background-color: #282f34;
|
||||
color: #aeb9a6;
|
||||
font-family: monospace;
|
||||
font-size: 10.5pt;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #adad9d;
|
||||
}
|
||||
|
||||
footer a {
|
||||
color: darkgray;
|
||||
text-decoration: none;
|
||||
font-size: smaller
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin-bottom: 0
|
||||
}
|
||||
|
||||
table td:nth-child(2) {
|
||||
text-align: right
|
||||
}
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
margin: 1em auto;
|
||||
width: 100%;
|
||||
max-width: 35em;
|
||||
margin: 1em 0;
|
||||
padding: 0.5em 0;
|
||||
border-top: 1px;
|
||||
border-bottom: 1px;
|
||||
border-color: #444;
|
||||
border-style: solid none;
|
||||
}
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
use futures::prelude::*;
|
||||
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::thread;
|
||||
|
||||
/*
|
||||
* TODO:
|
||||
* don't tar hidden files
|
||||
*/
|
||||
|
||||
type Stream = futures::channel::mpsc::Receiver<bytes::Bytes>;
|
||||
type Sender = futures::channel::mpsc::Sender<bytes::Bytes>;
|
||||
|
||||
pub fn stream_tar_in_thread<P>(path: P) -> Stream
|
||||
where
|
||||
P: AsRef<Path> + Send + 'static,
|
||||
{
|
||||
let (writer, stream) = StreamWriter::new(64);
|
||||
|
||||
thread::spawn(move || {
|
||||
let mut a = tar::Builder::new(writer);
|
||||
let last_path_component = path.as_ref().file_name().unwrap();
|
||||
a.mode(tar::HeaderMode::Deterministic);
|
||||
a.append_dir_all(last_path_component, &path)
|
||||
.unwrap_or_else(|e| println!("{}", e));
|
||||
a.finish().unwrap_or_else(|e| println!("{}", e));
|
||||
});
|
||||
stream
|
||||
}
|
||||
|
||||
struct StreamWriter {
|
||||
tx: Sender,
|
||||
}
|
||||
|
||||
impl StreamWriter {
|
||||
fn new(size: usize) -> (Self, Stream) {
|
||||
let (tx, rx) = futures::channel::mpsc::channel(size);
|
||||
(StreamWriter { tx }, rx)
|
||||
}
|
||||
}
|
||||
|
||||
impl io::Write for StreamWriter {
|
||||
fn write(&mut self, data: &[u8]) -> io::Result<usize> {
|
||||
let len = data.len();
|
||||
futures::executor::block_on(async move {
|
||||
let buf = bytes::Bytes::copy_from_slice(data);
|
||||
self.tx.send(buf).await.ok(); // maybe propagate any errors back
|
||||
});
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
futures::executor::block_on(async move { self.tx.flush().await.ok() });
|
||||
Ok(())
|
||||
}
|
||||
}
|
67
src/web.rs
67
src/web.rs
|
@ -1,8 +1,8 @@
|
|||
use actix_files::{Files, NamedFile};
|
||||
use actix_files::Files;
|
||||
use actix_web::{
|
||||
get, middleware, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
|
||||
dev::{fn_service, ServiceRequest, ServiceResponse},
|
||||
get, middleware, App, HttpResponse, HttpServer, Responder,
|
||||
};
|
||||
use futures::StreamExt;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -10,15 +10,41 @@ pub async fn run(bind_addr: &str, root: &PathBuf) -> std::io::Result<()> {
|
|||
let root_ = root.clone();
|
||||
let s = HttpServer::new(move || {
|
||||
let static_files = Files::new("/", &root_)
|
||||
.prefer_utf8(true)
|
||||
.index_file("index.html")
|
||||
.use_hidden_files()
|
||||
.show_files_listing()
|
||||
.redirect_to_slash_directory()
|
||||
.files_listing_renderer(crate::directory_listing::directory_listing);
|
||||
.files_listing_renderer(crate::directory_listing::directory_listing)
|
||||
.default_handler(fn_service(|req: ServiceRequest| async {
|
||||
let (req, _) = req.into_parts();
|
||||
let style = include_str!("style.css");
|
||||
|
||||
let html = format!(
|
||||
"<!DOCTYPE html>\n\
|
||||
<html>\n\
|
||||
<head>\n\
|
||||
<title>Error</title>\n\
|
||||
<style>\n{}</style></head>\n\
|
||||
<body>\n\
|
||||
<h1>Error</h1>\
|
||||
<p>File not found</p>\
|
||||
</body>\n</html>",
|
||||
style
|
||||
);
|
||||
|
||||
Ok(ServiceResponse::new(
|
||||
req,
|
||||
HttpResponse::NotFound()
|
||||
.content_type("text/html; charset=utf-8")
|
||||
.body(html),
|
||||
))
|
||||
}));
|
||||
|
||||
App::new()
|
||||
.app_data(root_.clone())
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(favicon_ico)
|
||||
.service(handle_tar)
|
||||
.service(static_files)
|
||||
})
|
||||
.bind(bind_addr)?
|
||||
|
@ -28,37 +54,6 @@ pub async fn run(bind_addr: &str, root: &PathBuf) -> std::io::Result<()> {
|
|||
s.await
|
||||
}
|
||||
|
||||
#[get("/{tail:.*}.tar")]
|
||||
async fn handle_tar(
|
||||
req: HttpRequest,
|
||||
root: web::Data<PathBuf>,
|
||||
tail: web::Path<String>,
|
||||
) -> impl Responder {
|
||||
let relpath = PathBuf::from(tail.trim_end_matches('/'));
|
||||
let fullpath = root.join(&relpath).canonicalize().unwrap();
|
||||
|
||||
// if a .tar already exists, just return it as-is
|
||||
let mut fullpath_tar = fullpath.clone();
|
||||
fullpath_tar.set_extension("tar");
|
||||
if fullpath_tar.is_file() {
|
||||
return NamedFile::open_async(fullpath_tar)
|
||||
.await
|
||||
.unwrap()
|
||||
.into_response(&req);
|
||||
}
|
||||
|
||||
if !(fullpath.is_dir()) {
|
||||
return HttpResponse::NotFound().body("Directory not found\n");
|
||||
}
|
||||
|
||||
let stream = crate::threaded_archiver::stream_tar_in_thread(fullpath).map(Ok::<_, Error>);
|
||||
let response = HttpResponse::Ok()
|
||||
.content_type("application/x-tar")
|
||||
.streaming(stream);
|
||||
|
||||
response
|
||||
}
|
||||
|
||||
const FAVICON_ICO: &[u8] = include_bytes!("favicon.png");
|
||||
|
||||
#[get("/favicon.ico")]
|
||||
|
|
Loading…
Add table
Reference in a new issue