From 4bafa1eb75eea0a105cd7b3ef3ec9a2f80c13cc5 Mon Sep 17 00:00:00 2001 From: Stridsvagn69420 Date: Tue, 15 Nov 2022 22:44:07 +0100 Subject: [PATCH] Added hostinfo route and route utils --- src/album.rs | 2 +- src/artist.rs | 2 +- src/config.rs | 4 ++-- src/hostinfo.rs | 6 +++--- src/server/mod.rs | 43 ++++++++++++++++++++++++++++++------- src/server/responses.rs | 41 +++++++++++++++++++++++++++++++++++ src/server/routes.rs | 47 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 130 insertions(+), 15 deletions(-) create mode 100644 src/server/responses.rs diff --git a/src/album.rs b/src/album.rs index 5357411..dce30b8 100644 --- a/src/album.rs +++ b/src/album.rs @@ -6,7 +6,7 @@ use super::{Artist, Metadata, add_vec, remove_vec}; /// Album /// /// A struct representing an album of the Cyrkensia repository. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Album { /// Name /// diff --git a/src/artist.rs b/src/artist.rs index b715dfa..fffef9e 100644 --- a/src/artist.rs +++ b/src/artist.rs @@ -7,7 +7,7 @@ use super::{Owner, add_vec, remove_vec}; /// Artist /// /// A struct representing an author or artist of a song. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Artist { pub name: String, diff --git a/src/config.rs b/src/config.rs index 2215eb3..0903cba 100644 --- a/src/config.rs +++ b/src/config.rs @@ -59,9 +59,9 @@ pub struct Config { /// Maximum Age /// - /// The maximum age of the [Hostinfo] in milliseconds as a [u32]. If [None], the Hostinfo will always be regenerated when its route is accessed. + /// The maximum age of the [Hostinfo] in milliseconds as a [u64]. If [None], the Hostinfo will always be regenerated when its route is accessed. /// This basically activates caching. - pub max_age: Option + pub max_age: Option } impl Config { diff --git a/src/hostinfo.rs b/src/hostinfo.rs index 1c40524..1ea45e6 100644 --- a/src/hostinfo.rs +++ b/src/hostinfo.rs @@ -10,7 +10,7 @@ use super::{Owner, Album, Config, Metadata}; /// Hostinfo /// /// A struct representing the metadata or hostinfo and index of a Cyrkensia repository. -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct Hostinfo { /// Name /// @@ -80,13 +80,13 @@ impl Hostinfo { /// Generate Hostinfo /// /// Generates a Hostinfo based on a [Config]. - pub fn generate(cfg: Config) -> io::Result { + pub fn generate(cfg: &Config) -> io::Result { let mut albums: Vec = Vec::new(); for rootpath in &cfg.root { let album_slice = Hostinfo::read_albums(rootpath)?; albums.extend(album_slice); } - let mut hostinfo = Hostinfo::from(cfg); + let mut hostinfo = Hostinfo::from(cfg.clone()); hostinfo.size = albums.iter().map(|x| x.size).sum(); hostinfo.albums = albums; Ok(hostinfo) diff --git a/src/server/mod.rs b/src/server/mod.rs index 4f83728..a773f24 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,4 +1,6 @@ +use actix_web::http::Uri; use crate::{Hostinfo, Config}; +use std::fmt::Write; use std::sync::Mutex; use std::time::Instant; use std::io; @@ -19,6 +21,11 @@ pub mod redirect; /// Submodule containing Cyrkensia routes. pub mod routes; +/// Responses +/// +/// Submodue containing Cyrkensia responses. +pub mod responses; + /// Cyrkensia State /// /// State for the Actix-Web server. Used in [routes]. @@ -32,14 +39,14 @@ pub struct CyrkensiaState { /// /// The latest generated [Hostinfo]. /// Only used if caching is activated. - pub hostinfo: Mutex>, + pub hostinfo: Mutex, /// Last Hostinfo Update /// /// The [timestamp](Instant) when the [hostinfo] was last updated. /// `.elapsed().as_secs()` will be used to compare it with the `max_age` in the [Config]. /// Only used if caching is activated. - pub last_updated: Mutex> + pub last_updated: Mutex } impl CyrkensiaState { @@ -47,21 +54,41 @@ impl CyrkensiaState { /// /// Creates a new [CyrkensiaState] with given [Config]. pub fn new(cfg: Config) -> io::Result { + // State with caching if cfg.max_age.is_some() { - // State with caching - let hostinfo = Hostinfo::generate(cfg.clone())?; + let hostinfo = Hostinfo::generate(&cfg)?; return Ok(CyrkensiaState { - last_updated: Mutex::new(Some(Instant::now())), - hostinfo: Mutex::new(Some(hostinfo)), + last_updated: Mutex::new(Instant::now()), + hostinfo: Mutex::new(hostinfo), config: cfg }); } // State without caching Ok(CyrkensiaState { - hostinfo: Mutex::new(None), - last_updated: Mutex::new(None), + hostinfo: Mutex::new(Hostinfo::empty()), + last_updated: Mutex::new(Instant::now()), config: cfg, }) } +} + +/// Uri Display without Query +/// +/// Displays a Uri without the query parameters +pub fn uri_noquery(uri: &Uri) -> String { + let mut f = String::new(); + + // Protocol + if let Some(scheme) = uri.scheme() { + let _ = write!(&mut f, "{}://", scheme); + } + // Server + if let Some(authority) = uri.authority() { + let _ = write!(&mut f, "{}", authority); + } + // Path + let _ = write!(&mut f, "{}", uri.path()); + + f } \ No newline at end of file diff --git a/src/server/responses.rs b/src/server/responses.rs new file mode 100644 index 0000000..cd628ab --- /dev/null +++ b/src/server/responses.rs @@ -0,0 +1,41 @@ +use std::io; +use actix_web::{HttpResponse, body::MessageBody}; +use actix_web::http::header::{ContentType, CONTENT_LENGTH}; +use crate::{Config, Hostinfo}; + +/// Hostinfo from Config +/// +/// Attempts to create a [HttpResponse] from a generated [Hostinfo] by a [Config]. +pub fn hostinfo_json(cfg: &Config) -> io::Result { + // Generate Hostinfo + let hostinfo = Hostinfo::generate(cfg)?; + hostinfo_data(&hostinfo) +} + +/// Hostinfo from Data +/// +/// Attempts to create a [HttpResponse] from a [Hostinfo] struct. +pub fn hostinfo_data(hstinfo: &Hostinfo) -> io::Result { + // Convert to String + let raw_json = serde_json::to_string(hstinfo)?; + + // Return HttpReponse + Ok(HttpResponse::Ok() + .content_type(ContentType::json()) + .append_header( + (CONTENT_LENGTH, raw_json.len()) + ) + .body(raw_json)) +} + +/// HTTP Status 500 +/// +/// Returns a 500 Error with an optional body message +pub fn server_500(msg: Option) -> HttpResponse { + if let Some(message) = msg { + return HttpResponse::InternalServerError() + .body(message) + } + HttpResponse::InternalServerError() + .finish() +} \ No newline at end of file diff --git a/src/server/routes.rs b/src/server/routes.rs index e69de29..ab5d842 100644 --- a/src/server/routes.rs +++ b/src/server/routes.rs @@ -0,0 +1,47 @@ +use std::time::Instant; +use actix_web::{web, Responder, HttpRequest}; +use super::{CyrkensiaState, responses, uri_noquery}; +use crate::Hostinfo; + +/// Hostinfo Route +/// +/// Route for serving a [Hostinfo]. Server needs [CyrkensiaState] in `.app_data()` for this. +pub async fn hostinfo(req: HttpRequest, data: web::Data) -> impl Responder { + // Get config + let Some(delay) = data.config.max_age else { + // Ad hoch Hostinfo + let Ok(resp) = responses::hostinfo_json(&data.config) else { + return responses::server_500(Some("Failed to generate hostinfo")); + }; + return resp; + }; + + // Get last update timestamp and cached hostinfo + let Ok(mut last_updated) = data.last_updated.lock() else { + return responses::server_500(None::); + }; + let Ok(mut hostinfo) = data.hostinfo.lock() else { + return responses::server_500(None::); + }; + + if last_updated.elapsed().as_secs() >= delay { + // Generate new Hostinfo if expired + let Ok(new_hostinfo) = Hostinfo::generate(&data.config) else { + return responses::server_500(Some("Failed to update hostinfo")); + }; + + // Update Hostinfo and Timestamp + *hostinfo = new_hostinfo; + *last_updated = Instant::now(); + } + + // Set Origin URL + let mut final_hostinfo = hostinfo.clone(); + final_hostinfo.set_origin(uri_noquery(req.uri())); + + // Return final result + let Ok(finalres) = responses::hostinfo_data(&final_hostinfo) else { + return responses::server_500(Some("Failed to generate hostinfo")); + }; + finalres +} \ No newline at end of file