use crate::config::Config;
use crate::error::DevbrainError;
use crate::models::Entry;
use fs2::FileExt;
use serde::{Deserialize, Serialize};
use std::fs::{self, OpenOptions};
use std::io::{Read, Seek, SeekFrom, Write};
pub const DB_VERSION: &str = "1.0";
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Database {
#[serde(default = "default_db_version")]
pub version: String,
pub entries: Vec<Entry>,
}
fn default_db_version() -> String {
DB_VERSION.to_string()
}
pub fn init_db(config: &Config) -> Result<(), DevbrainError> {
let db_path = &config.db_path;
if let Some(parent) = db_path.parent() {
fs::create_dir_all(parent).map_err(|error| DevbrainError::IoError(error.to_string()))?;
}
if !db_path.exists() {
let db = Database {
version: default_db_version(),
entries: Vec::new(),
};
save_db(config, &db)?;
}
Ok(())
}
pub fn load_db(config: &Config) -> Result<Database, DevbrainError> {
let db_path = &config.db_path;
if !db_path.exists() {
init_db(config)?;
}
let mut file = OpenOptions::new()
.read(true)
.open(db_path)
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
file.lock_shared()
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
let mut contents = String::new();
let read_result = file
.read_to_string(&mut contents)
.map_err(|error| DevbrainError::IoError(error.to_string()));
let unlock_result = file
.unlock()
.map_err(|error| DevbrainError::IoError(error.to_string()));
read_result?;
unlock_result?;
let db = serde_json::from_str(&contents)
.map_err(|error| DevbrainError::ParseError(error.to_string()))?;
Ok(db)
}
pub fn save_db(config: &Config, db: &Database) -> Result<(), DevbrainError> {
let db_path = &config.db_path;
let json = serde_json::to_string_pretty(db)
.map_err(|error| DevbrainError::ParseError(error.to_string()))?;
let mut file = OpenOptions::new()
.create(true)
.read(true)
.write(true)
.truncate(false)
.open(db_path)
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
file.lock_exclusive()
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
let write_result = (|| -> Result<(), DevbrainError> {
file.set_len(0)
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
file.seek(SeekFrom::Start(0))
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
file.write_all(json.as_bytes())
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
file.sync_all()
.map_err(|error| DevbrainError::IoError(error.to_string()))?;
Ok(())
})();
let unlock_result = file
.unlock()
.map_err(|error| DevbrainError::IoError(error.to_string()));
write_result?;
unlock_result?;
Ok(())
}
pub fn add_entry(config: &Config, entry: Entry) -> Result<(), DevbrainError> {
let mut db = load_db(config)?;
db.entries.push(entry);
save_db(config, &db)?;
Ok(())
}