mirror of
https://github.com/DefectingCat/candy
synced 2025-07-15 08:41:35 +00:00
feat(serve): implement list directory
This commit is contained in:
100
Cargo.lock
generated
100
Cargo.lock
generated
@ -41,6 +41,21 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anstream"
|
||||
version = "0.6.18"
|
||||
@ -378,6 +393,7 @@ dependencies = [
|
||||
"axum-extra",
|
||||
"axum-server",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"clap",
|
||||
"const_format",
|
||||
"dashmap",
|
||||
@ -431,6 +447,20 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clang-sys"
|
||||
version = "1.8.1"
|
||||
@ -1019,6 +1049,30 @@ dependencies = [
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.63"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.0.0"
|
||||
@ -2519,6 +2573,41 @@ version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.61.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings 0.4.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.1"
|
||||
@ -2532,7 +2621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-strings 0.3.1",
|
||||
"windows-targets 0.53.0",
|
||||
]
|
||||
|
||||
@ -2554,6 +2643,15 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
@ -48,6 +48,7 @@ clap = { version = "4.5.39", features = ["derive"] }
|
||||
serde_repr = "0.1.20"
|
||||
serde_json = "1.0.140"
|
||||
mime_guess = "2.0.5"
|
||||
chrono = "0.4.41"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.20.0"
|
||||
|
@ -1,8 +1,4 @@
|
||||
use std::{
|
||||
path::PathBuf,
|
||||
str::FromStr,
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
use std::{path::PathBuf, str::FromStr, time::UNIX_EPOCH};
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use axum::{
|
||||
@ -13,7 +9,7 @@ use axum::{
|
||||
use axum_extra::extract::Host;
|
||||
use dashmap::mapref::one::Ref;
|
||||
use http::{
|
||||
HeaderValue, StatusCode, Uri,
|
||||
HeaderMap, HeaderValue, StatusCode, Uri,
|
||||
header::{CONTENT_TYPE, ETAG, IF_NONE_MATCH},
|
||||
};
|
||||
use mime_guess::from_path;
|
||||
@ -162,8 +158,10 @@ pub async fn serve(
|
||||
if host_route.auto_index {
|
||||
let root_path = PathBuf::from(root);
|
||||
let list = list_dir(&root_path).await?;
|
||||
debug!("list: {:?}", list);
|
||||
todo!()
|
||||
let list_html = render_list_html(list);
|
||||
let mut headers = HeaderMap::new();
|
||||
headers.insert(CONTENT_TYPE, HeaderValue::from_static("text/html"));
|
||||
return Ok((headers, list_html).into_response());
|
||||
} else {
|
||||
debug!("No valid file found in path candidates");
|
||||
return custom_not_found!(host_route, request, false).await;
|
||||
@ -216,7 +214,7 @@ async fn stream_file(
|
||||
path: PathBuf,
|
||||
request: Request,
|
||||
status: Option<StatusCode>,
|
||||
) -> RouteResult<impl IntoResponse> {
|
||||
) -> RouteResult<Response<Body>> {
|
||||
let file = File::open(path.clone())
|
||||
.await
|
||||
.with_context(|| "open file failed")?;
|
||||
@ -233,8 +231,6 @@ async fn stream_file(
|
||||
.with_context(|| "parse if-none-match failed")?
|
||||
== etag
|
||||
{
|
||||
// let empty_stream = stream::empty::<u8>();
|
||||
// let body = Some(StreamBody::new(empty_stream));
|
||||
response = response.status(StatusCode::NOT_MODIFIED);
|
||||
not_modified = true;
|
||||
};
|
||||
@ -332,16 +328,107 @@ pub fn resolve_parent_path(uri: &Uri, path: Option<&Path<String>>) -> String {
|
||||
}
|
||||
}
|
||||
|
||||
fn render_list_html(list: Vec<DirList>) -> String {
|
||||
let body_rows = list
|
||||
.iter()
|
||||
.map(|dist| {
|
||||
format!(
|
||||
r#"<tr><td><a href="{}">{}</a></td><td>{}</td><td>{}</td></tr>"#,
|
||||
dist.path.display(),
|
||||
dist.name,
|
||||
dist.size,
|
||||
dist.last_modified,
|
||||
// dist.is_dir
|
||||
)
|
||||
})
|
||||
.collect::<Vec<String>>()
|
||||
.join("");
|
||||
|
||||
let list_html = format!(
|
||||
r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Index of /</title>
|
||||
<style>
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 20px;
|
||||
background-color: #ffffff;
|
||||
color: #000000;
|
||||
}}
|
||||
h1 {{
|
||||
font-size: 1.5em;
|
||||
margin-bottom: 20px;
|
||||
text-align: left;
|
||||
}}
|
||||
table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #dddddd;
|
||||
}}
|
||||
th, td {{
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #dddddd;
|
||||
}}
|
||||
th {{
|
||||
background-color: #f0f0f0;
|
||||
font-weight: bold;
|
||||
}}
|
||||
tr:nth-child(even) {{
|
||||
background-color: #f9f9f9;
|
||||
}}
|
||||
tr:hover {{
|
||||
background-color: #f0f0f0;
|
||||
}}
|
||||
.dir {{
|
||||
color: #0066cc;
|
||||
font-weight: bold;
|
||||
}}
|
||||
.file {{
|
||||
color: #000000;
|
||||
}}
|
||||
a {{
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}}
|
||||
a:hover {{
|
||||
text-decoration: underline;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Index of /</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Last Modified</th>
|
||||
<th>Size</th>
|
||||
</tr>
|
||||
<tbody id="directory-content">
|
||||
{body_rows}
|
||||
</tbody>
|
||||
</table>
|
||||
</body>
|
||||
</html>
|
||||
"#
|
||||
);
|
||||
list_html
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct DirList {
|
||||
pub name: String,
|
||||
pub path: PathBuf,
|
||||
pub is_dir: bool,
|
||||
pub size: u64,
|
||||
pub last_modified: SystemTime,
|
||||
pub last_modified: String,
|
||||
}
|
||||
|
||||
async fn list_dir(path: &PathBuf) -> anyhow::Result<Vec<DirList>> {
|
||||
use chrono::{Local, TimeZone};
|
||||
|
||||
let mut list = vec![];
|
||||
let mut entries = fs::read_dir(path)
|
||||
.await
|
||||
@ -358,9 +445,23 @@ async fn list_dir(path: &PathBuf) -> anyhow::Result<Vec<DirList>> {
|
||||
.metadata()
|
||||
.await
|
||||
.with_context(|| "get file metadata failed")?;
|
||||
// convert last modified to string
|
||||
let last_modified = metadata
|
||||
.modified()
|
||||
.with_context(|| "get file modified failed")?;
|
||||
let last_modified = last_modified
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.with_context(|| "calculate unix timestamp failed")?;
|
||||
let datetime = match Local
|
||||
.timestamp_opt(last_modified.as_secs() as i64, last_modified.subsec_nanos())
|
||||
{
|
||||
chrono::LocalResult::Ambiguous(earlier, later) => {
|
||||
tracing::warn!("发现歧义时间: {} 和 {}", earlier, later);
|
||||
earlier // 选择较早的时间
|
||||
}
|
||||
_ => Local::now(),
|
||||
};
|
||||
let last_modified = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
let size = metadata.len();
|
||||
let is_dir = metadata.is_dir();
|
||||
let name = entry.file_name().to_string_lossy().to_string();
|
||||
|
Reference in New Issue
Block a user