diff --git a/confetti-box/src/endpoints.rs b/confetti-box/src/endpoints.rs index ef34161..479aecc 100644 --- a/confetti-box/src/endpoints.rs +++ b/confetti-box/src/endpoints.rs @@ -3,14 +3,15 @@ use std::{ sync::{Arc, RwLock}, }; +use chrono::Utc; +use maud::{html, Markup, DOCTYPE}; use rocket::{ get, http::ContentType, response::{self, Redirect, Responder, Response}, serde::{self, json::Json}, tokio::{self, fs::File}, uri, Request, State }; use serde::Serialize; use crate::{ - database::{Mmid, MochiFile, Mochibase}, - settings::Settings, + database::{Mmid, MochiFile, Mochibase}, settings::Settings, strings::{to_pretty_size, to_pretty_time, BreakStyle, TimeGranularity} }; /// An endpoint to obtain information about the server's capabilities @@ -39,6 +40,37 @@ pub async fn file_info(db: &State>>, mmid: &str) -> Option Some(Json(entry)) } +#[get("/info/?opengraph")] +pub async fn file_info_opengraph( + db: &State>>, + settings: &State, + mmid: &str, +) -> Option { + let mmid: Mmid = mmid.try_into().ok()?; + let entry = db.read().unwrap().get(&mmid).cloned()?; + + let file = File::open(settings.file_dir.join(entry.hash().to_string())) + .await + .ok()?; + + let size = to_pretty_size(file.metadata().await.ok()?.len()); + + let seconds_till_expiry = entry.expiry().signed_duration_since(Utc::now()).num_seconds(); + let expiry = to_pretty_time(seconds_till_expiry as u32, BreakStyle::Space, TimeGranularity::Minutes); + + let title = entry.name().clone() + " - " + &size + " - " + &expiry; + + Some(html! { + (DOCTYPE) + meta charset="UTF-8"; + title { (title) } + link rel="icon" type="image/svg+xml" href="/favicon.svg"; + meta property="og:title" content=(title); + meta property="twitter:title" content=(title); + meta property="og:description" content={"Size: " (size) ", expires in " (expiry)}; + }) +} + #[derive(Serialize, Debug)] #[serde(crate = "rocket::serde")] pub struct ServerInfo { diff --git a/confetti-box/src/lib.rs b/confetti-box/src/lib.rs index c85053e..62dbf97 100644 --- a/confetti-box/src/lib.rs +++ b/confetti-box/src/lib.rs @@ -24,6 +24,7 @@ use rocket::{ fs, io::{AsyncSeekExt, AsyncWriteExt} }, Data, State }; +use strings::{BreakStyle, TimeGranularity}; use uuid::Uuid; #[get("/")] @@ -47,7 +48,7 @@ pub fn home(settings: &State) -> Markup { button.button.{@if settings.duration.default == *d { "selected" }} data-duration-seconds=(d.num_seconds()) { - (PreEscaped(to_pretty_time(d.num_seconds() as u32))) + (PreEscaped(to_pretty_time(d.num_seconds() as u32, BreakStyle::Break, TimeGranularity::Seconds))) } } } diff --git a/confetti-box/src/main.rs b/confetti-box/src/main.rs index d2f0fc8..ca59f9e 100644 --- a/confetti-box/src/main.rs +++ b/confetti-box/src/main.rs @@ -84,6 +84,7 @@ async fn main() { confetti_box::websocket_upload, endpoints::server_info, endpoints::file_info, + endpoints::file_info_opengraph, endpoints::lookup_mmid, endpoints::lookup_mmid_noredir, endpoints::lookup_mmid_name, diff --git a/confetti-box/src/strings.rs b/confetti-box/src/strings.rs index 98f8a6d..4ba9417 100644 --- a/confetti-box/src/strings.rs +++ b/confetti-box/src/strings.rs @@ -35,7 +35,21 @@ pub fn parse_time_string(string: &str) -> Result> { Ok(final_time) } -pub fn to_pretty_time(seconds: u32) -> String { +pub enum BreakStyle { + Break, + Newline, + Space, + Nothing, +} + +pub enum TimeGranularity { + Days, + Hours, + Minutes, + Seconds, +} + +pub fn to_pretty_time(seconds: u32, breaks: BreakStyle, granularity: TimeGranularity) -> String { let days = (seconds as f32 / 86400.0).floor(); let hour = ((seconds as f32 - (days * 86400.0)) / 3600.0).floor(); let mins = ((seconds as f32 - (hour * 3600.0) - (days * 86400.0)) / 60.0).floor(); @@ -44,36 +58,68 @@ pub fn to_pretty_time(seconds: u32) -> String { let days = if days == 0.0 { "".to_string() } else if days == 1.0 { - days.to_string() + "
day" + days.to_string() + "\nday" } else { - days.to_string() + "
days" + days.to_string() + "\ndays" }; let hour = if hour == 0.0 { "".to_string() } else if hour == 1.0 { - hour.to_string() + "
hour" + hour.to_string() + "\nhour" } else { - hour.to_string() + "
hours" + hour.to_string() + "\nhours" }; let mins = if mins == 0.0 { "".to_string() } else if mins == 1.0 { - mins.to_string() + "
minute" + mins.to_string() + "\nminute" } else { - mins.to_string() + "
minutes" + mins.to_string() + "\nminutes" }; let secs = if secs == 0.0 { "".to_string() } else if secs == 1.0 { - secs.to_string() + "
second" + secs.to_string() + "\nsecond" } else { - secs.to_string() + "
seconds" + secs.to_string() + "\nseconds" }; - (days + " " + &hour + " " + &mins + " " + &secs) - .trim() - .to_string() + let mut out_string = match granularity { + TimeGranularity::Days => days, + TimeGranularity::Hours => days + " " + &hour, + TimeGranularity::Minutes => days + " " + &hour + " " + &mins, + TimeGranularity::Seconds => days + " " + &hour + " " + &mins + " " + &secs, + }.trim().to_string(); + + match breaks { + BreakStyle::Break => out_string = out_string.replace("\n", "
"), + BreakStyle::Newline => (), + BreakStyle::Space => out_string = out_string.replace("\n", " "), + BreakStyle::Nothing => out_string = out_string.replace("\n", ""), + } + + out_string +} + +pub fn to_pretty_size(size: u64) -> String { + if size < 1000 { + size.to_string() + " B" + } else if size < 1000u64.pow(2) { + (size / 1000).to_string() + " kB" + } else if size < 1000u64.pow(3) { + (size / 1000u64.pow(2)).to_string() + " MB" + } else if size < 1000u64.pow(4) { + (size / 1000u64.pow(3)).to_string() + " GB" + } else if size < 1000u64.pow(5) { + (size as u128 / 1000u128.pow(4)).to_string() + " TB" + } else if size < 1000u64.pow(6) { + (size as u128 / 1000u128.pow(5)).to_string() + " PB" + } else if size < 1000u64.pow(7) { + (size as u128 / 1000u128.pow(6)).to_string() + " EB" + } else { + size.to_string() + " B" + } }