From 273ec529c02fbc6f7349f1b13f2e3b05263c7e94 Mon Sep 17 00:00:00 2001 From: Pantonshire Date: Fri, 8 Jul 2022 19:59:47 +0100 Subject: [PATCH] Changes to config format, move all global context into dedicated Context struct --- example_config.toml | 25 ++++++++++------ src/bin/blog_server/config.rs | 25 +++++++++++----- src/bin/blog_server/context.rs | 31 +++++++++++++++++++ src/bin/blog_server/main.rs | 36 ++++++++++------------- src/bin/blog_server/render.rs | 22 ++++++-------- src/bin/blog_server/service/atom.rs | 25 +++++++--------- src/bin/blog_server/service/index.rs | 10 +++---- src/bin/blog_server/service/post.rs | 10 +++---- src/bin/blog_server/service/posts_list.rs | 10 +++---- src/bin/blog_server/service/rss.rs | 26 +++++++--------- src/bin/blog_server/service/site.rs | 35 +++++++++------------- src/lib/db.rs | 5 ++-- 12 files changed, 142 insertions(+), 118 deletions(-) create mode 100644 src/bin/blog_server/context.rs diff --git a/example_config.toml b/example_config.toml index e767e76..a7635b4 100644 --- a/example_config.toml +++ b/example_config.toml @@ -1,19 +1,21 @@ -bind = "127.0.0.1:8080" -concurrency_limit = 128 +bind = "127.0.0.1:8080" +concurrency_limit = 128 +fs_event_delay_millis = 2000 +namespace_uuid = "00000000-0000-0000-0000-000000000000" +[site] +protocol = "http" +domain = "localhost:8080" + +[content] static_dir = "./content/static/" favicon_dir = "./content/favicon/" robots_path = "./content/robots.txt" posts_dir = "./articles/src/" post_media_dir = "./articles/media/" -fs_event_delay_millis = 2000 - -namespace_uuid = "00000000-0000-0000-0000-000000000000" - -[self_ref] -protocol = "http" -domain = "localhost:8080" +[github] +source_url = "https://github.com/pantonshire/blog_content/blob/main/src" [rss] num_posts = 20 @@ -23,3 +25,8 @@ ttl = 360 [atom] num_posts = 20 title = "Pantonshire" + +[[contact]] +name = "Twitter" +user = "@pantonshire" +url = "https://twitter.com/pantonshire" diff --git a/src/bin/blog_server/config.rs b/src/bin/blog_server/config.rs index 2bcf69e..d2c194e 100644 --- a/src/bin/blog_server/config.rs +++ b/src/bin/blog_server/config.rs @@ -7,25 +7,36 @@ use serde::{Deserialize, Deserializer}; pub(crate) struct Config { pub bind: SocketAddr, pub concurrency_limit: usize, - pub static_dir: PathBuf, - pub favicon_dir: PathBuf, - pub robots_path: PathBuf, - pub posts_dir: PathBuf, - pub post_media_dir: PathBuf, #[serde(rename = "fs_event_delay_millis", deserialize_with = "deserialize_millis")] pub fs_event_delay: Duration, pub namespace_uuid: Uuid, - pub self_ref: SelfRefConfig, + pub content: ContentConfig, + pub github: GithubConfig, + pub site: SiteConfig, pub rss: RssConfig, pub atom: AtomConfig, } #[derive(Deserialize, Clone, Debug)] -pub(crate) struct SelfRefConfig { +pub(crate) struct SiteConfig { pub protocol: String, pub domain: String, } +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct ContentConfig { + pub static_dir: PathBuf, + pub favicon_dir: PathBuf, + pub robots_path: PathBuf, + pub posts_dir: PathBuf, + pub post_media_dir: PathBuf, +} + +#[derive(Deserialize, Clone, Debug)] +pub(crate) struct GithubConfig { + pub source_url: Option, +} + #[derive(Deserialize, Clone, Debug)] pub(crate) struct RssConfig { pub num_posts: usize, diff --git a/src/bin/blog_server/context.rs b/src/bin/blog_server/context.rs new file mode 100644 index 0000000..b451bfb --- /dev/null +++ b/src/bin/blog_server/context.rs @@ -0,0 +1,31 @@ +use blog::db::ConcurrentPostsStore; + +use crate::Config; + +pub(crate) struct Context { + config: Config, + posts: ConcurrentPostsStore, +} + +impl Context { + #[inline] + #[must_use] + pub(crate) fn new(config: Config, posts: ConcurrentPostsStore) -> Self { + Self { + config, + posts, + } + } + + #[inline] + #[must_use] + pub(crate) fn config(&self) -> &Config { + &self.config + } + + #[inline] + #[must_use] + pub(crate) fn posts(&self) -> &ConcurrentPostsStore { + &self.posts + } +} diff --git a/src/bin/blog_server/main.rs b/src/bin/blog_server/main.rs index 247290f..5ebe2e1 100644 --- a/src/bin/blog_server/main.rs +++ b/src/bin/blog_server/main.rs @@ -1,4 +1,5 @@ mod config; +mod context; mod fs_watcher; mod render; mod service; @@ -27,6 +28,7 @@ use blog::{ }; use config::Config; +use context::Context; use render::Renderer; fn main() { @@ -42,7 +44,7 @@ fn run() -> Result<(), Error> { // Load the configuration from the TOML config file specified by the first command-line // argument. - let config = Arc::new({ + let config = { let config_path = env::args().nth(1) .ok_or(Error::NoConfig)?; @@ -53,27 +55,25 @@ fn run() -> Result<(), Error> { contents.parse::() .map_err(Error::BadConfig)? - }); + }; - // Create the data structure used to store the rendered posts. This uses an `Arc` internally, - // so clones will point to the same underlying data. - let posts_store = ConcurrentPostsStore::new(); + // Create the global context that will be used and modified throughout the program. + let context = Arc::new(Context::new(config, ConcurrentPostsStore::new())); let code_renderer = CodeBlockRenderer::new(); // Create the post renderer and the mpsc channel that will be used to communicate with it. let (renderer, tx) = Renderer::new( - config.clone(), - posts_store.clone(), + context.clone(), code_renderer, - config.posts_dir.clone() + context.config().content.posts_dir.clone() ); // Dropping the watcher stops its thread, so keep it alive until the server has stopped. let watcher = fs_watcher::start_watching( tx, - &config.posts_dir, - config.fs_event_delay + &context.config().content.posts_dir, + context.config().fs_event_delay )?; let renderer_handle = thread::spawn(move || { @@ -88,7 +88,7 @@ fn run() -> Result<(), Error> { .enable_all() .build() .map_err(Error::TokioRuntime)? - .block_on(run_server(config, posts_store))?; + .block_on(run_server(context))?; info!("Stopped server"); @@ -105,17 +105,13 @@ fn run() -> Result<(), Error> { Ok(()) } -async fn run_server( - config: Arc, - posts_store: ConcurrentPostsStore, -) -> Result<(), Error> -{ - let service = service::site_service(config.clone(), posts_store); +async fn run_server(context: Arc) -> Result<(), Error> { + let service = service::site_service(context.clone()); - info!(address = %config.bind, "Starting server"); + info!(address = %context.config().bind, "Starting server"); - Server::try_bind(&config.bind) - .map_err(|err| Error::Bind(config.bind, err))? + Server::try_bind(&context.config().bind) + .map_err(|err| Error::Bind(context.config().bind, err))? .serve(service.into_make_service()) .with_graceful_shutdown(handle_interrupt()) .await diff --git a/src/bin/blog_server/render.rs b/src/bin/blog_server/render.rs index 5f9407a..f22822c 100644 --- a/src/bin/blog_server/render.rs +++ b/src/bin/blog_server/render.rs @@ -13,14 +13,12 @@ use tracing::{info, warn, error}; use blog::{ codeblock::CodeBlockRenderer, post::{Error as ParseError, Post, Id}, - db::ConcurrentPostsStore, }; -use crate::Config; +use crate::Context; pub(crate) struct Renderer { - config: Arc, - posts: ConcurrentPostsStore, + context: Arc, code_renderer: CodeBlockRenderer, posts_dir_path: PathBuf, rx: mpsc::Receiver, @@ -28,8 +26,7 @@ pub(crate) struct Renderer { impl Renderer { pub(crate) fn new( - config: Arc, - posts: ConcurrentPostsStore, + context: Arc, code_renderer: CodeBlockRenderer, posts_dir_path: PathBuf, ) -> (Self, mpsc::Sender) @@ -42,8 +39,7 @@ impl Renderer { tx.send(DebouncedEvent::Rescan).unwrap(); (Self { - config, - posts, + context, code_renderer, posts_dir_path, rx, @@ -106,7 +102,7 @@ impl Renderer { fn update(&self, target: &EventTarget) { match self.parse_post_from_target(target) { Ok(post) => { - let mut guard = self.posts.write_blocking(); + let mut guard = self.context.posts().write_blocking(); guard.insert(post); }, Err(err) => { @@ -118,7 +114,7 @@ impl Renderer { #[tracing::instrument(skip(self))] fn rename(&self, old_target: &EventTarget, new_target: &EventTarget) { let post_res = self.parse_post_from_target(new_target); - let mut guard = self.posts.write_blocking(); + let mut guard = self.context.posts().write_blocking(); guard.remove(&old_target.id); match post_res { Ok(post) => { @@ -132,7 +128,7 @@ impl Renderer { #[tracing::instrument(skip(self))] fn remove(&self, target: &EventTarget) { - let mut guard = self.posts.write_blocking(); + let mut guard = self.context.posts().write_blocking(); guard.remove(&target.id); } @@ -168,7 +164,7 @@ impl Renderer { } } - let mut guard = self.posts.write_blocking(); + let mut guard = self.context.posts().write_blocking(); guard.clear(); for post in posts { guard.insert(post); @@ -204,7 +200,7 @@ impl Renderer { Post::new_from_str( &self.code_renderer, - self.config.namespace_uuid, + self.context.config().namespace_uuid, target.id.clone(), updated, &contents diff --git a/src/bin/blog_server/service/atom.rs b/src/bin/blog_server/service/atom.rs index febc830..3f5deaf 100644 --- a/src/bin/blog_server/service/atom.rs +++ b/src/bin/blog_server/service/atom.rs @@ -3,22 +3,19 @@ use std::sync::Arc; use atom_syndication as atom; use axum::{body::Bytes, extract::Extension}; -use blog::{db::ConcurrentPostsStore, time::unix_epoch}; +use blog::time::unix_epoch; -use crate::Config; +use crate::Context; use super::response::Atom; -pub(super) async fn handle( - Extension(config): Extension>, - Extension(posts): Extension, -) -> Atom { +pub(super) async fn handle(Extension(context): Extension>) -> Atom { let (atom_entries, updated) = { - let guard = posts.read().await; + let guard = context.posts().read().await; let atom_entries = guard .iter_by_published() - .take(config.atom.num_posts) + .take(context.config().atom.num_posts) .map(|post| { atom::EntryBuilder::default() .id(format!("urn:uuid:{}", post.uuid())) @@ -27,8 +24,8 @@ pub(super) async fn handle( .links(vec![atom::LinkBuilder::default() .href(format!( "{}://{}/articles/{}", - config.self_ref.protocol, - config.self_ref.domain, + context.config().site.protocol, + context.config().site.domain, post.id() )) .rel("alternate".to_owned()) @@ -50,21 +47,21 @@ pub(super) async fn handle( Atom( atom::FeedBuilder::default() - .id(format!("urn:uuid:{}", config.namespace_uuid)) - .title(config.atom.title.clone()) + .id(format!("urn:uuid:{}", context.config().namespace_uuid)) + .title(context.config().atom.title.clone()) .updated(updated) .links(vec![ atom::LinkBuilder::default() .href(format!( "{}://{}/atom.xml", - config.self_ref.protocol, config.self_ref.domain + context.config().site.protocol, context.config().site.domain )) .rel("self".to_owned()) .build(), atom::LinkBuilder::default() .href(format!( "{}://{}/articles/", - config.self_ref.protocol, config.self_ref.domain + context.config().site.protocol, context.config().site.domain )) .rel("alternate".to_owned()) .mime_type(Some("text/html".to_owned())) diff --git a/src/bin/blog_server/service/index.rs b/src/bin/blog_server/service/index.rs index 8b47850..e35c144 100644 --- a/src/bin/blog_server/service/index.rs +++ b/src/bin/blog_server/service/index.rs @@ -1,13 +1,13 @@ +use std::sync::Arc; + use axum::extract::Extension; use maud::html; -use blog::db::ConcurrentPostsStore; - -use crate::template; +use crate::{Context, template}; use super::response::Html; -pub(super) async fn handle(Extension(posts): Extension) -> Html { +pub(super) async fn handle(Extension(context): Extension>) -> Html { Html::new() .with_title_static("Pantonshire") .with_crawler_permissive() @@ -49,7 +49,7 @@ pub(super) async fn handle(Extension(posts): Extension) -> h2 { "Articles" } p { "Some recent ones:" } ul .articles_list { - @for post in posts.read().await.iter_by_published().rev().take(3) { + @for post in context.posts().read().await.iter_by_published().rev().take(3) { li { h3 { a href={"/articles/" (post.id())} { (post.title()) } } @if let Some(subtitle) = post.subtitle() { diff --git a/src/bin/blog_server/service/post.rs b/src/bin/blog_server/service/post.rs index a463693..3e90c25 100644 --- a/src/bin/blog_server/service/post.rs +++ b/src/bin/blog_server/service/post.rs @@ -1,18 +1,18 @@ +use std::sync::Arc; + use axum::extract::{Extension, Path}; use maud::html; -use blog::db::ConcurrentPostsStore; - -use crate::template; +use crate::{Context, template}; use super::response::{Error, Html}; pub(super) async fn handle( Path(post_id): Path, - Extension(posts): Extension + Extension(context): Extension>, ) -> Result { - let post = posts.get(&post_id) + let post = context.posts().get(&post_id) .await .ok_or(Error::PostNotFound)?; diff --git a/src/bin/blog_server/service/posts_list.rs b/src/bin/blog_server/service/posts_list.rs index 05ca825..8c30a8c 100644 --- a/src/bin/blog_server/service/posts_list.rs +++ b/src/bin/blog_server/service/posts_list.rs @@ -1,13 +1,13 @@ +use std::sync::Arc; + use axum::extract::Extension; use maud::html; -use blog::db::ConcurrentPostsStore; - -use crate::template; +use crate::{Context, template}; use super::response::Html; -pub(super) async fn handle(Extension(posts): Extension) -> Html { +pub(super) async fn handle(Extension(context): Extension>) -> Html { Html::new() .with_title_static("Articles") .with_crawler_permissive() @@ -23,7 +23,7 @@ pub(super) async fn handle(Extension(posts): Extension) -> "A collection of words I have written, against my better judgement." } ul .articles_list { - @for post in posts.read().await.iter_by_published().rev() { + @for post in context.posts().read().await.iter_by_published().rev() { li { h3 { a href={"/articles/" (post.id())} { (post.title()) } } @if let Some(subtitle) = post.subtitle() { diff --git a/src/bin/blog_server/service/rss.rs b/src/bin/blog_server/service/rss.rs index c2dab07..20a4ea8 100644 --- a/src/bin/blog_server/service/rss.rs +++ b/src/bin/blog_server/service/rss.rs @@ -5,24 +5,18 @@ use axum::{ extract::Extension, }; -use blog::{ - db::ConcurrentPostsStore, - time::unix_epoch, -}; +use blog::time::unix_epoch; -use crate::Config; +use crate::Context; use super::response::Rss; -pub(super) async fn handle( - Extension(config): Extension>, - Extension(posts): Extension, -) -> Rss { +pub(super) async fn handle(Extension(context): Extension>) -> Rss { let (rss_items, updated) = { - let guard = posts.read().await; + let guard = context.posts().read().await; let rss_items = guard.iter_by_published() - .take(config.rss.num_posts) + .take(context.config().rss.num_posts) .map(|post| { rss::ItemBuilder::default() .title(Some(post.title().to_owned())) @@ -32,8 +26,8 @@ pub(super) async fn handle( .build())) .link(Some(format!( "{}://{}/articles/{}", - config.self_ref.protocol, - config.self_ref.domain, + context.config().site.protocol, + context.config().site.domain, post.id() ))) .pub_date(Some(post.published().to_rfc2822())) @@ -48,12 +42,12 @@ pub(super) async fn handle( }; Rss(rss::ChannelBuilder::default() - .title(config.rss.title.clone()) + .title(context.config().rss.title.clone()) .link(format!( "{}://{}", - config.self_ref.protocol, config.self_ref.domain + context.config().site.protocol, context.config().site.domain )) - .ttl(Some(config.rss.ttl.to_string())) + .ttl(Some(context.config().rss.ttl.to_string())) .last_build_date(Some(updated.to_rfc2822())) .items(rss_items) .build() diff --git a/src/bin/blog_server/service/site.rs b/src/bin/blog_server/service/site.rs index 840419d..6055799 100644 --- a/src/bin/blog_server/service/site.rs +++ b/src/bin/blog_server/service/site.rs @@ -11,9 +11,7 @@ use tower::limit::ConcurrencyLimitLayer; use tower_http::trace::TraceLayer; use tracing::info; -use blog::db::ConcurrentPostsStore; - -use crate::Config; +use crate::Context; use super::{ atom, @@ -26,11 +24,7 @@ use super::{ static_content, }; -pub(crate) fn service( - config: Arc, - posts_store: ConcurrentPostsStore, -) -> Router -{ +pub(crate) fn service(context: Arc) -> Router { Router::new() .route("/", get(index::handle)) .route("/contact", get(contact::handle)) @@ -38,21 +32,20 @@ pub(crate) fn service( .route("/rss.xml", get(rss::handle)) .route("/atom.xml", get(atom::handle)) .route("/articles/:post_id", get(post::handle)) - .route("/robots.txt", static_content::file_service(&config.robots_path, None)) - .route("/favicon.ico", static_content::file_service(&config.favicon_dir.join("favicon.ico"), None)) - .route("/favicon-16x16.png", static_content::file_service(&config.favicon_dir.join("favicon-16x16.png"), None)) - .route("/favicon-32x32.png", static_content::file_service(&config.favicon_dir.join("favicon-32x32.png"), None)) - .route("/apple-touch-icon.png", static_content::file_service(&config.favicon_dir.join("apple-touch-icon.png"), None)) - .route("/android-chrome-192x192.png", static_content::file_service(&config.favicon_dir.join("android-chrome-192x192.png"), None)) - .route("/android-chrome-512x512.png", static_content::file_service(&config.favicon_dir.join("android-chrome-512x512.png"), None)) - .route("/site.webmanifest", static_content::file_service(&config.favicon_dir.join("site.webmanifest"), None)) - .nest("/static", static_content::dir_service(&config.static_dir)) - .nest("/article_media", static_content::dir_service(&config.post_media_dir)) + .route("/robots.txt", static_content::file_service(&context.config().content.robots_path, None)) + .route("/favicon.ico", static_content::file_service(&context.config().content.favicon_dir.join("favicon.ico"), None)) + .route("/favicon-16x16.png", static_content::file_service(&context.config().content.favicon_dir.join("favicon-16x16.png"), None)) + .route("/favicon-32x32.png", static_content::file_service(&context.config().content.favicon_dir.join("favicon-32x32.png"), None)) + .route("/apple-touch-icon.png", static_content::file_service(&context.config().content.favicon_dir.join("apple-touch-icon.png"), None)) + .route("/android-chrome-192x192.png", static_content::file_service(&context.config().content.favicon_dir.join("android-chrome-192x192.png"), None)) + .route("/android-chrome-512x512.png", static_content::file_service(&context.config().content.favicon_dir.join("android-chrome-512x512.png"), None)) + .route("/site.webmanifest", static_content::file_service(&context.config().content.favicon_dir.join("site.webmanifest"), None)) + .nest("/static", static_content::dir_service(&context.config().content.static_dir)) + .nest("/article_media", static_content::dir_service(&context.config().content.post_media_dir)) .fallback(handle_fallback.into_service()) - .layer(ConcurrencyLimitLayer::new(config.concurrency_limit)) + .layer(ConcurrencyLimitLayer::new(context.config().concurrency_limit)) .layer(TraceLayer::new_for_http()) - .layer(Extension(config)) - .layer(Extension(posts_store)) + .layer(Extension(context)) } async fn handle_fallback(uri: Uri) -> Error { diff --git a/src/lib/db.rs b/src/lib/db.rs index 3a57633..2672ea3 100644 --- a/src/lib/db.rs +++ b/src/lib/db.rs @@ -10,14 +10,13 @@ use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::post::{Post, Id}; -#[derive(Clone)] pub struct ConcurrentPostsStore { - inner: Arc>, + inner: RwLock, } impl ConcurrentPostsStore { pub fn new() -> Self { - Self { inner: Arc::new(RwLock::new(PostsStore::new())) } + Self { inner: RwLock::new(PostsStore::new()) } } pub async fn read(&self) -> RwLockReadGuard<'_, PostsStore> {