add logs
This commit is contained in:
parent
f2dceef1a3
commit
f800d63e02
50
Cargo.lock
generated
50
Cargo.lock
generated
@ -67,6 +67,18 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-compression"
|
||||||
|
version = "0.4.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7d67d43201f4d20c78bcda740c142ca52482d81da80681533d33bf3f0596c8e2"
|
||||||
|
dependencies = [
|
||||||
|
"compression-codecs",
|
||||||
|
"compression-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.89"
|
version = "0.1.89"
|
||||||
@ -234,6 +246,23 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compression-codecs"
|
||||||
|
version = "0.4.37"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7"
|
||||||
|
dependencies = [
|
||||||
|
"compression-core",
|
||||||
|
"flate2",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "compression-core"
|
||||||
|
version = "0.4.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
@ -658,6 +687,7 @@ dependencies = [
|
|||||||
"syntect",
|
"syntect",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
"tower-http",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -1233,6 +1263,26 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-http"
|
||||||
|
version = "0.6.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
|
||||||
|
dependencies = [
|
||||||
|
"async-compression",
|
||||||
|
"bitflags 2.11.0",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower-layer"
|
name = "tower-layer"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
|
|||||||
@ -23,3 +23,4 @@ notify = "6.1"
|
|||||||
tokio-stream = { version = "0.1", features = ["sync"] }
|
tokio-stream = { version = "0.1", features = ["sync"] }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { version = "4.5", features = ["derive"] }
|
||||||
|
tower-http = { version = "0.6.8", features = ["trace", "compression-gzip", "cors"] }
|
||||||
|
|||||||
158
src/main.rs
158
src/main.rs
@ -1,27 +1,29 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
extract::{Path, State},
|
|
||||||
routing::get,
|
|
||||||
Router,
|
Router,
|
||||||
http::{StatusCode, HeaderMap, header},
|
extract::{Path, State},
|
||||||
response::{Html, Sse, IntoResponse},
|
http::{HeaderMap, StatusCode, header},
|
||||||
|
response::{Html, IntoResponse, Sse},
|
||||||
|
routing::get,
|
||||||
};
|
};
|
||||||
use std::net::{SocketAddr, ToSocketAddrs};
|
use futures::StreamExt;
|
||||||
use pulldown_cmark::{Options, html, Event, Tag, CodeBlockKind};
|
use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
|
||||||
use pulldown_cmark;
|
use pulldown_cmark;
|
||||||
|
use pulldown_cmark::{CodeBlockKind, Event, Options, Tag, html};
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::net::{SocketAddr, ToSocketAddrs};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
use tokio_stream::wrappers::BroadcastStream;
|
use tokio_stream::wrappers::BroadcastStream;
|
||||||
use notify::{Config, RecommendedWatcher, RecursiveMode, Watcher, EventKind};
|
|
||||||
use futures::StreamExt;
|
|
||||||
use std::convert::Infallible;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use syntect::easy::HighlightLines;
|
use syntect::easy::HighlightLines;
|
||||||
use syntect::highlighting::ThemeSet;
|
use syntect::highlighting::ThemeSet;
|
||||||
use syntect::html::{styled_line_to_highlighted_html, IncludeBackground};
|
use syntect::html::{IncludeBackground, styled_line_to_highlighted_html};
|
||||||
use syntect::parsing::SyntaxSet;
|
use syntect::parsing::SyntaxSet;
|
||||||
|
use tower_http::trace::TraceLayer;
|
||||||
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::io;
|
use std::io;
|
||||||
@ -30,7 +32,6 @@ use std::io;
|
|||||||
const TEMPLATE_FILE: &str = include_str!("../templates/file.html");
|
const TEMPLATE_FILE: &str = include_str!("../templates/file.html");
|
||||||
const TEMPLATE_DIR: &str = include_str!("../templates/dir.html");
|
const TEMPLATE_DIR: &str = include_str!("../templates/dir.html");
|
||||||
|
|
||||||
|
|
||||||
#[derive(clap::Parser, Debug)]
|
#[derive(clap::Parser, Debug)]
|
||||||
#[command(author, version, about, long_about = None)]
|
#[command(author, version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
@ -47,7 +48,6 @@ struct Args {
|
|||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
syntax_set: Arc<SyntaxSet>,
|
syntax_set: Arc<SyntaxSet>,
|
||||||
@ -65,7 +65,12 @@ async fn main() {
|
|||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::registry()
|
||||||
|
.with(tracing_subscriber::EnvFilter::new(
|
||||||
|
std::env::var("RUST_LOG").unwrap_or_else(|_| "info,tower_http=info".into()),
|
||||||
|
))
|
||||||
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
.init();
|
||||||
|
|
||||||
let ss = SyntaxSet::load_defaults_newlines();
|
let ss = SyntaxSet::load_defaults_newlines();
|
||||||
let ts = ThemeSet::load_defaults();
|
let ts = ThemeSet::load_defaults();
|
||||||
@ -88,7 +93,8 @@ async fn main() {
|
|||||||
.route("/", get(root))
|
.route("/", get(root))
|
||||||
.route("/*path", get(serve_file))
|
.route("/*path", get(serve_file))
|
||||||
.route("/events/*path", get(sse_handler))
|
.route("/events/*path", get(sse_handler))
|
||||||
.with_state(state);
|
.with_state(state)
|
||||||
|
.layer(TraceLayer::new_for_http());
|
||||||
|
|
||||||
let addr = resolve_addr(&args.host, args.port).unwrap();
|
let addr = resolve_addr(&args.host, args.port).unwrap();
|
||||||
|
|
||||||
@ -107,9 +113,7 @@ fn resolve_addr(host: &str, port: u16) -> io::Result<SocketAddr> {
|
|||||||
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Не удалось разрешить адрес"))
|
.ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Не удалось разрешить адрес"))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn root(
|
async fn root(State(state): State<AppState>) -> Result<Html<String>, StatusCode> {
|
||||||
State(state): State<AppState>,
|
|
||||||
) ->Result<Html<String>, StatusCode> {
|
|
||||||
render_directory_index(&PathBuf::from(state.root), "").await
|
render_directory_index(&PathBuf::from(state.root), "").await
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -118,40 +122,47 @@ async fn sse_handler(
|
|||||||
Path(full_path): Path<String>,
|
Path(full_path): Path<String>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let mut headers = HeaderMap::new();
|
let mut headers = HeaderMap::new();
|
||||||
headers.insert(header::CONTENT_TYPE, header::HeaderValue::from_static("text/event-stream"));
|
headers.insert(
|
||||||
headers.insert(header::CACHE_CONTROL, header::HeaderValue::from_static("no-cache"));
|
header::CONTENT_TYPE,
|
||||||
headers.insert(header::CONNECTION, header::HeaderValue::from_static("keep-alive"));
|
header::HeaderValue::from_static("text/event-stream"),
|
||||||
|
);
|
||||||
|
headers.insert(
|
||||||
|
header::CACHE_CONTROL,
|
||||||
|
header::HeaderValue::from_static("no-cache"),
|
||||||
|
);
|
||||||
|
headers.insert(
|
||||||
|
header::CONNECTION,
|
||||||
|
header::HeaderValue::from_static("keep-alive"),
|
||||||
|
);
|
||||||
|
|
||||||
let rx = state.tx.subscribe();
|
let rx = state.tx.subscribe();
|
||||||
let requested_path = full_path.clone();
|
let requested_path = full_path.clone();
|
||||||
|
|
||||||
let stream = BroadcastStream::new(rx)
|
let stream = BroadcastStream::new(rx).filter_map(move |res| {
|
||||||
.filter_map(move |res| {
|
let req_path = requested_path.clone();
|
||||||
let req_path = requested_path.clone();
|
async move {
|
||||||
async move {
|
match res {
|
||||||
match res {
|
Ok(changed_path) => {
|
||||||
Ok(changed_path) => {
|
if changed_path.contains(&req_path) {
|
||||||
if changed_path.contains(&req_path) {
|
Some(Ok::<axum::response::sse::Event, Infallible>(
|
||||||
Some(Ok::<axum::response::sse::Event, Infallible>(
|
axum::response::sse::Event::default()
|
||||||
axum::response::sse::Event::default()
|
.event("reload")
|
||||||
.event("reload")
|
.data(""),
|
||||||
.data("")
|
))
|
||||||
))
|
} else {
|
||||||
} else {
|
None
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(_) => None,
|
|
||||||
}
|
}
|
||||||
|
Err(_) => None,
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let sse = Sse::new(stream)
|
let sse = Sse::new(stream).keep_alive(
|
||||||
.keep_alive(
|
axum::response::sse::KeepAlive::new()
|
||||||
axum::response::sse::KeepAlive::new()
|
.interval(Duration::from_secs(15))
|
||||||
.interval(Duration::from_secs(15))
|
.text("ping"),
|
||||||
.text("ping")
|
);
|
||||||
);
|
|
||||||
|
|
||||||
(headers, sse)
|
(headers, sse)
|
||||||
}
|
}
|
||||||
@ -206,7 +217,11 @@ async fn serve_file(
|
|||||||
// Логика кнопки "Назад"
|
// Логика кнопки "Назад"
|
||||||
let back_link = if let Some(pos) = full_path.rfind('/') {
|
let back_link = if let Some(pos) = full_path.rfind('/') {
|
||||||
let parent = &full_path[..pos];
|
let parent = &full_path[..pos];
|
||||||
if parent.is_empty() { "/".to_string() } else { format!("/{}", parent) }
|
if parent.is_empty() {
|
||||||
|
"/".to_string()
|
||||||
|
} else {
|
||||||
|
format!("/{}", parent)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
"/".to_string()
|
"/".to_string()
|
||||||
};
|
};
|
||||||
@ -246,7 +261,11 @@ async fn render_directory_index(
|
|||||||
|
|
||||||
let mut files: Vec<(String, String, bool)> = Vec::new();
|
let mut files: Vec<(String, String, bool)> = Vec::new();
|
||||||
|
|
||||||
while let Some(entry) = entries.next_entry().await.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? {
|
while let Some(entry) = entries
|
||||||
|
.next_entry()
|
||||||
|
.await
|
||||||
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||||
|
{
|
||||||
let file_name = entry.file_name().to_string_lossy().to_string();
|
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
|
||||||
if file_name.starts_with('.') {
|
if file_name.starts_with('.') {
|
||||||
@ -264,19 +283,21 @@ async fn render_directory_index(
|
|||||||
files.push((file_name, link_path, is_dir));
|
files.push((file_name, link_path, is_dir));
|
||||||
}
|
}
|
||||||
|
|
||||||
files.sort_by(|a, b| {
|
files.sort_by(|a, b| match (a.2, b.2) {
|
||||||
match (a.2, b.2) {
|
(true, false) => std::cmp::Ordering::Less,
|
||||||
(true, false) => std::cmp::Ordering::Less,
|
(false, true) => std::cmp::Ordering::Greater,
|
||||||
(false, true) => std::cmp::Ordering::Greater,
|
_ => a.0.cmp(&b.0),
|
||||||
_ => a.0.cmp(&b.0),
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut list_html = String::from("<ul>");
|
let mut list_html = String::from("<ul>");
|
||||||
|
|
||||||
if !request_path.is_empty() && request_path != "files" {
|
if !request_path.is_empty() && request_path != "files" {
|
||||||
let parent_path = request_path.rsplit_once('/').map(|(p, _)| p).unwrap_or("");
|
let parent_path = request_path.rsplit_once('/').map(|(p, _)| p).unwrap_or("");
|
||||||
let parent_link = if parent_path.is_empty() { "/".to_string() } else { format!("/{}", parent_path) };
|
let parent_link = if parent_path.is_empty() {
|
||||||
|
"/".to_string()
|
||||||
|
} else {
|
||||||
|
format!("/{}", parent_path)
|
||||||
|
};
|
||||||
|
|
||||||
list_html.push_str(&format!(
|
list_html.push_str(&format!(
|
||||||
r#"<li>
|
r#"<li>
|
||||||
@ -302,7 +323,11 @@ async fn render_directory_index(
|
|||||||
}
|
}
|
||||||
list_html.push_str("</ul>");
|
list_html.push_str("</ul>");
|
||||||
|
|
||||||
let title_path = if request_path.is_empty() { "" } else { request_path.trim_start_matches('/') };
|
let title_path = if request_path.is_empty() {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
request_path.trim_start_matches('/')
|
||||||
|
};
|
||||||
|
|
||||||
let final_html = TEMPLATE_DIR
|
let final_html = TEMPLATE_DIR
|
||||||
.replace("{{TITLE_PATH}}", title_path)
|
.replace("{{TITLE_PATH}}", title_path)
|
||||||
@ -326,7 +351,8 @@ async fn run_file_watcher(state: AppState) {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Config::default(),
|
Config::default(),
|
||||||
).expect("Failed to create watcher");
|
)
|
||||||
|
.expect("Failed to create watcher");
|
||||||
|
|
||||||
let watch_path = PathBuf::from("./notes");
|
let watch_path = PathBuf::from("./notes");
|
||||||
if let Err(e) = watcher.watch(&watch_path, RecursiveMode::Recursive) {
|
if let Err(e) = watcher.watch(&watch_path, RecursiveMode::Recursive) {
|
||||||
@ -368,7 +394,7 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &
|
|||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Event::End(Tag::CodeBlock(_)) => {
|
Event::End(Tag::CodeBlock(_)) => {
|
||||||
in_code_block = false;
|
in_code_block = false;
|
||||||
let is_mermaid = current_lang.as_deref() == Some("mermaid");
|
let is_mermaid = current_lang.as_deref() == Some("mermaid");
|
||||||
@ -398,11 +424,16 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &
|
|||||||
let line_with_newline = format!("{}\n", line);
|
let line_with_newline = format!("{}\n", line);
|
||||||
match h.highlight_line(&line_with_newline, ss) {
|
match h.highlight_line(&line_with_newline, ss) {
|
||||||
Ok(regions) => {
|
Ok(regions) => {
|
||||||
let html_line = styled_line_to_highlighted_html(®ions[..], IncludeBackground::No)
|
let html_line = styled_line_to_highlighted_html(
|
||||||
.unwrap_or_else(|_| escape_html(&line_with_newline));
|
®ions[..],
|
||||||
|
IncludeBackground::No,
|
||||||
|
)
|
||||||
|
.unwrap_or_else(|_| escape_html(&line_with_newline));
|
||||||
result_html.push_str(&html_line);
|
result_html.push_str(&html_line);
|
||||||
},
|
}
|
||||||
Err(_) => result_html.push_str(&escape_html(&line_with_newline)),
|
Err(_) => {
|
||||||
|
result_html.push_str(&escape_html(&line_with_newline))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result_html
|
result_html
|
||||||
@ -421,15 +452,14 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &
|
|||||||
</div>
|
</div>
|
||||||
<pre style="margin: 0; border-radius: 0 0 6px 6px;"><code>{}</code></pre>
|
<pre style="margin: 0; border-radius: 0 0 6px 6px;"><code>{}</code></pre>
|
||||||
</div>"#,
|
</div>"#,
|
||||||
lang_escaped,
|
lang_escaped, highlighted_html
|
||||||
highlighted_html
|
|
||||||
);
|
);
|
||||||
processed_events.push(Event::Html(code_container.into()));
|
processed_events.push(Event::Html(code_container.into()));
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
Event::Text(text) if in_code_block => {
|
Event::Text(text) if in_code_block => {
|
||||||
current_code.push_str(&text);
|
current_code.push_str(&text);
|
||||||
},
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if !in_code_block {
|
if !in_code_block {
|
||||||
processed_events.push(event);
|
processed_events.push(event);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user