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
|
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`)
|
||||||
|
|
|
@ -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"));
|
||||||
|
|
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()
|
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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue