diff --git a/src/main.rs b/src/main.rs index dcaf14a..305e155 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,13 +17,11 @@ use futures::StreamExt; use std::convert::Infallible; use std::time::Duration; -// Импорт для syntect use syntect::easy::HighlightLines; use syntect::highlighting::ThemeSet; use syntect::html::{styled_line_to_highlighted_html, IncludeBackground}; use syntect::parsing::SyntaxSet; -/// Храним тяжелые ресурсы и канал для уведомлений #[derive(Clone)] struct AppState { syntax_set: Arc, @@ -141,6 +139,18 @@ async fn serve_file( return Err(StatusCode::FORBIDDEN); } + let metadata = match fs::metadata(&safe_path).await { + Ok(m) => m, + Err(e) => { + eprintln!("Ошибка получения метаданных: {}", e); + return Err(StatusCode::INTERNAL_SERVER_ERROR); + } + }; + + if metadata.is_dir() { + return render_directory_index(&safe_path, &full_path).await; + } + let content = match fs::read_to_string(&safe_path).await { Ok(c) => c, Err(e) => { @@ -154,6 +164,122 @@ async fn serve_file( Ok(Html(html_content)) } +async fn render_directory_index( + dir_path: &PathBuf, + request_path: &str, +) -> Result, StatusCode> { + let mut entries = match fs::read_dir(dir_path).await { + Ok(list) => list, + Err(e) => { + eprintln!("Ошибка чтения директории: {}", e); + return Err(StatusCode::FORBIDDEN); + } + }; + + let mut files: Vec<(String, String, bool)> = Vec::new(); // (name, link, is_dir) + + 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(); + + // Пропускаем скрытые файлы (начинающиеся с точки) + if file_name.starts_with('.') { + continue; + } + + let is_dir = entry.metadata().await.map(|m| m.is_dir()).unwrap_or(false); + + // Формируем ссылку + let mut link_path = request_path.to_string(); + if !link_path.ends_with('/') { + link_path.push('/'); + } + link_path.push_str(&file_name); + + files.push((file_name, link_path, is_dir)); + } + + // Сортировка: сначала директории, потом файлы, по алфавиту + files.sort_by(|a, b| { + match (a.2, b.2) { + (true, false) => std::cmp::Ordering::Less, // Директории первыми + (false, true) => std::cmp::Ordering::Greater, + _ => a.0.cmp(&b.0), // Затем по имени + } + }); + + let mut list_html = String::from(""); + + let html_content = format!( + r#" + + + + + Обзор директории + + + +
+

📂 Обзор директории: /{}

+ {} +

+ ← На главную +

+
+ +"#, + request_path.trim_start_matches('/'), + list_html + ); + + Ok(Html(html_content)) +} +// <--- КОНЕЦ ИЗМЕНЕНИЯ + /// Запуск наблюдателя за файловой системой async fn run_file_watcher(state: AppState) { let (tx_fs, mut rx_fs) = tokio::sync::mpsc::channel::(100); @@ -308,8 +434,7 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, file_path: &s