about summary refs log tree commit diff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs150
1 files changed, 149 insertions, 1 deletions
diff --git a/src/main.rs b/src/main.rs
index 1ba94f7..c429e8a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,3 +1,151 @@
+mod error;
 mod fs;
+mod settings;
+mod templated;
 
-fn main() {}
+use std::{os::unix::fs::MetadataExt, str::FromStr};
+
+use axum::{
+	body::Body, extract::Path, http::header, response::Response, routing::get, Extension, Router,
+};
+use bempline::{Document, Options};
+use camino::Utf8PathBuf;
+pub use error::RuntimeError;
+use fs::Filesystem;
+use settings::Settings;
+use tokio_util::io::ReaderStream;
+
+use crate::templated::Templated;
+
+#[tokio::main]
+async fn main() {
+	let fs = Filesystem::new("../inf/served");
+
+	let settings = Settings {
+		template_dir: Utf8PathBuf::from("../inf/templates"),
+	};
+
+	let app = Router::new()
+		.route("/", get(index_handler))
+		.route("/*path", get(handler))
+		.layer(Extension(fs))
+		.layer(Extension(settings));
+
+	let listener = tokio::net::TcpListener::bind("0.0.0.0:2560").await.unwrap();
+	axum::serve(listener, app).await.unwrap()
+}
+
+async fn index_handler(fse: Extension<Filesystem>, se: Extension<Settings>) -> Response {
+	handler(fse, se, Path(String::from("/"))).await
+}
+
+async fn handler(
+	Extension(fs): Extension<Filesystem>,
+	Extension(settings): Extension<Settings>,
+	Path(path): Path<String>,
+) -> Response {
+	match falible_handler(fs, settings, path).await {
+		Ok(resp) => resp,
+		Err(re) => Response::builder()
+			.body(Body::from(re.to_string()))
+			.unwrap(),
+	}
+}
+
+async fn falible_handler(
+	fs: Filesystem,
+	settings: Settings,
+	path: String,
+) -> Result<Response, RuntimeError> {
+	println!("raw = {path}");
+
+	let path = path.parse()?;
+
+	println!("path = {path}");
+
+	let filepath = fs.resolve(&path)?;
+
+	let ext = filepath.extension().unwrap_or_default();
+
+	if ext != "html" {
+		send_file(filepath).await
+	} else {
+		let content = Filesystem::read_to_string(&filepath).await?;
+		match Templated::from_str(&content) {
+			Ok(templated) => send_template(templated, filepath, settings).await,
+			Err(_) => Ok(Response::builder()
+				.header(header::CONTENT_TYPE, "text/html")
+				.body(Body::from(content))
+				.unwrap()),
+		}
+	}
+}
+
+// 20 megabytes
+const STREAM_AFTER: u64 = 20 * 1024 * 1024;
+
+async fn send_file(filepath: Utf8PathBuf) -> Result<Response, RuntimeError> {
+	let ext = filepath.extension().unwrap_or_default();
+
+	let mime = match ext {
+		// Text
+		"css" => "text/css",
+		"html" => "text/html",
+		"js" => "text/javascript",
+		"txt" => "txt/plain",
+
+		// Multimedia
+		"gif" => "image/gif",
+		"jpg" | "jpeg" => "image/jpeg",
+		"mp4" => "video/mp4",
+		"png" => "image/png",
+		_ => "",
+	};
+
+	let mut response = Response::builder();
+	if !mime.is_empty() {
+		response = response.header(header::CONTENT_TYPE, mime);
+	}
+
+	let metadata = Filesystem::metadata(&filepath)?;
+	if metadata.size() > STREAM_AFTER {
+		let file = Filesystem::open(filepath).await?;
+		let stream = ReaderStream::new(file);
+		Ok(response.body(Body::from_stream(stream)).unwrap())
+	} else {
+		let content = Filesystem::read(filepath).await?;
+		Ok(response.body(Body::from(content)).unwrap())
+	}
+}
+
+async fn send_template(
+	templated: Templated,
+	path: Utf8PathBuf,
+	settings: Settings,
+) -> Result<Response, RuntimeError> {
+	let template_stem = templated.frontmatter.get("template").expect("no template");
+	let template_name = Utf8PathBuf::from(format!("{template_stem}.html"));
+	let template_path = settings.template_dir.join(template_name);
+
+	let filename = path.file_name().expect("template has no filename");
+
+	let mut template = Document::from_file(
+		template_path,
+		Options::default().include_path(bempline::options::IncludeMethod::Path(
+			settings.template_dir.as_std_path().to_owned(),
+		)),
+	)
+	.unwrap();
+
+	template.set(
+		"title",
+		templated.frontmatter.get("title").unwrap_or(filename),
+	);
+
+	template.set("main", templated.content);
+
+	Ok(Response::builder()
+		.header(header::CONTENT_TYPE, "text/html")
+		.body(Body::from(template.compile()))
+		.unwrap())
+}