diff --git a/Cargo.lock b/Cargo.lock index 77c607e89cc11b8ee659b5150152e4b4ddfa6435..37a7a4742d24025828387325d0b34ffc9a496e1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anyhow" version = "1.0.69" @@ -108,12 +117,43 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "iana-time-zone", + "js-sys", + "num-integer", + "num-traits", + "time 0.1.45", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "codespan-reporting" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] + [[package]] name = "color_quant" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cpufeatures" version = "0.2.5" @@ -191,6 +231,50 @@ dependencies = [ "typenum", ] +[[package]] +name = "cxx" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90d59d9acd2a682b4e40605a242f6670eaa58c5957471cbf85e8aa6a0b97a5e8" +dependencies = [ + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", +] + +[[package]] +name = "cxx-build" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebfa40bda659dd5c864e65f4c9a2b0aff19bea56b017b9b77c73d3766a453a38" +dependencies = [ + "cc", + "codespan-reporting", + "once_cell", + "proc-macro2", + "quote", + "scratch", + "syn", +] + +[[package]] +name = "cxxbridge-flags" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "457ce6757c5c70dc6ecdbda6925b958aae7f959bda7d8fb9bde889e34a09dc03" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebf883b7aacd7b2aeb2a7b338648ee19f57c140d4ee8e52c68979c6b2f7f2263" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.6" @@ -331,7 +415,7 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] @@ -471,6 +555,30 @@ dependencies = [ "want", ] +[[package]] +name = "iana-time-zone" +version = "0.1.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "winapi", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +dependencies = [ + "cxx", + "cxx-build", +] + [[package]] name = "idna" version = "0.3.0" @@ -583,6 +691,15 @@ dependencies = [ "safemem", ] +[[package]] +name = "link-cplusplus" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.6" @@ -662,7 +779,7 @@ checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" dependencies = [ "libc", "log", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.42.0", ] @@ -785,12 +902,12 @@ name = "org_flux" version = "0.1.0" dependencies = [ "anyhow", + "chrono", "fnv", "image", "orgize", "serde", "syntect", - "time", "tokio", "toml", "warp", @@ -889,7 +1006,7 @@ dependencies = [ "line-wrap", "quick-xml", "serde", - "time", + "time 0.3.17", ] [[package]] @@ -1067,6 +1184,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scratch" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" + [[package]] name = "serde" version = "1.0.152" @@ -1248,6 +1371,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + [[package]] name = "thiserror" version = "1.0.38" @@ -1288,6 +1420,17 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi", +] + [[package]] name = "time" version = "0.3.17" @@ -1529,6 +1672,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "url" version = "2.3.1" @@ -1604,6 +1753,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 65a47df4b732ab822ae9de12120a45e6b059f924..7dfe630f326529c6f4af0d90f1033b265018009a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ syntect = "5.0" anyhow = "1.0" tokio = { version = "1.0", features = ["full"] } warp = "0.3" -time = { version = "0.3", features = ["macros", "formatting"] } +chrono = { version = "0.4", features = ["clock"] } image = "0.24.5" fnv = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/src/date.rs b/src/date.rs index 0de7ad81a347b6ca2f358634a340f533f7cb864f..1e29dba8e42b8a38aac402b6340517edd514a090 100644 --- a/src/date.rs +++ b/src/date.rs @@ -1,11 +1,11 @@ use anyhow::Context; -use time::{macros::format_description, Date}; +use chrono::NaiveDate; // very basic org mode date parser -pub fn parse_org_date(s: &str) -> anyhow::Result<Date> { +pub fn parse_org_date(s: &str) -> anyhow::Result<NaiveDate> { let s = s.strip_prefix('<').context("Invalid date")?; let s = s.strip_suffix('>').context("Invalid date")?; let s = s.rsplit_once(' ').context("Invalid date")?.0; - Ok(Date::parse(s, format_description!("[year]-[month]-[day]"))?) + Ok(NaiveDate::parse_from_str(s, "%Y-%m-%d")?) } diff --git a/src/image.rs b/src/image.rs index c512d79e60b04abada8228b2aa3a66094adf4e32..312688f14a14d640c6b3a63b96f092d19437f569 100644 --- a/src/image.rs +++ b/src/image.rs @@ -3,7 +3,11 @@ use std::{io::Cursor, path::Path}; use anyhow::{bail, Context}; use image::{io::Reader as ImageReader, DynamicImage, ImageOutputFormat}; -use crate::{load::Loadable, resp::Response, util::slugify}; +use crate::{ + load::Loadable, + resp::{Expiry, Response}, + util::slugify, +}; pub struct MyImage { original: DynamicImage, @@ -30,11 +34,13 @@ impl MyImage { self.original_url, Response { content_type, + expiry: Expiry::Year, data: original, }, self.thumbnail_url, Response { content_type, + expiry: Expiry::Year, data: thumbnail, }, )) diff --git a/src/main.rs b/src/main.rs index 679afd7307b6ac0fc888ef3fdc157f70a413e973..2d8fb83c1f0064435e461b768f277f6d34427dc7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,10 @@ use tokio::sync::RwLock; use blog::{Blog, RenderedBlog}; use warp::{ http::HeaderValue, - hyper::{header::CONTENT_TYPE, Body}, + hyper::{ + header::{CONTENT_TYPE, EXPIRES}, + Body, + }, path::FullPath, reply, Filter, }; @@ -32,9 +35,15 @@ async fn handle_request( let (status, content) = blog.get(req.as_str()); let mut reply = reply::Response::new(Body::from(content.data)); - reply - .headers_mut() - .insert(CONTENT_TYPE, HeaderValue::from_static(content.content_type)); + let headers = reply.headers_mut(); + headers.insert(CONTENT_TYPE, HeaderValue::from_static(content.content_type)); + + if let Some(expires) = content.expiry.header_value() { + // TODO log a metric (statsd) if this fails) + if let Ok(hv) = HeaderValue::from_str(&expires) { + headers.insert(EXPIRES, hv); + } + } *reply.status_mut() = status; Ok(reply) diff --git a/src/misc.rs b/src/misc.rs index 4f725bc9c65fc7fac824b47ca77ddb25e5148e62..7e327e3ce7b3e616ccd56aff9db20848656373b4 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -2,7 +2,11 @@ use std::{fs::File, io::Read, path::Path}; use anyhow::{bail, Context}; -use crate::{load::Loadable, resp::Response, util::slugify}; +use crate::{ + load::Loadable, + resp::{Expiry, Response}, + util::slugify, +}; pub struct Misc { pub url: String, @@ -42,6 +46,7 @@ impl Misc { pub fn response(self) -> Response { Response { content_type: self.content_type, + expiry: Expiry::Year, data: self.data, } } diff --git a/src/parser.rs b/src/parser.rs index e94f35893776d2103281bee17ccdf58860bcda50..c050e6c8d32f66905327e0fcddad1bb7495e68cd 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,7 @@ use anyhow::bail; use anyhow::Context; use anyhow::Error; +use chrono::NaiveDate; use orgize::elements::Keyword; use orgize::elements::Link; use orgize::elements::Timestamp; @@ -15,8 +16,6 @@ use syntect::{ html::highlighted_html_for_string, parsing::SyntaxSet, }; -use time::macros::format_description; -use time::Date; pub trait Parser<T> { fn start(&mut self, _: &Element) -> anyhow::Result<()>; @@ -80,16 +79,14 @@ impl HtmlHandler<Error> for CustomHtmlHandler { _ => bail!("Unrecognized special block name {}", block.name), }, Element::Timestamp(Timestamp::Active { start, .. }) => { - let date = Date::from_calendar_date( + let date = NaiveDate::from_ymd_opt( start.year.into(), - start.month.try_into()?, - start.day, - )?; + start.month.into(), + start.day.into(), + ) + .context("Invalid date")?; - date.format_into( - &mut w, - format_description!("[day] [month repr:short] [year]"), - )?; + write!(w, "{}", date.format("%d %b %Y"))?; } Element::Link(Link { path, desc }) if desc.is_none() => { write!(w, r#"<a href="/img/{path}"><img src="/thumb/{path}">"#)?; diff --git a/src/post.rs b/src/post.rs index 79eca9e4ec4805cc36db4f945910a72da463b95d..780012116686736b9518a15bd6395fd20aef5418 100644 --- a/src/post.rs +++ b/src/post.rs @@ -1,8 +1,8 @@ use std::{cmp::Ordering, fs, path::Path}; use anyhow::{bail, Context}; +use chrono::NaiveDate; use orgize::{elements::Keyword, export::HtmlHandler, Element}; -use time::{macros::format_description, Date}; use crate::{ date::parse_org_date, @@ -14,7 +14,7 @@ use crate::{ #[derive(Eq)] pub struct Post { pub title: String, - pub date: Option<Date>, + pub date: Option<NaiveDate>, pub index: u32, pub content: String, pub url: String, @@ -64,9 +64,10 @@ impl Post { fn date_f(&self) -> anyhow::Result<Option<String>> { let date_f = match self.date { - Some(date) => date.format(format_description!("[day] [month repr:short] [year]"))?, + Some(date) => date.format("%d %b %Y"), None => return Ok(None), - }; + } + .to_string(); Ok(Some(date_f)) } @@ -115,7 +116,7 @@ impl PartialEq for Post { struct PostParser { title: Option<String>, - date: Option<Date>, + date: Option<NaiveDate>, index: Option<u32>, url: String, content: Vec<u8>, diff --git a/src/resp.rs b/src/resp.rs index 009574150920d743b74d1529e89b680f74bc280e..b3f45a348e7a1bbe968ced229ad009d2edcb19e0 100644 --- a/src/resp.rs +++ b/src/resp.rs @@ -1,6 +1,26 @@ +use chrono::{Days, Utc}; + +#[derive(Copy, Clone)] +pub enum Expiry { + Instant, + Year, +} + +impl Expiry { + pub fn header_value(self) -> Option<String> { + let date = match self { + Expiry::Instant => return None, + Expiry::Year => Utc::now() + Days::new(365), + }; + + Some(date.format("%a, %d %b %Y %T GMT").to_string()) + } +} + #[derive(Clone)] pub struct Response { pub content_type: &'static str, + pub expiry: Expiry, pub data: Vec<u8>, } @@ -8,6 +28,7 @@ impl Response { pub fn html(html: String) -> Self { Self { content_type: "text/html; charset=utf-8", + expiry: Expiry::Instant, data: html.into_bytes(), } } @@ -15,6 +36,7 @@ impl Response { pub fn css(css: String) -> Self { Self { content_type: "text/css; charset=utf-8", + expiry: Expiry::Year, data: css.into_bytes(), } } @@ -22,6 +44,7 @@ impl Response { pub fn png(data: Vec<u8>) -> Self { Self { content_type: "image/png", + expiry: Expiry::Year, data, } } @@ -29,6 +52,7 @@ impl Response { pub fn ico(data: Vec<u8>) -> Self { Self { content_type: "image/x-icon", + expiry: Expiry::Year, data, } }