extern crate bincode;
extern crate chrono;
extern crate fern;
#[macro_use]
extern crate log;
extern crate rayon;
extern crate remove_dir_all;
extern crate ron;
#[macro_use]
extern crate serde;
extern crate uuid;
use std::collections::HashMap;
use std::error::Error;
use std::path::PathBuf;
use std::sync::RwLock;
pub mod config;
pub mod core;
mod ephemeral;
pub mod fetch;
pub mod maintenance;
mod persistence;
pub mod prelude;
mod system;
use self::ephemeral::Data;
use crate::{config::Config, fetch::Fetchable};
use mycelium_command::node::Node;
type NodeId = DbId;
type PageId = DbId;
pub type DbId = [u8; 16];
pub(crate) const RESERVED: &str = "db_sys";
pub struct Db {
config: Config,
primary: Data,
working_dir: std::path::PathBuf,
system: system::System,
}
impl Db {
pub fn init(config: Option<Config>) -> std::io::Result<Db> {
if config.is_some() {
return Db::with_config(config.unwrap());
}
Db::new()
}
#[allow(clippy::new_without_default)]
pub fn new() -> std::io::Result<Db> {
let config = Config::default();
let path = PathBuf::from(config.get_data_dir().unwrap());
let system: crate::system::System =
crate::system::System::fetch(RESERVED, system::FILE_NAME, &path)
.expect("Failed to fetch system info.");
system.save_x(RESERVED, system::FILE_NAME, &path)?;
Ok(Db {
config,
primary: RwLock::new(HashMap::new()),
working_dir: std::path::PathBuf::new(),
system,
})
}
pub fn add(&self, item: &[u8], tag: &str) -> Result<[u8; 16], Box<dyn std::error::Error>> {
core::add(self, item, tag)
}
fn empty(&self) -> Db {
Db {
config: self.config.clone(),
primary: RwLock::new(HashMap::new()),
working_dir: self.working_dir.to_path_buf(),
system: self.system.clone(),
}
}
pub fn get(&self, tag: &str, id: DbId) -> Option<Node> {
match self.primary.read().expect("Lock Primary.").get(tag) {
Some(t) => t.get_node(id),
None => None,
}
}
pub fn get_reserved_sys(&self) -> PathBuf {
let mut reserved = self.working_dir.to_path_buf();
reserved.push(RESERVED);
reserved
}
pub fn get_tag(&self, tag: &str) -> Option<Vec<Node>> {
match self
.primary
.read()
.expect("Lock Primary core::get_tag()")
.get(tag)
{
Some(t) => Some(t.all_container_nodes()),
None => None,
}
}
pub fn get_tag_hashmap(&self, tag: &str) -> Option<HashMap<DbId, Node>> {
match self
.primary
.read()
.expect("Lock core::get_tag_hashmap()")
.get(tag)
{
Some(t) => Some(t.all_container_nodes_map()),
None => None,
}
}
pub fn get_working_dir(&self) -> PathBuf {
self.working_dir.to_path_buf()
}
pub fn get_system_id(&self) -> DbId {
self.system.db_id
}
pub fn list_tags(&self) -> Vec<String> {
let mut tags = vec![];
let tag_iter = self.primary.read().expect("Lock stew primary.");
for tag in tag_iter.keys() {
if tag == RESERVED {
continue;
}
tags.push(tag.to_string())
}
tags
}
pub fn load_tag(&self, tag: &str) -> std::io::Result<()> {
crate::core::validate_tag(self, tag).expect("Invalid container tag");
let mut primary = self.primary.write().expect("core::load_tag()");
let cont = primary.get_mut(tag);
if cont.is_some() {
let cont = cont.unwrap();
let cont = cont.load().with_page_item_idex();
primary.insert(tag.to_string(), cont);
}
Ok(())
}
pub fn node_history(
&mut self,
tag: &str,
node_id: DbId,
limit: usize,
) -> Option<(Option<Node>, Vec<Node>)> {
let mut nodes: Vec<Node> = vec![];
let mut primary = self.primary.write().expect("core::node_history lock()");
let cont = primary.get_mut(tag);
let mut current_node = None;
if cont.is_some() {
let cont = cont.unwrap();
if cont.is_loaded() {
current_node = cont.get_node(node_id);
let pages = cont.history.get_node_pages(node_id);
for page in pages {
match cont.history.load_page(page) {
Ok(_) => (),
Err(e) => panic!("Error loading page: {:?}", e),
};
}
let mut node_hist = cont.history.get_nodes(node_id, limit);
nodes.append(&mut node_hist);
} else {
let current_node = cont.get_node(node_id);
let node_hist = cont.history.get_nodes(node_id, limit);
return Some((current_node, node_hist));
}
}
Some((current_node, nodes))
}
#[allow(clippy::match_wild_err_arm)]
fn with_config(config: Config) -> std::io::Result<Db> {
let path = match config.get_data_dir() {
Some(_) => PathBuf::from(config.get_data_dir().unwrap()),
None => match std::env::current_dir() {
Ok(p) => p,
Err(_) => panic!("Couldn't get current executable directory."),
},
};
let sys_path = PathBuf::from(config.get_data_dir().unwrap());
let system: crate::system::System =
system::System::fetch(RESERVED, system::FILE_NAME, &sys_path)?;
system.save_x(RESERVED, system::FILE_NAME, &sys_path)?;
let max_page_size = config.get_max_page_size();
let primary = match core::load_tag_containers(&path, max_page_size) {
Ok(primary) => primary,
Err(e) => {
error!("Error loading tags: {}", e.description());
return Err(e);
}
};
let db = Db {
config,
primary,
working_dir: path,
system,
};
validate_probable_success(&db);
Ok(db)
}
pub fn refresh(&self, tag: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut clear = false;
match self.primary.write().expect("Lock primary").get_mut(tag) {
Some(cont) => {
let cont = cont;
cont.clear().expect("Failed to clear tag on refresh.");
clear = true;
}
None => (),
}
if clear {
self.load_tag(tag)
.expect("Failed to reload tag on refresh.");
}
Ok(())
}
pub fn save_all(&self) -> Result<(), std::io::Error> {
crate::core::save_all(self)
}
pub fn update_node(
&self,
node_id: DbId,
item: &[u8],
tag: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut primary = self.primary.write().expect("core::update_node() lock()");
let cont = primary.get_mut(tag).unwrap();
let hist_node = cont.update_node(node_id, item)?;
cont.update_node_history(hist_node)
}
}
fn file_permission_check(path: std::path::PathBuf) -> Result<(), std::io::Error> {
use std::fs::File;
use std::io::Write;
use uuid::Uuid;
if !path.exists() {
std::fs::create_dir_all(path.to_path_buf())?;
}
let mut test = path.to_path_buf();
let path = format!("{}/{}", RESERVED, Uuid::new_v4());
test.push(&path);
std::fs::create_dir_all(&test)?;
test.push("file_test.txt");
let mut f = File::create(&test)?;
f.write_all(b"Beef, carrot, peas, and onion.")?;
match f.sync_all() {
Ok(_) => (),
Err(e) => {
error!("Issues writing files to disk in permission check.");
return Err(e);
}
}
use remove_dir_all::*;
remove_dir_all(test.parent().unwrap())
}
fn log_report() {
info!("Info level logging up");
trace!("trace level logging up");
debug!("debug level logging up");
error!("error level logging up");
warn!("warning level logging up");
}
pub fn setup_logging(verbosity: Option<usize>) {
#[allow(unused_assignments)]
let mut verbos = 3;
if verbosity.is_some() {
verbos = verbosity.unwrap()
} else {
verbos = if cfg!(debug_assertions) { 3 } else { 0 };
}
match crate::config::setup_logging(verbos) {
Ok(_) => {
log_report();
}
Err(e) => panic!("Couldn't init logging: {:?}", e),
}
}
fn validate_probable_success(db: &Db) {
match file_permission_check(db.working_dir.to_path_buf()) {
Ok(_) => (),
Err(e) => panic!("File permission check failure: {}", e.description()),
}
}
#[cfg(test)]
mod tests;