Flat file database editor and browser with web interface
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
rocket-inv/src/store/mod.rs

136 lines
3.8 KiB

use crate::store::model::Model;
use indexmap::map::IndexMap;
use serde::Serialize;
use serde_json::Value;
use std::collections::HashMap;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
pub mod form;
pub mod model;
/// Store instance
#[derive(Debug)]
pub struct Store {
path: PathBuf,
pub model: Model,
pub data: Cards,
pub index: Indexes,
}
/// Indexes loaded from the indexes file
#[derive(Serialize, Deserialize, Debug, Default)]
pub struct Indexes {
pub free_enums: HashMap<String, Vec<String>>,
pub free_tags: HashMap<String, Vec<String>>,
}
/// Struct loaded from the repositroy config file
#[derive(Deserialize, Debug)]
struct RepositoryConfig {
pub model: Model,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Cards {
#[serde(default)]
pub cards: IndexMap<usize, Value>,
#[serde(default)]
pub counter: usize,
}
const REPO_CONFIG_FILE: &'static str = "repository.yaml";
const REPO_DATA_FILE: &'static str = "data.json";
const REPO_INDEX_FILE: &'static str = "index.json";
impl Store {
pub fn new(path: impl AsRef<Path>) -> Self {
let file = load_file(path.as_ref().join(REPO_CONFIG_FILE));
let repository_config: RepositoryConfig =
serde_yaml::from_str(&file).expect("Error parsing repository config file.");
let items = load_file_or(path.as_ref().join(REPO_DATA_FILE), "{}");
let indexes = load_file_or(path.as_ref().join(REPO_INDEX_FILE), "{}");
Store {
path: path.as_ref().into(),
model: repository_config.model,
data: serde_json::from_str(&items).expect("Error parsing data file."),
index: serde_json::from_str(&indexes).unwrap_or_default(),
}
}
pub fn persist(&mut self) {
let mut file = OpenOptions::new()
.write(true)
.create(true)
.truncate(true)
.open(self.path.join(REPO_DATA_FILE))
.expect("Error opening data file for writing.");
let serialized = serde_json::to_string_pretty(&self.data).expect("Error serialize.");
file.write(serialized.as_bytes())
.expect("Error write data file");
}
pub fn add_card(&mut self, values: IndexMap<String, Value>) -> usize {
let packed = serde_json::to_value(values).expect("Error serialize");
if let p @ Value::Object(_) = packed {
let id = self.data.counter;
self.data.counter += 1;
self.data.cards.insert(id, p);
self.persist();
id
} else {
panic!("Packing did not produce a map.");
}
}
pub fn update_card(&mut self, id: usize, values: IndexMap<String, Value>) {
let packed = serde_json::to_value(values).expect("Error serialize");
if !self.data.cards.contains_key(&id) {
panic!("No such card.");
}
if let p @ Value::Object(_) = packed {
self.data.cards.insert(id, p);
} else {
panic!("Packing did not produce a map.");
}
self.persist()
}
pub fn delete_card(&mut self, id: usize) {
self.data.cards.remove(&id);
self.persist()
}
}
fn load_file(path: impl AsRef<Path>) -> String {
let mut file =
File::open(&path).expect(&format!("Error opening file {}", path.as_ref().display()));
let mut buf = String::new();
file.read_to_string(&mut buf)
.expect(&format!("Error reading file {}", path.as_ref().display()));
buf
}
fn load_file_or(file: impl AsRef<Path>, def: impl Into<String>) -> String {
let mut file = match File::open(file) {
Ok(file) => file,
Err(_) => return def.into(),
};
let mut buf = String::new();
if file.read_to_string(&mut buf).is_err() {
return def.into();
}
buf
}