From 066c95dc077b930e190a28ca46f53270685fb910 Mon Sep 17 00:00:00 2001 From: _ <> Date: Sun, 20 Dec 2020 18:38:39 +0000 Subject: [PATCH] :recycle: refactor: extract html.rs --- crates/ptth_server/src/file_server/html.rs | 196 ++++++++++++++++++++ crates/ptth_server/src/file_server/mod.rs | 174 +---------------- crates/ptth_server/src/file_server/tests.rs | 23 --- crates/ptth_server/src/lib.rs | 1 - 4 files changed, 204 insertions(+), 190 deletions(-) create mode 100644 crates/ptth_server/src/file_server/html.rs diff --git a/crates/ptth_server/src/file_server/html.rs b/crates/ptth_server/src/file_server/html.rs new file mode 100644 index 0000000..498b3d2 --- /dev/null +++ b/crates/ptth_server/src/file_server/html.rs @@ -0,0 +1,196 @@ +use std::borrow::Cow; + +use handlebars::Handlebars; +use serde::Serialize; +use tracing::instrument; + +use super::{ + FileServerError, + Response, + metrics, + pretty_print_bytes, +}; + +#[derive (Serialize)] +struct Dir <'a> { + #[serde (flatten)] + instance_metrics: &'a metrics::Startup, + + path: Cow <'a, str>, + entries: Vec , +} + +#[derive (Serialize)] +struct DirEntry { + icon: &'static str, + trailing_slash: &'static str, + + // Unfortunately file_name will allocate as long as some platforms + // (Windows!) aren't UTF-8. Cause I don't want to write separate code + // for such a small problem. + + file_name: String, + + // This could be a Cow with file_name if no encoding was done but + // it's simpler to allocate. + + encoded_file_name: String, + + size: Cow <'static, str>, + + error: bool, +} + +pub async fn serve_root ( + handlebars: &Handlebars <'static>, + instance_metrics: &metrics::Startup +) -> Result +{ + let s = handlebars.render ("file_server_root", &instance_metrics)?; + + Ok (serve_html (s)) +} + +#[instrument (level = "debug", skip (handlebars, instance_metrics, dir))] +pub async fn serve_dir ( + handlebars: &Handlebars <'static>, + instance_metrics: &metrics::Startup, + path: Cow <'_, str>, + mut dir: tokio::fs::ReadDir +) -> Result +{ + let mut entries = vec! []; + + while let Ok (Some (entry)) = dir.next_entry ().await { + entries.push (read_dir_entry (entry).await); + } + + entries.sort_unstable_by (|a, b| a.file_name.cmp (&b.file_name)); + + let s = handlebars.render ("file_server_dir", &Dir { + path, + entries, + instance_metrics, + })?; + + Ok (serve_html (s)) +} + +async fn read_dir_entry (entry: tokio::fs::DirEntry) -> DirEntry +{ + use percent_encoding::{ + CONTROLS, + utf8_percent_encode, + }; + + let file_name = match entry.file_name ().into_string () { + Ok (x) => x, + Err (_) => return DirEntry { + icon: emoji::ERROR, + trailing_slash: "", + file_name: "File / directory name is not UTF-8".into (), + encoded_file_name: "".into (), + size: "".into (), + error: true, + }, + }; + + let metadata = match entry.metadata ().await { + Ok (x) => x, + Err (_) => return DirEntry { + icon: emoji::ERROR, + trailing_slash: "", + file_name: "Could not fetch metadata".into (), + encoded_file_name: "".into (), + size: "".into (), + error: true, + }, + }; + + let (trailing_slash, icon, size) = { + let t = metadata.file_type (); + + if t.is_dir () { + ("/", emoji::FOLDER, "".into ()) + } + else { + ("", get_icon (&file_name), pretty_print_bytes (metadata.len ()).into ()) + } + }; + + let encoded_file_name = utf8_percent_encode (&file_name, CONTROLS).to_string (); + + DirEntry { + icon, + trailing_slash: &trailing_slash, + file_name, + encoded_file_name, + size, + error: false, + } +} + +pub fn serve_html (s: String) -> Response { + let mut resp = Response::default (); + resp + .header ("content-type".to_string (), "text/html; charset=UTF-8".to_string ().into_bytes ()) + .body_bytes (s.into_bytes ()) + ; + resp +} + +fn get_icon (file_name: &str) -> &'static str { + if + file_name.ends_with (".mp4") || + file_name.ends_with (".avi") || + file_name.ends_with (".mkv") || + file_name.ends_with (".webm") + { + emoji::VIDEO + } + else if + file_name.ends_with (".jpg") || + file_name.ends_with (".jpeg") || + file_name.ends_with (".png") || + file_name.ends_with (".bmp") + { + emoji::PICTURE + } + else { + emoji::FILE + } +} + +mod emoji { + pub const VIDEO: &str = "\u{1f39e}\u{fe0f}"; + pub const PICTURE: &str = "\u{1f4f7}"; + pub const FILE: &str = "\u{1f4c4}"; + pub const FOLDER: &str = "\u{1f4c1}"; + pub const ERROR: &str = "\u{26a0}\u{fe0f}"; +} + +#[cfg (test)] +mod tests { + #[test] + fn icons () { + let video = "🎞️"; + let picture = "📷"; + let file = "📄"; + + for (input, expected) in vec! [ + ("copying_is_not_theft.mp4", video), + ("copying_is_not_theft.avi", video), + ("copying_is_not_theft.mkv", video), + ("copying_is_not_theft.webm", video), + ("lolcats.jpg", picture), + ("lolcats.jpeg", picture), + ("lolcats.png", picture), + ("lolcats.bmp", picture), + ("ptth.log", file), + ("README.md", file), + ("todo.txt", file), + ].into_iter () { + assert_eq! (super::get_icon (input), expected); + } + } +} diff --git a/crates/ptth_server/src/file_server/mod.rs b/crates/ptth_server/src/file_server/mod.rs index d98d395..9c0afbc 100644 --- a/crates/ptth_server/src/file_server/mod.rs +++ b/crates/ptth_server/src/file_server/mod.rs @@ -4,7 +4,6 @@ #![allow (clippy::enum_glob_use)] use std::{ - borrow::Cow, cmp::min, collections::HashMap, convert::{Infallible, TryFrom}, @@ -42,20 +41,13 @@ use ptth_core::{ pub mod errors; pub mod metrics; +mod html; mod internal; mod markdown; mod range; use errors::FileServerError; -mod emoji { - pub const VIDEO: &str = "\u{1f39e}\u{fe0f}"; - pub const PICTURE: &str = "\u{1f4f7}"; - pub const FILE: &str = "\u{1f4c4}"; - pub const FOLDER: &str = "\u{1f4c1}"; - pub const ERROR: &str = "\u{26a0}\u{fe0f}"; -} - #[derive (Default)] pub struct Config { pub file_server_root: Option , @@ -68,122 +60,16 @@ pub struct State { pub hidden_path: Option , } -#[derive (Serialize)] -struct DirEntryJson { - name: String, - size: u64, - is_dir: bool, -} - #[derive (Serialize)] struct DirJson { entries: Vec , } #[derive (Serialize)] -struct DirEntryHtml { - icon: &'static str, - trailing_slash: &'static str, - - // Unfortunately file_name will allocate as long as some platforms - // (Windows!) aren't UTF-8. Cause I don't want to write separate code - // for such a small problem. - - file_name: String, - - // This could be a Cow with file_name if no encoding was done but - // it's simpler to allocate. - - encoded_file_name: String, - - size: Cow <'static, str>, - - error: bool, -} - -#[derive (Serialize)] -struct DirHtml <'a> { - #[serde (flatten)] - instance_metrics: &'a metrics::Startup, - - path: Cow <'a, str>, - entries: Vec , -} - -fn get_icon (file_name: &str) -> &'static str { - if - file_name.ends_with (".mp4") || - file_name.ends_with (".avi") || - file_name.ends_with (".mkv") || - file_name.ends_with (".webm") - { - emoji::VIDEO - } - else if - file_name.ends_with (".jpg") || - file_name.ends_with (".jpeg") || - file_name.ends_with (".png") || - file_name.ends_with (".bmp") - { - emoji::PICTURE - } - else { - emoji::FILE - } -} - -async fn read_dir_entry_html (entry: DirEntry) -> DirEntryHtml -{ - use percent_encoding::{ - CONTROLS, - utf8_percent_encode, - }; - - let file_name = match entry.file_name ().into_string () { - Ok (x) => x, - Err (_) => return DirEntryHtml { - icon: emoji::ERROR, - trailing_slash: "", - file_name: "File / directory name is not UTF-8".into (), - encoded_file_name: "".into (), - size: "".into (), - error: true, - }, - }; - - let metadata = match entry.metadata ().await { - Ok (x) => x, - Err (_) => return DirEntryHtml { - icon: emoji::ERROR, - trailing_slash: "", - file_name: "Could not fetch metadata".into (), - encoded_file_name: "".into (), - size: "".into (), - error: true, - }, - }; - - let (trailing_slash, icon, size) = { - let t = metadata.file_type (); - - if t.is_dir () { - ("/", emoji::FOLDER, "".into ()) - } - else { - ("", get_icon (&file_name), pretty_print_bytes (metadata.len ()).into ()) - } - }; - - let encoded_file_name = utf8_percent_encode (&file_name, CONTROLS).to_string (); - - DirEntryHtml { - icon, - trailing_slash: &trailing_slash, - file_name, - encoded_file_name, - size, - error: false, - } +struct DirEntryJson { + name: String, + size: u64, + is_dir: bool, } async fn read_dir_entry_json (entry: DirEntry) -> Option @@ -200,25 +86,6 @@ async fn read_dir_entry_json (entry: DirEntry) -> Option }) } -async fn serve_root ( - handlebars: &Handlebars <'static>, - instance_metrics: &metrics::Startup -) -> Result -{ - let s = handlebars.render ("file_server_root", &instance_metrics)?; - - Ok (serve_html (s)) -} - -fn serve_html (s: String) -> Response { - let mut resp = Response::default (); - resp - .header ("content-type".to_string (), "text/html; charset=UTF-8".to_string ().into_bytes ()) - .body_bytes (s.into_bytes ()) - ; - resp -} - async fn serve_dir_json ( mut dir: ReadDir ) -> Result @@ -244,31 +111,6 @@ async fn serve_dir_json ( Ok (response) } -#[instrument (level = "debug", skip (handlebars, instance_metrics, dir))] -async fn serve_dir_html ( - handlebars: &Handlebars <'static>, - instance_metrics: &metrics::Startup, - path: Cow <'_, str>, - mut dir: ReadDir -) -> Result -{ - let mut entries = vec! []; - - while let Ok (Some (entry)) = dir.next_entry ().await { - entries.push (read_dir_entry_html (entry).await); - } - - entries.sort_unstable_by (|a, b| a.file_name.cmp (&b.file_name)); - - let s = handlebars.render ("file_server_dir", &DirHtml { - path, - entries, - instance_metrics, - })?; - - Ok (serve_html (s)) -} - #[instrument (level = "debug", skip (f))] async fn serve_file ( mut f: File, @@ -419,14 +261,14 @@ pub async fn serve_all ( }, InvalidQuery => serve_error (StatusCode::BadRequest, "Query is invalid for this object\n"), - Root => serve_root (handlebars, instance_metrics).await?, + Root => html::serve_root (handlebars, instance_metrics).await?, ServeDir (internal::ServeDirParams { path, dir, format }) => match format { OutputFormat::Json => serve_dir_json (dir.into_inner ()).await?, - OutputFormat::Html => serve_dir_html (handlebars, instance_metrics, path.to_string_lossy (), dir.into_inner ()).await?, + OutputFormat::Html => html::serve_dir (handlebars, instance_metrics, path.to_string_lossy (), dir.into_inner ()).await?, }, ServeFile (internal::ServeFileParams { file, @@ -443,7 +285,7 @@ pub async fn serve_all ( serve_error (code, e.to_string ()) }, - MarkdownPreview (s) => serve_html (s), + MarkdownPreview (s) => html::serve_html (s), }) } diff --git a/crates/ptth_server/src/file_server/tests.rs b/crates/ptth_server/src/file_server/tests.rs index 9f096be..c616a29 100644 --- a/crates/ptth_server/src/file_server/tests.rs +++ b/crates/ptth_server/src/file_server/tests.rs @@ -9,29 +9,6 @@ use std::{ use maplit::*; use tokio::runtime::Runtime; -#[test] -fn icons () { - let video = "🎞️"; - let picture = "📷"; - let file = "📄"; - - for (input, expected) in vec! [ - ("copying_is_not_theft.mp4", video), - ("copying_is_not_theft.avi", video), - ("copying_is_not_theft.mkv", video), - ("copying_is_not_theft.webm", video), - ("lolcats.jpg", picture), - ("lolcats.jpeg", picture), - ("lolcats.png", picture), - ("lolcats.bmp", picture), - ("ptth.log", file), - ("README.md", file), - ("todo.txt", file), - ].into_iter () { - assert_eq! (super::get_icon (input), expected); - } -} - #[test] fn pretty_print_bytes () { for (input_after, expected_before, expected_after) in vec! [ diff --git a/crates/ptth_server/src/lib.rs b/crates/ptth_server/src/lib.rs index 1152d14..809edb8 100644 --- a/crates/ptth_server/src/lib.rs +++ b/crates/ptth_server/src/lib.rs @@ -14,7 +14,6 @@ use std::{ }; use futures::FutureExt; -use handlebars::Handlebars; use reqwest::Client; use serde::Deserialize; use tokio::{