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.add_raw_template( "markdown.html.jinja", include_str!("../templates/markdown.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)?) } pub fn render_markdown(filename: &str, content: &str) -> AResult { let tera = &*TERA; let mut context = Context::new(); context.insert("filename", &filename); context.insert("content", &content); Ok(tera.render("markdown.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) }