forked from slonk/bingus-blog
make cache optional
This commit is contained in:
parent
58c8021864
commit
18385d3e57
4 changed files with 105 additions and 62 deletions
10
README.md
10
README.md
|
@ -38,11 +38,15 @@ port = 3000 # port to listen on
|
|||
title = "bingus-blog" # title 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
|
||||
#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
|
||||
|
||||
[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.themes_dir = "themes" # directory to include themes from
|
||||
syntect.theme = "Catppuccin Mocha" # theme file name (without `.tmTheme`)
|
||||
|
|
|
@ -10,13 +10,14 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|||
use tracing::{error, info};
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[serde(default)]
|
||||
pub struct SyntectConfig {
|
||||
pub load_defaults: bool,
|
||||
pub themes_dir: Option<PathBuf>,
|
||||
pub theme: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||
#[serde(default)]
|
||||
pub struct RenderConfig {
|
||||
pub syntect: SyntectConfig,
|
||||
|
@ -30,6 +31,13 @@ pub struct PrecompressionConfig {
|
|||
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)]
|
||||
#[serde(default)]
|
||||
pub struct Config {
|
||||
|
@ -41,7 +49,7 @@ pub struct Config {
|
|||
pub render: RenderConfig,
|
||||
#[cfg(feature = "precompression")]
|
||||
pub precompression: PrecompressionConfig,
|
||||
pub cache_file: Option<PathBuf>,
|
||||
pub cache: CacheConfig,
|
||||
pub markdown_access: bool,
|
||||
}
|
||||
|
||||
|
@ -56,20 +64,18 @@ impl Default for Config {
|
|||
posts_dir: "posts".into(),
|
||||
#[cfg(feature = "precompression")]
|
||||
precompression: Default::default(),
|
||||
cache_file: None,
|
||||
cache: Default::default(),
|
||||
markdown_access: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for RenderConfig {
|
||||
impl Default for SyntectConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
syntect: SyntectConfig {
|
||||
load_defaults: false,
|
||||
themes_dir: Some("themes".into()),
|
||||
theme: Some("Catppuccin Mocha".into()),
|
||||
},
|
||||
load_defaults: false,
|
||||
themes_dir: Some("themes".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> {
|
||||
let config_file = env::var(format!("{}_CONFIG", env!("CARGO_BIN_NAME")))
|
||||
.unwrap_or(String::from("config.toml"));
|
||||
|
|
72
src/main.rs
72
src/main.rs
|
@ -189,37 +189,45 @@ async fn main() -> eyre::Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
let posts = if let Some(path) = config.cache_file.as_ref()
|
||||
&& tokio::fs::try_exists(&path)
|
||||
.await
|
||||
.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)
|
||||
let posts = if config.cache.enable {
|
||||
if let Some(path) = config.cache.persistence.as_ref()
|
||||
&& tokio::fs::try_exists(&path)
|
||||
.await
|
||||
.context("failed to open cache file")?;
|
||||
let mut serialized = Vec::with_capacity(4096);
|
||||
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(
|
||||
.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
|
||||
.context("failed to open cache file")?;
|
||||
let mut serialized = Vec::with_capacity(4096);
|
||||
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.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())
|
||||
}
|
||||
Default::default(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
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");
|
||||
AppState::clone(state.as_ref())
|
||||
});
|
||||
if let Some(path) = config.cache_file.as_ref() {
|
||||
let cache = posts.into_cache();
|
||||
if config.cache.enable
|
||||
&& 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 cache_file = tokio::fs::File::create(path)
|
||||
.await
|
||||
|
|
|
@ -75,7 +75,7 @@ pub enum RenderStats {
|
|||
#[derive(Clone)]
|
||||
pub struct PostManager {
|
||||
dir: PathBuf,
|
||||
cache: Cache,
|
||||
cache: Option<Cache>,
|
||||
config: RenderConfig,
|
||||
}
|
||||
|
||||
|
@ -83,13 +83,17 @@ impl PostManager {
|
|||
pub fn new(dir: PathBuf, config: RenderConfig) -> PostManager {
|
||||
PostManager {
|
||||
dir,
|
||||
cache: Default::default(),
|
||||
cache: None,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
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(
|
||||
|
@ -125,19 +129,21 @@ impl PostManager {
|
|||
.render()?;
|
||||
let rendering = before_render.elapsed();
|
||||
|
||||
self.cache
|
||||
.insert(
|
||||
name.to_string(),
|
||||
metadata.clone(),
|
||||
modified
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
post.clone(),
|
||||
&self.config,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("failed to insert {:?} into cache", err.0));
|
||||
if let Some(cache) = self.cache.as_ref() {
|
||||
cache
|
||||
.insert(
|
||||
name.to_string(),
|
||||
metadata.clone(),
|
||||
modified
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs(),
|
||||
post.clone(),
|
||||
&self.config,
|
||||
)
|
||||
.await
|
||||
.unwrap_or_else(|err| warn!("failed to insert {:?} into cache", err.0))
|
||||
};
|
||||
|
||||
Ok((metadata, post, (parsing, rendering)))
|
||||
}
|
||||
|
@ -166,7 +172,9 @@ impl PostManager {
|
|||
.to_string_lossy()
|
||||
.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)
|
||||
} else if let Ok((metadata, ..)) = self.parse_and_render(name, path).await {
|
||||
posts.push(metadata);
|
||||
|
@ -194,7 +202,9 @@ impl PostManager {
|
|||
Ok(value) => value,
|
||||
Err(err) => match err.kind() {
|
||||
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::IoError(err)),
|
||||
|
@ -206,7 +216,9 @@ impl PostManager {
|
|||
.unwrap()
|
||||
.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((
|
||||
hit.metadata,
|
||||
hit.rendered,
|
||||
|
@ -222,7 +234,7 @@ impl PostManager {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn into_cache(self) -> Cache {
|
||||
pub fn into_cache(self) -> Option<Cache> {
|
||||
self.cache
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue