From 1b275061498b29e1cfedcb69046c52d056743e4f Mon Sep 17 00:00:00 2001 From: Avii Date: Tue, 28 Jan 2025 13:24:27 +0100 Subject: [PATCH] Split some shit to files to satisfy Veldmuizie --- src/index.rs | 163 ++++++++++++++++++++ src/index/filters.rs | 119 +++++++++++++++ src/lua.rs | 59 ++++++++ src/main.rs | 354 +------------------------------------------ 4 files changed, 346 insertions(+), 349 deletions(-) create mode 100644 src/index.rs create mode 100644 src/index/filters.rs create mode 100644 src/lua.rs diff --git a/src/index.rs b/src/index.rs new file mode 100644 index 0000000..13a0729 --- /dev/null +++ b/src/index.rs @@ -0,0 +1,163 @@ +mod filters; + +use std::{path::PathBuf, str::FromStr, sync::LazyLock}; + +use filters::*; +use tera::{Context, Tera}; +use time::OffsetDateTime; +use tokio::time::Instant; +use tokio_stream::{wrappers::ReadDirStream, StreamExt}; + +use crate::{icon::extract_icon, AResult, FileInfo, BASE_DIR}; + +pub static TERA: LazyLock = LazyLock::new(|| { + let mut tera = Tera::default(); + tera.add_raw_template( + "index.html.jinja", + include_str!("../templates/index.html.jinja"), + ) + .unwrap(); + tera.register_filter("iconize", iconize); + tera.register_filter("from_ico", from_ico); + tera.register_filter("size", size); + tera.register_filter("time", time); + tera.register_filter("md", md); + + tera +}); + +pub async fn render_index(path: &PathBuf, time: Instant) -> AResult { + let time2 = Instant::now(); + let base = BASE_DIR.display().to_string(); + let dirname = path + .display() + .to_string() + .replace(&base, "") + .replace('\\', "/"); + + let filename = path.file_name().unwrap().to_string_lossy().to_string(); + let description = PathBuf::from_str(&format!( + "{}/.{}.info", + path.parent().unwrap().to_string_lossy(), + filename + ))?; + let description = if description.exists() { + Some(std::fs::read_to_string(description)?) + } else { + None + }; + + let directories = get_directories(path).await?; + let files = get_files(path).await?; + + let tera = &*TERA; + + let mut context = Context::new(); + context.insert("description", &description); + context.insert("dirname", &dirname); + context.insert("directories", &directories); + context.insert("files", &files); + + let loading = time2.elapsed() + time.elapsed(); + context.insert("loading", &loading.as_millis()); + Ok(tera.render("index.html.jinja", &context)?) +} + +async fn get_directories(path: &PathBuf) -> AResult> { + let mut contents = ReadDirStream::new(tokio::fs::read_dir(&path).await?); + + let mut files = vec![]; + + while let Some(file) = contents.next().await { + let file = file?; + let metadata = file.metadata().await?; + if !metadata.is_dir() { + continue; + } + let filename = file.file_name().to_string_lossy().to_string(); + if filename.starts_with('.') { + continue; + } + + // let created: OffsetDateTime = metadata.created()?.into(); + let modified: OffsetDateTime = metadata.modified()?.into(); + + let description = PathBuf::from_str(&format!( + "{}/.{}.info", + file.path().parent().unwrap().to_string_lossy(), + filename + ))?; + + let description = if description.exists() { + Some(std::fs::read_to_string(description)?) + } else { + None + }; + + files.push(FileInfo { + filename, + format: "DIR".to_string(), + size: 0, + icon: None, + // created, + modified, + description, + }); + } + + Ok(files) +} + +async fn get_files(path: &PathBuf) -> AResult> { + let mut contents = ReadDirStream::new(tokio::fs::read_dir(&path).await?); + + let mut files = vec![]; + + while let Some(file) = contents.next().await { + let file = file?; + let metadata = file.metadata().await?; + if metadata.is_dir() { + continue; + } + let filename = file.file_name().to_string_lossy().to_string(); + if filename.starts_with('.') { + continue; + } + let format = file_format::FileFormat::from_file(file.path())?; + let format = format.media_type(); + + let icon = if format == "application/x-dosexec" { + // .. + extract_icon(&file.path()).ok().flatten() + } else { + None + }; + + // let created: OffsetDateTime = metadata.created()?.into(); + let modified: OffsetDateTime = metadata.modified()?.into(); + + let description = PathBuf::from_str(&format!( + "{}/.{}.info", + file.path().parent().unwrap().to_string_lossy(), + filename + ))?; + + let description = if description.exists() { + Some(std::fs::read_to_string(description)?) + } else { + None + }; + + files.push(FileInfo { + filename, + format: format.to_string(), + size: metadata.len() as usize, + icon, + // created, + modified, + description, + }); + } + + Ok(files) +} diff --git a/src/index/filters.rs b/src/index/filters.rs new file mode 100644 index 0000000..c12215f --- /dev/null +++ b/src/index/filters.rs @@ -0,0 +1,119 @@ +use std::{collections::HashMap, sync::LazyLock}; + +use base64::{prelude::BASE64_STANDARD, Engine}; +use tera::{to_value, Result as TeraResult, Value}; +use time::OffsetDateTime; + +pub static ICONS: LazyLock> = LazyLock::new(|| { + let mut map = HashMap::new(); + map.insert( + "unknown", + BASE64_STANDARD.encode(include_bytes!("../../icons/unknown.png")), + ); + map.insert( + "back", + BASE64_STANDARD.encode(include_bytes!("../../icons/back.png")), + ); + map.insert( + "dir", + BASE64_STANDARD.encode(include_bytes!("../../icons/dir.png")), + ); + map.insert( + "binary", + BASE64_STANDARD.encode(include_bytes!("../../icons/binary.png")), + ); + map.insert( + "image", + BASE64_STANDARD.encode(include_bytes!("../../icons/image2.png")), + ); + map.insert( + "compressed", + BASE64_STANDARD.encode(include_bytes!("../../icons/compressed.png")), + ); + + map +}); + +pub fn iconize(value: &Value, _args: &HashMap) -> TeraResult { + let icon = value.as_str().unwrap_or("unknown"); + + let icon = match icon { + "DIR" | "BACK" => "dir", + "image/png" | "image/jpg" | "image/webp" => "image", + "application/zip" => "compressed", + "application/octet-stream" => "binary", + _ => "unknown", + }; + + let icons = &*ICONS; + let icon = if icons.contains_key(icon) { + &icons[icon] + } else { + println!("Unknown filetype: {}", icon); + &icons["unknown"] + }; + + let str = format!("\"\"", icon); + + Ok(to_value(str).unwrap()) +} + +pub fn from_ico(value: &Value, _args: &HashMap) -> TeraResult { + let Some(icon) = value.as_array() else { + return iconize( + &Value::String(String::from("")), + &std::default::Default::default(), + ); + }; + + let icon = icon + .iter() + .map(|n| n.as_u64().unwrap() as u8) + .collect::>(); + + let data = BASE64_STANDARD.encode(icon); + + let str = format!("\"\"/", data); + + Ok(to_value(str).unwrap()) +} + +pub fn size(value: &Value, _args: &HashMap) -> TeraResult { + let Some(value) = value.as_f64() else { + return Ok(value.clone()); + }; + + Ok(to_value(human_bytes::human_bytes(value)).unwrap()) +} + +pub fn time(value: &Value, _args: &HashMap) -> TeraResult { + let time_format: std::vec::Vec> = + time::format_description::parse("[day]-[month]-[year] [hour]:[minute]:[second]").unwrap(); + + let Some(time) = value.as_array() else { + return Ok(value.clone()); + }; + + let time: Vec = time.iter().map(|v| v.as_i64().unwrap()).collect(); + + let new_time: OffsetDateTime = OffsetDateTime::now_utc(); + let new_time = new_time.replace_year(time[0] as i32).unwrap(); + let new_time = new_time.replace_ordinal(time[1] as u16).unwrap(); + let new_time = new_time.replace_hour(time[2] as u8).unwrap(); + let new_time = new_time.replace_minute(time[3] as u8).unwrap(); + let new_time = new_time.replace_second(time[4] as u8).unwrap(); + let new_time = new_time.replace_nanosecond(time[5] as u32).unwrap(); + + Ok(to_value(new_time.format(&time_format).unwrap()).unwrap()) +} + +pub fn md(value: &Value, _args: &HashMap) -> TeraResult { + // parse value as markdown and return html + if let Some(value) = value.as_str() { + if let Ok(md) = markdown::to_html_with_options(value, &markdown::Options::gfm()) { + return Ok(to_value(md).unwrap()); + } + } + + Ok(to_value(value).unwrap()) +} diff --git a/src/lua.rs b/src/lua.rs new file mode 100644 index 0000000..9fdcdb0 --- /dev/null +++ b/src/lua.rs @@ -0,0 +1,59 @@ +use std::sync::LazyLock; + +use mlua::Lua; + +use crate::AResult; + +pub static LUA_CPATH: LazyLock = LazyLock::new(|| { + String::from_utf8_lossy( + &std::process::Command::new("luarocks") + .arg("path") + .arg("--lr-cpath") + .arg("--full") + .output() + .map_err::, _>(|_| vec![]) + .map(|r| r.stdout) + .unwrap_or_default(), + ) + .trim() + .to_string() +}); + +pub static LUA_PATH: LazyLock = LazyLock::new(|| { + String::from_utf8_lossy( + &std::process::Command::new("luarocks") + .arg("path") + .arg("--lr-path") + .arg("--full") + .output() + .map_err::, _>(|_| vec![]) + .map(|r| r.stdout) + .unwrap_or_default(), + ) + .trim() + .to_string() +}); + +pub struct LuaHandler { + lua: Lua, +} + +impl LuaHandler { + pub fn new() -> AResult { + let lua = unsafe { Lua::unsafe_new() }; + + let this = Self { lua }; + + this.exec(&format!( + "package.path = \"{}\"\npackage.cpath = \"{}\"", + &*LUA_PATH, &*LUA_CPATH + ))?; + + Ok(this) + } + + pub fn exec(&self, blob: &str) -> AResult<()> { + self.lua.load(blob).exec()?; + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 60ff22c..597cdc9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ mod icon; +mod index; +// mod lua; use std::{collections::HashMap, fs, io::ErrorKind, path::PathBuf, str::FromStr, sync::LazyLock}; -use icon::extract_icon; use mlua::{ Lua, Number, Table, Value::{self as LuaValue}, @@ -15,12 +16,9 @@ use axum::{ response::{Html, IntoResponse}, routing::get, }; -use base64::prelude::*; use serde::Serialize; -use tera::{to_value, Context, Result as TeraResult, Tera, Value}; use time::OffsetDateTime; use tokio::time::Instant; -use tokio_stream::{wrappers::ReadDirStream, StreamExt}; use tower_http::services::ServeDir; pub type AError = Box; @@ -28,165 +26,6 @@ pub type AResult = Result; pub static CWD: LazyLock = LazyLock::new(|| std::env::current_dir().unwrap()); -// use reqwest::Method; -// #[derive(Debug, Clone)] -// struct ArrayBuffer(Vec); - -// impl mlua::FromLua for ArrayBuffer { -// fn from_lua(value: LuaValue, _: &Lua) -> mlua::Result { -// if let Some(data) = value.as_str() { -// return Ok(Self(data.as_bytes().to_owned())); -// } - -// if let Some(data) = value.as_table() { -// let mut out = vec![]; -// for pair in data.pairs::() { -// let (_, v) = pair?; -// if let Some(value) = v.as_integer() { -// out.push(value as u8); -// } -// } -// return Ok(Self(out)); -// } - -// Err(mlua::Error::FromLuaConversionError { -// from: value.type_name(), -// to: "ArrayBuffer".to_string(), -// message: None, -// }) -// } -// } - -pub static TERA: LazyLock = LazyLock::new(|| { - let mut tera = Tera::default(); - tera.add_raw_template( - "index.html.jinja", - include_str!("../templates/index.html.jinja"), - ) - .unwrap(); - tera.register_filter("iconize", iconize); - tera.register_filter("from_ico", from_ico); - tera.register_filter("size", size); - tera.register_filter("time", time); - tera.register_filter("md", md); - - tera -}); - -pub static ICONS: LazyLock> = LazyLock::new(|| { - let mut map = HashMap::new(); - map.insert( - "unknown", - BASE64_STANDARD.encode(include_bytes!("../icons/unknown.png")), - ); - map.insert( - "back", - BASE64_STANDARD.encode(include_bytes!("../icons/back.png")), - ); - map.insert( - "dir", - BASE64_STANDARD.encode(include_bytes!("../icons/dir.png")), - ); - map.insert( - "binary", - BASE64_STANDARD.encode(include_bytes!("../icons/binary.png")), - ); - map.insert( - "image", - BASE64_STANDARD.encode(include_bytes!("../icons/image2.png")), - ); - map.insert( - "compressed", - BASE64_STANDARD.encode(include_bytes!("../icons/compressed.png")), - ); - - map -}); - -fn from_ico(value: &Value, _args: &HashMap) -> TeraResult { - let Some(icon) = value.as_array() else { - return iconize( - &Value::String(String::from("")), - &std::default::Default::default(), - ); - }; - - let icon = icon - .iter() - .map(|n| n.as_u64().unwrap() as u8) - .collect::>(); - - let data = BASE64_STANDARD.encode(icon); - - let str = format!("\"\"/", data); - - Ok(to_value(str).unwrap()) -} - -fn iconize(value: &Value, _args: &HashMap) -> TeraResult { - let icon = value.as_str().unwrap_or("unknown"); - - let icon = match icon { - "DIR" | "BACK" => "dir", - "image/png" | "image/jpg" | "image/webp" => "image", - "application/zip" => "compressed", - "application/octet-stream" => "binary", - _ => "unknown", - }; - - let icons = &*ICONS; - let icon = if icons.contains_key(icon) { - &icons[icon] - } else { - println!("Unknown filetype: {}", icon); - &icons["unknown"] - }; - - let str = format!("\"\"", icon); - - Ok(to_value(str).unwrap()) -} - -fn size(value: &Value, _args: &HashMap) -> TeraResult { - let Some(value) = value.as_f64() else { - return Ok(value.clone()); - }; - - Ok(to_value(human_bytes::human_bytes(value)).unwrap()) -} - -fn time(value: &Value, _args: &HashMap) -> TeraResult { - let time_format: std::vec::Vec> = - time::format_description::parse("[day]-[month]-[year] [hour]:[minute]:[second]").unwrap(); - - let Some(time) = value.as_array() else { - return Ok(value.clone()); - }; - - let time: Vec = time.iter().map(|v| v.as_i64().unwrap()).collect(); - - let new_time: OffsetDateTime = OffsetDateTime::now_utc(); - let new_time = new_time.replace_year(time[0] as i32).unwrap(); - let new_time = new_time.replace_ordinal(time[1] as u16).unwrap(); - let new_time = new_time.replace_hour(time[2] as u8).unwrap(); - let new_time = new_time.replace_minute(time[3] as u8).unwrap(); - let new_time = new_time.replace_second(time[4] as u8).unwrap(); - let new_time = new_time.replace_nanosecond(time[5] as u32).unwrap(); - - Ok(to_value(new_time.format(&time_format).unwrap()).unwrap()) -} - -fn md(value: &Value, _args: &HashMap) -> TeraResult { - // parse value as markdown and return html - if let Some(value) = value.as_str() { - if let Ok(md) = markdown::to_html_with_options(value, &markdown::Options::gfm()) { - return Ok(to_value(md).unwrap()); - } - } - - Ok(to_value(value).unwrap()) -} - pub static LUA_CPATH: LazyLock = LazyLock::new(|| { String::from_utf8_lossy( &std::process::Command::new("luarocks") @@ -227,53 +66,10 @@ struct FileInfo { size: usize, format: String, icon: Option>, - // created: OffsetDateTime, modified: OffsetDateTime, description: Option, } -// #[derive(Debug, Clone)] -// struct FetchOptions { -// method: String, -// headers: HashMap, -// body: Option, -// } - -// impl Default for FetchOptions { -// fn default() -> Self { -// Self { -// method: "GET".to_string(), -// headers: HashMap::new(), -// body: None, -// } -// } -// } - -// impl mlua::FromLua for FetchOptions { -// fn from_lua(value: LuaValue, _lua: &Lua) -> mlua::Result { -// let Some(options) = value.as_table() else { -// return Err(mlua::Error::FromLuaConversionError { -// from: value.type_name(), -// to: "FetchOptions".to_string(), -// message: Some("options must be a table".to_string()), -// }); -// }; - -// let mut result = FetchOptions::default(); -// if let Ok(method) = options.get::("method") { -// result.method = method; -// } -// if let Ok(headers) = options.get::>("headers") { -// result.headers = headers; -// } -// if let Ok(body) = options.get::>("body") { -// result.body = body; -// } - -// Ok(result) -// } -// } - #[tokio::main] async fn main() -> AResult<()> { let listener = tokio::net::TcpListener::bind("[::]:3000").await.unwrap(); @@ -283,12 +79,9 @@ async fn main() -> AResult<()> { async fn file_handler(request: Request) -> impl IntoResponse { std::env::set_current_dir(&*CWD).unwrap(); - // let base_dir: &PathBuf = BASE_DIR.get_or_init(|| { - // PathBuf::from_str(&std::env::var("BASE_DIR").unwrap_or(String::from("./public"))).unwrap() - // }); let base_dir = BASE_DIR.canonicalize().map_err(|e| { - dbg!(e); + eprintln!("{:#?}", e); (StatusCode::NOT_FOUND, "404: Not Found".to_string()) })?; @@ -296,8 +89,7 @@ async fn file_handler(request: Request) -> impl IntoResponse { if request_uri_path.ends_with("/") { let mut a = base_dir.clone(); - a.push(".".to_owned() + &request_uri_path); - a.push("init.lua"); + a.push(format!(".{}/{}", &request_uri_path, "init.lua")); if let Ok(b) = a.canonicalize() { if b.exists() { request_uri_path += "init.lua"; @@ -639,7 +431,7 @@ async fn handler(request: Request) -> Result, (StatusCode, String)> return Err((StatusCode::BAD_REQUEST, "400: Bad Request".to_string())); } - let body = match render_dir(&path, start).await { + let body = match crate::index::render_index(&path, start).await { Ok(body) => Ok(body), Err(e) => { let ioerror: Option<&std::io::Error> = e.downcast_ref(); @@ -659,139 +451,3 @@ async fn handler(request: Request) -> Result, (StatusCode, String)> Ok(Html(body?)) } - -async fn render_dir(path: &PathBuf, time: Instant) -> AResult { - let time2 = Instant::now(); - let base = BASE_DIR.display().to_string(); - let dirname = path - .display() - .to_string() - .replace(&base, "") - .replace('\\', "/"); - - let filename = path.file_name().unwrap().to_string_lossy().to_string(); - let description = PathBuf::from_str(&format!( - "{}/.{}.info", - path.parent().unwrap().to_string_lossy(), - filename - ))?; - let description = if description.exists() { - Some(fs::read_to_string(description)?) - } else { - None - }; - - let directories = get_directories(path).await?; - let files = get_files(path).await?; - - let tera = &*TERA; - - let mut context = Context::new(); - context.insert("description", &description); - context.insert("dirname", &dirname); - context.insert("directories", &directories); - context.insert("files", &files); - - let loading = time2.elapsed() + time.elapsed(); - context.insert("loading", &loading.as_millis()); - Ok(tera.render("index.html.jinja", &context)?) -} - -async fn get_directories(path: &PathBuf) -> AResult> { - let mut contents = ReadDirStream::new(tokio::fs::read_dir(&path).await?); - - let mut files = vec![]; - - while let Some(file) = contents.next().await { - let file = file?; - let metadata = file.metadata().await?; - if !metadata.is_dir() { - continue; - } - let filename = file.file_name().to_string_lossy().to_string(); - if filename.starts_with('.') { - continue; - } - - // let created: OffsetDateTime = metadata.created()?.into(); - let modified: OffsetDateTime = metadata.modified()?.into(); - - let description = PathBuf::from_str(&format!( - "{}/.{}.info", - file.path().parent().unwrap().to_string_lossy(), - filename - ))?; - - let description = if description.exists() { - Some(fs::read_to_string(description)?) - } else { - None - }; - - files.push(FileInfo { - filename, - format: "DIR".to_string(), - size: 0, - icon: None, - // created, - modified, - description, - }); - } - - Ok(files) -} - -async fn get_files(path: &PathBuf) -> AResult> { - let mut contents = ReadDirStream::new(tokio::fs::read_dir(&path).await?); - - let mut files = vec![]; - - while let Some(file) = contents.next().await { - let file = file?; - let metadata = file.metadata().await?; - if metadata.is_dir() { - continue; - } - let filename = file.file_name().to_string_lossy().to_string(); - if filename.starts_with('.') { - continue; - } - let format = file_format::FileFormat::from_file(file.path())?; - let format = format.media_type(); - - let icon = if format == "application/x-dosexec" { - // .. - extract_icon(&file.path()).ok().flatten() - } else { - None - }; - - // let created: OffsetDateTime = metadata.created()?.into(); - let modified: OffsetDateTime = metadata.modified()?.into(); - - let description = PathBuf::from_str(&format!( - "{}/.{}.info", - file.path().parent().unwrap().to_string_lossy(), - filename - ))?; - - let description = if description.exists() { - Some(fs::read_to_string(description)?) - } else { - None - }; - - files.push(FileInfo { - filename, - format: format.to_string(), - size: metadata.len() as usize, - icon, - // created, - modified, - description, - }); - } - - Ok(files) -}