Skip to content

Commit

Permalink
loading from persistence at startup
Browse files Browse the repository at this point in the history
  • Loading branch information
babymotte committed Jun 30, 2022
1 parent 1a7a203 commit 23e6f3b
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 27 deletions.
2 changes: 2 additions & 0 deletions docker-compose-arm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ services:
environment:
- RUST_LOG=info
- WORTERBUCH_BIND_ADDRESS=0.0.0.0
- WORTERBUCH_DATA_DIR=/data
- WORTERBUCH_PERSISTENCE_INTERVAL=5
volumes:
- ./data:/data
logging:
Expand Down
2 changes: 2 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ services:
environment:
- RUST_LOG=info
- WORTERBUCH_BIND_ADDRESS=0.0.0.0
- WORTERBUCH_DATA_DIR=/data
- WORTERBUCH_PERSISTENCE_INTERVAL=5
volumes:
- ./data:/data
logging:
Expand Down
7 changes: 3 additions & 4 deletions worterbuch/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,14 @@ impl Config {

#[cfg(feature = "graphql")]
if let Ok(val) = env::var("WORTERBUCH_GRAPHQL_PORT") {
self.graphql_port = val.parse()?;
self.graphql_port = val.parse().as_port()?;
}

if let Ok(val) = env::var("WORTERBUCH_BIND_ADDRESS") {
self.bind_addr = val.parse()?;
}

if let Ok(val) = env::var("WORTERBUCH_PERSISTENT_DATA") {
if let Ok(val) = env::var("WORTERBUCH_USE_PERSISTENCE") {
self.persistent_data = val.to_lowercase() == "true";
}

Expand Down Expand Up @@ -111,8 +111,7 @@ impl Default for Config {
#[cfg(feature = "web")]
key_path: None,
persistent_data: false,
// TODO increase default persistence period
persistence_interval: Duration::from_secs(5),
persistence_interval: Duration::from_secs(30),
data_dir: "./data".into(),
}
}
Expand Down
16 changes: 10 additions & 6 deletions worterbuch/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ async fn main() -> Result<()> {
async fn run(msg: &str) -> Result<()> {
let config = Config::new()?;
let config_pers = config.clone();
let config_ppers = config.clone();

App::new("worterbuch")
.version(env!("CARGO_PKG_VERSION"))
Expand All @@ -49,11 +48,16 @@ async fn run(msg: &str) -> Result<()> {
log::debug!("Wildcard: {}", config.wildcard);
log::debug!("Multi-Wildcard: {}", config.multi_wildcard);

// let restore_from_persistence = config.persistent_data;
let restore_from_persistence = config.persistent_data;

let worterbuch = Arc::new(RwLock::new(Worterbuch::with_config(config.clone())));
let worterbuch = if restore_from_persistence {
persistence::load(config.clone()).await?
} else {
Worterbuch::with_config(config.clone())
};

let worterbuch = Arc::new(RwLock::new(worterbuch));
let worterbuch_pers = worterbuch.clone();
let worterbuch_ppers = worterbuch.clone();

spawn(persistence::periodic(worterbuch_pers, config_pers));

Expand All @@ -74,10 +78,10 @@ async fn run(msg: &str) -> Result<()> {

#[cfg(not(feature = "docker"))]
{
repl(worterbuch).await;
repl(worterbuch.clone()).await;
}

persistence::once(worterbuch_ppers, config_ppers).await?;
persistence::once(worterbuch.clone(), config.clone()).await?;

Ok(())
}
76 changes: 66 additions & 10 deletions worterbuch/src/persistence.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{config::Config, worterbuch::Worterbuch};
use anyhow::Result;
use sha2::{Digest, Sha256};
use std::{path::PathBuf, sync::Arc};
use tokio::{
fs::{self, File},
Expand All @@ -20,6 +21,70 @@ pub(crate) async fn periodic(worterbuch: Arc<RwLock<Worterbuch>>, config: Config
pub(crate) async fn once(worterbuch: Arc<RwLock<Worterbuch>>, config: Config) -> Result<()> {
let wb = worterbuch.read().await;

let (json_temp_path, json_path, sha_temp_path, sha_path) = file_paths(&config);

let json = wb.export()?.to_string();

let mut hasher = Sha256::new();
hasher.update(&json);
let result = hasher.finalize();
let sha = hex::encode(&result);

let mut file = File::create(&json_temp_path).await?;
file.write_all(json.as_bytes()).await?;

let mut file = File::create(&sha_temp_path).await?;
file.write_all(sha.as_bytes()).await?;

fs::copy(&json_temp_path, &json_path).await?;
fs::copy(&sha_temp_path, &sha_path).await?;

Ok(())
}

pub(crate) async fn load(config: Config) -> Result<Worterbuch> {
log::info!("Restoring Wörterbuch form persistence …");

let (json_temp_path, json_path, sha_temp_path, sha_path) = file_paths(&config);

if !json_path.exists() && !json_temp_path.exists() {
log::info!("No persistence file found, starting empty instance.");
return Ok(Worterbuch::with_config(config));
}

match try_load(&json_path, &sha_path, &config).await {
Ok(worterbuch) => {
log::info!("Wörterbuch successfully restored form persistence.");
Ok(worterbuch)
}
Err(e) => {
log::warn!("Default persistence file could not be loaded: {e}");
log::info!("Restoring Wörterbuch form backup file …");
let worterbuch = try_load(&json_temp_path, &sha_temp_path, &config).await?;
log::info!("Wörterbuch successfully restored form backup file.");
Ok(worterbuch)
}
}
}

async fn try_load(json_path: &PathBuf, sha_path: &PathBuf, config: &Config) -> Result<Worterbuch> {
let json = fs::read_to_string(json_path).await?;
let sha = fs::read_to_string(sha_path).await?;

let mut hasher = Sha256::new();
hasher.update(&json);
let result = hasher.finalize();
let loaded_sha = hex::encode(&result);

if sha != loaded_sha {
Err(anyhow::Error::msg("checksums did not match"))
} else {
let worterbuch = Worterbuch::from_json(&json, config.to_owned())?;
Ok(worterbuch)
}
}

fn file_paths(config: &Config) -> (PathBuf, PathBuf, PathBuf, PathBuf) {
let dir = PathBuf::from(&config.data_dir);

let mut json_temp_path = dir.clone();
Expand All @@ -31,14 +96,5 @@ pub(crate) async fn once(worterbuch: Arc<RwLock<Worterbuch>>, config: Config) ->
let mut sha_path = dir.clone();
sha_path.push(".store.sha");

let mut file = File::create(&json_temp_path).await?;
let sha = wb.export_to_file(&mut file).await?;
let sha = hex::encode(&sha);
let mut file = File::create(&sha_temp_path).await?;
file.write_all(sha.as_bytes()).await?;

fs::copy(&json_temp_path, &json_path).await?;
fs::copy(&sha_temp_path, &sha_path).await?;

Ok(())
(json_temp_path, json_path, sha_temp_path, sha_path)
}
18 changes: 11 additions & 7 deletions worterbuch/src/worterbuch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ use libworterbuch::{
};
use serde::{Deserialize, Serialize};
use serde_json::{from_str, to_value, Value};
use sha2::{Digest, Sha256};
use std::fmt::Display;
use tokio::{
fs::File,
Expand Down Expand Up @@ -45,6 +44,15 @@ impl Worterbuch {
}
}

pub fn from_json(json: &str, config: Config) -> WorterbuchResult<Worterbuch> {
let store: Store = from_str(json).context(|| format!("Error parsing JSON"))?;
Ok(Worterbuch {
config,
store,
..Default::default()
})
}

pub fn get<'a>(&self, key: impl AsRef<str>) -> WorterbuchResult<(String, String)> {
let path: Vec<&str> = key.as_ref().split(self.config.separator).collect();

Expand Down Expand Up @@ -193,20 +201,16 @@ impl Worterbuch {
Ok(imported_values)
}

pub async fn export_to_file(&self, file: &mut File) -> WorterbuchResult<Vec<u8>> {
pub async fn export_to_file(&self, file: &mut File) -> WorterbuchResult<()> {
log::debug!("Exporting to {file:?} …");
let json = self.export()?.to_string();
let json_bytes = json.as_bytes();

let mut hasher = Sha256::new();
hasher.update(b"hello world");
let result = hasher.finalize();

file.write_all(json_bytes)
.await
.context(|| format!("Error writing to file {file:?}"))?;
log::debug!("Done.");
Ok(result.as_slice().to_owned())
Ok(())
}

pub async fn import_from_file(&mut self, path: &Path) -> WorterbuchResult<()> {
Expand Down

0 comments on commit 23e6f3b

Please sign in to comment.