make cache optional

This commit is contained in:
slonkazoid 2024-04-20 20:59:00 +03:00
parent 58c8021864
commit 18385d3e57
Signed by: slonk
SSH key fingerprint: SHA256:tbZfJX4IOvZ0LGWOWu5Ijo8jfMPi78TU7x1VoEeCIjM
4 changed files with 105 additions and 62 deletions

View file

@ -38,11 +38,15 @@ port = 3000 # port to listen on
title = "bingus-blog" # title of the website title = "bingus-blog" # title of the website
description = "blazingly fast markdown blog software written in rust memory safe" # description of the website description = "blazingly fast markdown blog software written in rust memory safe" # description of the website
posts_dir = "posts" # where posts are stored posts_dir = "posts" # where posts are stored
#cache_file = "..." # file to serialize the cache into on shutdown, and
# to deserialize from on startup. uncomment to enable
markdown_access = true # allow users to see the raw markdown of a post markdown_access = true # allow users to see the raw markdown of a post
[render] # rendering-specific settings [cache] # cache settings
enable = true # save metadata and rendered posts into RAM
# highly recommended, only turn off if asolutely necessary
#persistence = "..." # file to save the cache to on shutdown, and
# to load from on startup. uncomment to enable
[render] # post rendering settings
syntect.load_defaults = false # include default syntect themes syntect.load_defaults = false # include default syntect themes
syntect.themes_dir = "themes" # directory to include themes from syntect.themes_dir = "themes" # directory to include themes from
syntect.theme = "Catppuccin Mocha" # theme file name (without `.tmTheme`) syntect.theme = "Catppuccin Mocha" # theme file name (without `.tmTheme`)

View file

@ -10,13 +10,14 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tracing::{error, info}; use tracing::{error, info};
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
#[serde(default)]
pub struct SyntectConfig { pub struct SyntectConfig {
pub load_defaults: bool, pub load_defaults: bool,
pub themes_dir: Option<PathBuf>, pub themes_dir: Option<PathBuf>,
pub theme: Option<String>, pub theme: Option<String>,
} }
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
#[serde(default)] #[serde(default)]
pub struct RenderConfig { pub struct RenderConfig {
pub syntect: SyntectConfig, pub syntect: SyntectConfig,
@ -30,6 +31,13 @@ pub struct PrecompressionConfig {
pub watch: bool, pub watch: bool,
} }
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(default)]
pub struct CacheConfig {
pub enable: bool,
pub persistence: Option<PathBuf>,
}
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(default)] #[serde(default)]
pub struct Config { pub struct Config {
@ -41,7 +49,7 @@ pub struct Config {
pub render: RenderConfig, pub render: RenderConfig,
#[cfg(feature = "precompression")] #[cfg(feature = "precompression")]
pub precompression: PrecompressionConfig, pub precompression: PrecompressionConfig,
pub cache_file: Option<PathBuf>, pub cache: CacheConfig,
pub markdown_access: bool, pub markdown_access: bool,
} }
@ -56,20 +64,18 @@ impl Default for Config {
posts_dir: "posts".into(), posts_dir: "posts".into(),
#[cfg(feature = "precompression")] #[cfg(feature = "precompression")]
precompression: Default::default(), precompression: Default::default(),
cache_file: None, cache: Default::default(),
markdown_access: true, markdown_access: true,
} }
} }
} }
impl Default for RenderConfig { impl Default for SyntectConfig {
fn default() -> Self { fn default() -> Self {
Self { Self {
syntect: SyntectConfig { load_defaults: false,
load_defaults: false, themes_dir: Some("themes".into()),
themes_dir: Some("themes".into()), theme: Some("Catppuccin Mocha".into()),
theme: Some("Catppuccin Mocha".into()),
},
} }
} }
} }
@ -84,6 +90,15 @@ impl Default for PrecompressionConfig {
} }
} }
impl Default for CacheConfig {
fn default() -> Self {
Self {
enable: true,
persistence: None,
}
}
}
pub async fn load() -> Result<Config> { pub async fn load() -> Result<Config> {
let config_file = env::var(format!("{}_CONFIG", env!("CARGO_BIN_NAME"))) let config_file = env::var(format!("{}_CONFIG", env!("CARGO_BIN_NAME")))
.unwrap_or(String::from("config.toml")); .unwrap_or(String::from("config.toml"));

View file

@ -189,37 +189,45 @@ async fn main() -> eyre::Result<()> {
} }
} }
let posts = if let Some(path) = config.cache_file.as_ref() let posts = if config.cache.enable {
&& tokio::fs::try_exists(&path) if let Some(path) = config.cache.persistence.as_ref()
.await && tokio::fs::try_exists(&path)
.with_context(|| format!("failed to check if {} exists", path.display()))?
{
info!("loading cache from file");
let load_cache = async {
let mut cache_file = tokio::fs::File::open(&path)
.await .await
.context("failed to open cache file")?; .with_context(|| format!("failed to check if {} exists", path.display()))?
let mut serialized = Vec::with_capacity(4096); {
cache_file info!("loading cache from file");
.read_to_end(&mut serialized) let load_cache = async {
.await let mut cache_file = tokio::fs::File::open(&path)
.context("failed to read cache file")?; .await
let cache = .context("failed to open cache file")?;
bitcode::deserialize(serialized.as_slice()).context("failed to parse cache")?; let mut serialized = Vec::with_capacity(4096);
Ok::<PostManager, color_eyre::Report>(PostManager::new_with_cache( cache_file
.read_to_end(&mut serialized)
.await
.context("failed to read cache file")?;
let cache =
bitcode::deserialize(serialized.as_slice()).context("failed to parse cache")?;
Ok::<PostManager, color_eyre::Report>(PostManager::new_with_cache(
config.posts_dir.clone(),
config.render.clone(),
cache,
))
}
.await;
match load_cache {
Ok(posts) => posts,
Err(err) => {
error!("failed to load cache: {}", err);
info!("using empty cache");
PostManager::new(config.posts_dir.clone(), config.render.clone())
}
}
} else {
PostManager::new_with_cache(
config.posts_dir.clone(), config.posts_dir.clone(),
config.render.clone(), config.render.clone(),
cache, Default::default(),
)) )
}
.await;
match load_cache {
Ok(posts) => posts,
Err(err) => {
error!("failed to load cache: {}", err);
info!("using empty cache");
PostManager::new(config.posts_dir.clone(), config.render.clone())
}
} }
} else { } else {
PostManager::new(config.posts_dir.clone(), config.render.clone()) PostManager::new(config.posts_dir.clone(), config.render.clone())
@ -321,8 +329,12 @@ async fn main() -> eyre::Result<()> {
warn!("couldn't unwrap Arc over AppState, more than one strong reference exists for Arc. cloning instead"); warn!("couldn't unwrap Arc over AppState, more than one strong reference exists for Arc. cloning instead");
AppState::clone(state.as_ref()) AppState::clone(state.as_ref())
}); });
if let Some(path) = config.cache_file.as_ref() { if config.cache.enable
let cache = posts.into_cache(); && let Some(path) = config.cache.persistence.as_ref()
{
let cache = posts
.into_cache()
.unwrap_or_else(|| unreachable!("cache should always exist in this state"));
let mut serialized = bitcode::serialize(&cache).context("failed to serialize cache")?; let mut serialized = bitcode::serialize(&cache).context("failed to serialize cache")?;
let mut cache_file = tokio::fs::File::create(path) let mut cache_file = tokio::fs::File::create(path)
.await .await

View file

@ -75,7 +75,7 @@ pub enum RenderStats {
#[derive(Clone)] #[derive(Clone)]
pub struct PostManager { pub struct PostManager {
dir: PathBuf, dir: PathBuf,
cache: Cache, cache: Option<Cache>,
config: RenderConfig, config: RenderConfig,
} }
@ -83,13 +83,17 @@ impl PostManager {
pub fn new(dir: PathBuf, config: RenderConfig) -> PostManager { pub fn new(dir: PathBuf, config: RenderConfig) -> PostManager {
PostManager { PostManager {
dir, dir,
cache: Default::default(), cache: None,
config, config,
} }
} }
pub fn new_with_cache(dir: PathBuf, config: RenderConfig, cache: Cache) -> PostManager { pub fn new_with_cache(dir: PathBuf, config: RenderConfig, cache: Cache) -> PostManager {
PostManager { dir, cache, config } PostManager {
dir,
cache: Some(cache),
config,
}
} }
async fn parse_and_render( async fn parse_and_render(
@ -125,19 +129,21 @@ impl PostManager {
.render()?; .render()?;
let rendering = before_render.elapsed(); let rendering = before_render.elapsed();
self.cache if let Some(cache) = self.cache.as_ref() {
.insert( cache
name.to_string(), .insert(
metadata.clone(), name.to_string(),
modified metadata.clone(),
.duration_since(SystemTime::UNIX_EPOCH) modified
.unwrap() .duration_since(SystemTime::UNIX_EPOCH)
.as_secs(), .unwrap()
post.clone(), .as_secs(),
&self.config, post.clone(),
) &self.config,
.await )
.unwrap_or_else(|err| warn!("failed to insert {:?} into cache", err.0)); .await
.unwrap_or_else(|err| warn!("failed to insert {:?} into cache", err.0))
};
Ok((metadata, post, (parsing, rendering))) Ok((metadata, post, (parsing, rendering)))
} }
@ -166,7 +172,9 @@ impl PostManager {
.to_string_lossy() .to_string_lossy()
.to_string(); .to_string();
if let Some(hit) = self.cache.lookup_metadata(&name, mtime).await { if let Some(cache) = self.cache.as_ref()
&& let Some(hit) = cache.lookup_metadata(&name, mtime).await
{
posts.push(hit) posts.push(hit)
} else if let Ok((metadata, ..)) = self.parse_and_render(name, path).await { } else if let Ok((metadata, ..)) = self.parse_and_render(name, path).await {
posts.push(metadata); posts.push(metadata);
@ -194,7 +202,9 @@ impl PostManager {
Ok(value) => value, Ok(value) => value,
Err(err) => match err.kind() { Err(err) => match err.kind() {
io::ErrorKind::NotFound => { io::ErrorKind::NotFound => {
self.cache.remove(name).await; if let Some(cache) = self.cache.as_ref() {
cache.remove(name).await;
}
return Err(PostError::NotFound(name.to_string())); return Err(PostError::NotFound(name.to_string()));
} }
_ => return Err(PostError::IoError(err)), _ => return Err(PostError::IoError(err)),
@ -206,7 +216,9 @@ impl PostManager {
.unwrap() .unwrap()
.as_secs(); .as_secs();
if let Some(hit) = self.cache.lookup(name, mtime, &self.config).await { if let Some(cache) = self.cache.as_ref()
&& let Some(hit) = cache.lookup(name, mtime, &self.config).await
{
Ok(( Ok((
hit.metadata, hit.metadata,
hit.rendered, hit.rendered,
@ -222,7 +234,7 @@ impl PostManager {
} }
} }
pub fn into_cache(self) -> Cache { pub fn into_cache(self) -> Option<Cache> {
self.cache self.cache
} }
} }