diff options
Diffstat (limited to 'server/src/ui/mod.rs')
-rw-r--r-- | server/src/ui/mod.rs | 136 |
1 files changed, 136 insertions, 0 deletions
diff --git a/server/src/ui/mod.rs b/server/src/ui/mod.rs new file mode 100644 index 0000000..b98fbec --- /dev/null +++ b/server/src/ui/mod.rs @@ -0,0 +1,136 @@ +/* + This file is part of jellything (https://codeberg.org/metamuffin/jellything) + which is licensed under the GNU Affero General Public License (version 3); see /COPYING. + Copyright (C) 2025 metamuffin <metamuffin.org> +*/ +use crate::logic::session::Session; +use error::MyResult; +use home::rocket_uri_macro_r_home; +use jellybase::CONF; +use layout::{DynLayoutPage, LayoutPage}; +use log::debug; +use markup::Render; +use rocket::{ + futures::FutureExt, + get, + http::{ContentType, Header, Status}, + response::{self, Redirect, Responder}, + Either, Request, Response, +}; +use std::{ + collections::hash_map::DefaultHasher, + future::Future, + hash::{Hash, Hasher}, + io::Cursor, + os::unix::prelude::MetadataExt, + path::Path, + pin::Pin, +}; +use tokio::{ + fs::{read_to_string, File}, + io::AsyncRead, +}; + +pub mod account; +pub mod admin; +pub mod assets; +pub mod browser; +pub mod error; +pub mod home; +pub mod layout; +pub mod node; +pub mod player; +pub mod search; +pub mod sort; +pub mod stats; +pub mod style; + +#[get("/")] +pub async fn r_index(sess: Option<Session>) -> MyResult<Either<Redirect, DynLayoutPage<'static>>> { + if sess.is_some() { + Ok(Either::Left(Redirect::temporary(rocket::uri!(r_home())))) + } else { + let front = read_to_string(CONF.asset_path.join("front.htm")).await?; + Ok(Either::Right(LayoutPage { + title: "Home".to_string(), + content: markup::new! { + @markup::raw(&front) + }, + ..Default::default() + })) + } +} + +#[get("/favicon.ico")] +pub async fn r_favicon() -> MyResult<File> { + Ok(File::open(CONF.asset_path.join("favicon.ico")).await?) +} + +pub struct HtmlTemplate<'a>(pub markup::DynRender<'a>); + +impl<'r> Responder<'r, 'static> for HtmlTemplate<'_> { + fn respond_to(self, _req: &'r Request<'_>) -> response::Result<'static> { + let mut out = String::new(); + self.0.render(&mut out).unwrap(); + Response::build() + .header(ContentType::HTML) + .sized_body(out.len(), Cursor::new(out)) + .ok() + } +} + +pub struct Defer(Pin<Box<dyn Future<Output = String> + Send>>); + +impl AsyncRead for Defer { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut tokio::io::ReadBuf<'_>, + ) -> std::task::Poll<std::io::Result<()>> { + match self.0.poll_unpin(cx) { + std::task::Poll::Ready(r) => { + buf.put_slice(r.as_bytes()); + std::task::Poll::Ready(Ok(())) + } + std::task::Poll::Pending => std::task::Poll::Pending, + } + } +} + +pub struct CacheControlFile(File, String); +impl CacheControlFile { + pub async fn new_cachekey(p: &Path) -> anyhow::Result<Self> { + let tag = p.file_name().unwrap().to_str().unwrap().to_owned(); + let f = File::open(p).await?; + Ok(Self(f, tag)) + } + pub async fn new_mtime(f: File) -> Self { + let meta = f.metadata().await.unwrap(); + let modified = meta.mtime(); + let mut h = DefaultHasher::new(); + modified.hash(&mut h); + let tag = format!("{:0>16x}", h.finish()); + Self(f, tag) + } +} +impl<'r> Responder<'r, 'static> for CacheControlFile { + fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> { + let Self(file, tag) = self; + if req.headers().get_one("if-none-match") == Some(&tag) { + debug!("file cache: not modified"); + Response::build() + .status(Status::NotModified) + .header(Header::new("cache-control", "private")) + .header(Header::new("etag", tag)) + .ok() + } else { + debug!("file cache: transfer"); + Response::build() + .status(Status::Ok) + .header(Header::new("cache-control", "private")) + .header(Header::new("etag", tag)) + .streamed_body(file) + .ok() + } + } +} |