use std::any::{Any, TypeId};
use std::fmt::Debug;
use std::sync::Arc;
use std::{
collections::HashMap,
ffi::{OsStr, OsString},
fs::{self, File, remove_file},
io::{BufReader, Error, ErrorKind, Write},
mem,
path::{Path, PathBuf},
};
use deserialize_untagged_verbose_error::DeserializeUntaggedVerboseError;
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use std::cell::{Cell, RefCell};
use crate::Format;
pub fn type_name<T>() -> &'static str {
let full_name = std::any::type_name::<T>();
full_name
.rsplit("::")
.next()
.expect("full type name has at least one entry")
}
#[typetag::serde]
pub trait DatabaseEntry: Any {
fn name(&self) -> &OsStr;
}
pub type Cache = HashMap<TypeId, HashMap<OsString, CacheEntry>>;
#[derive(Clone)]
pub struct CacheEntry {
pub arc: Arc<dyn DatabaseEntry + Send + Sync + 'static>,
pub checksum: Option<u32>,
}
impl CacheEntry {
pub fn new(arc: Arc<dyn DatabaseEntry + Send + Sync + 'static>) -> Self {
return Self::from(arc);
}
pub fn insert<T: DatabaseEntry + Send + Sync>(
cache: &mut Cache,
instance: Arc<T>,
) -> Option<Arc<T>> {
let type_id = TypeId::of::<T>();
let name = instance.name().to_owned();
match cache.get_mut(&type_id) {
Some(subcache) => {
let old_entry = subcache.insert(name, CacheEntry::new(instance))?;
let any_arc = old_entry.arc as Arc<dyn Any + Send + Sync + 'static>;
return any_arc.downcast().ok();
}
None => {
let mut subcache = HashMap::new();
subcache.insert(name, CacheEntry::new(instance));
cache.insert(type_id, subcache);
return None;
}
}
}
}
impl From<Arc<dyn DatabaseEntry + Send + Sync + 'static>> for CacheEntry {
fn from(value: Arc<dyn DatabaseEntry + Send + Sync + 'static>) -> Self {
return Self {
arc: value,
checksum: None,
};
}
}
impl From<CacheEntry> for Arc<dyn Any + Send + Sync + 'static> {
fn from(value: CacheEntry) -> Self {
return value.arc;
}
}
pub struct DatabaseKey<'a> {
pub type_name: &'a OsStr,
pub name: &'a OsStr,
}
impl<'a, T: DatabaseEntry> From<&'a T> for DatabaseKey<'a> {
fn from(value: &'a T) -> Self {
return Self {
type_name: OsStr::new(type_name::<T>()),
name: value.name(),
};
}
}
impl<'a, A, B> From<(&'a A, &'a B)> for DatabaseKey<'a>
where
A: AsRef<OsStr> + ?Sized,
B: AsRef<OsStr> + ?Sized,
{
fn from(value: (&'a A, &'a B)) -> Self {
Self {
type_name: value.0.as_ref(),
name: value.1.as_ref(),
}
}
}
impl<'a> From<[&'a OsStr; 2]> for DatabaseKey<'a> {
fn from(value: [&'a OsStr; 2]) -> Self {
return Self {
type_name: value[0],
name: value[1],
};
}
}
impl<'a> From<[&'a str; 2]> for DatabaseKey<'a> {
fn from(value: [&'a str; 2]) -> Self {
return Self {
type_name: OsStr::new(value[0]),
name: OsStr::new(value[1]),
};
}
}
#[derive(Clone)]
pub struct DatabaseManager {
dir: PathBuf,
format: Box<dyn Format>,
cache: Cache,
}
impl DatabaseManager {
pub fn new<P, F>(path: P, format: F) -> std::io::Result<Self>
where
P: AsRef<Path>,
F: Format + 'static,
{
return Self::with_boxed_format(path, Box::new(format));
}
pub fn with_boxed_format<P>(path: P, format: Box<dyn Format>) -> std::io::Result<Self>
where
P: AsRef<Path>,
{
let mut dir = PathBuf::new();
dir.push(&path);
if !dir.exists() {
fs::create_dir(&dir).map_err(|err: Error| {
Error::new(
err.kind(),
format!("Could not create directory {}", dir.display()),
)
})?;
}
return Self::open_with_boxed_format(path, format);
}
pub fn open<P, F>(path: P, format: F) -> std::io::Result<Self>
where
P: AsRef<Path>,
F: Format + 'static,
{
return Self::open_with_boxed_format(path, Box::new(format));
}
pub fn open_with_boxed_format<P>(path: P, format: Box<dyn Format>) -> std::io::Result<Self>
where
P: AsRef<Path>,
{
let mut dir = PathBuf::new();
dir.push(path);
if dir.exists() {
return Ok(Self {
dir,
format,
cache: Default::default(),
});
} else {
return Err(Error::new(
ErrorKind::NotFound,
format!("Could not find directory {}", dir.display()),
));
}
}
pub fn dir(&self) -> &Path {
return self.dir.as_path();
}
pub fn data_format(&self) -> &dyn Format {
return &*self.format;
}
pub fn file_ext(&self) -> &OsStr {
return self.format.file_ext();
}
pub fn checksum<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> Option<u32> {
return checksum(&self.full_path_unchecked(key));
}
pub fn remove_empty_subfolders(&mut self) -> std::io::Result<()> {
fn remove_priv(path: &Path) -> std::io::Result<()> {
let reader = path.read_dir()?;
for folder in reader {
let dir_entry = folder?;
let path = dir_entry.path();
if path.read_dir()?.next().is_none() {
std::fs::remove_dir_all(path)?;
}
}
return Ok(());
}
remove_priv(self.dir())?;
return Ok(());
}
pub fn remove<'a, T: Into<DatabaseKey<'a>>>(&mut self, key: T) -> std::io::Result<()> {
let file_path = self.full_path_unchecked(key);
if file_path.exists() {
return std::fs::remove_file(&file_path).map_err(|err| {
Error::new(
err.kind(),
format!("Could not remove file {}: {}", file_path.display(), err),
)
});
} else {
return Ok(());
}
}
pub fn remove_all<O: AsRef<OsStr>>(&mut self, name: O) -> std::io::Result<()> {
fn remove_all_inner(dbm: &mut DatabaseManager, name: &OsStr) -> std::io::Result<()> {
let mut file_with_ext = name.to_os_string();
if !dbm.file_ext().is_empty() {
file_with_ext.push(".");
file_with_ext.push(dbm.file_ext());
}
let paths = fs::read_dir(dbm.dir())?;
for path in paths {
if let Ok(dir) = path {
let file_path = dir.path().join(&file_with_ext);
if file_path.exists() {
std::fs::remove_file(&file_path)?;
}
}
}
return Ok(());
}
return remove_all_inner(self, name.as_ref());
}
pub fn exists<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> bool {
return self.full_path(key).is_some();
}
pub fn full_path<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> Option<PathBuf> {
let path = self.full_path_unchecked(key);
if path.exists() {
return Some(path);
} else {
return None;
}
}
pub(crate) fn full_path_unchecked<'a, T: Into<DatabaseKey<'a>>>(&self, key: T) -> PathBuf {
let key: DatabaseKey = key.into();
let mut file_with_ext = OsStr::new(&key.name).to_os_string();
if !self.file_ext().is_empty() {
file_with_ext.push(".");
file_with_ext.push(self.file_ext());
}
return self
.dir()
.join(OsStr::new(&key.type_name))
.join(file_with_ext);
}
pub fn cache(&self) -> &Cache {
return &self.cache;
}
pub fn cache_mut(&mut self) -> &mut Cache {
return &mut self.cache;
}
pub fn write<T: DatabaseEntry>(
&mut self,
instance: &T,
write_options: &WriteOptions,
) -> std::io::Result<PathBuf> {
return self
.write_verbose_log(instance, write_options, false)
.map(|arg| arg.0);
}
pub fn write_verbose<T: DatabaseEntry>(
&mut self,
instance: &T,
write_options: &WriteOptions,
) -> std::io::Result<(PathBuf, WriteInfo)> {
return self.write_verbose_log(instance, write_options, true);
}
fn write_verbose_log<T: DatabaseEntry>(
&mut self,
instance: &T,
write_options: &WriteOptions,
log: bool,
) -> std::io::Result<(PathBuf, WriteInfo)> {
let result = WRITE_CONTEXT.with(|thread_context| {
let context = WriteContext::new(self, write_options, log);
thread_context.set(Some(context.clone()));
let result = context.write(instance);
thread_context.set(None);
result
});
let write_info = RwInfo::take_write_info();
match result {
Ok(path_buf) => return Ok((path_buf, write_info)),
Err(err) => return Err(err),
}
}
pub fn read<T: DatabaseEntry, O: AsRef<OsStr>>(&mut self, name: O) -> std::io::Result<T> {
return self.read_verbose(name).map(|arg| arg.0);
}
pub fn read_verbose<T: DatabaseEntry, O: AsRef<OsStr>>(
&mut self,
name: O,
) -> std::io::Result<(T, ReadInfo)> {
return self.read_verbose_log(name, true);
}
fn read_verbose_log<T: DatabaseEntry, O: AsRef<OsStr>>(
&mut self,
name: O,
log: bool,
) -> std::io::Result<(T, ReadInfo)> {
let result = READ_CONTEXT.with(|thread_context| {
let context = ReadContext::new(self, log);
thread_context.set(Some(context.clone()));
let result = context.read(name.as_ref());
thread_context.set(None);
result
});
let read_info = RwInfo::take_read_info();
match result {
Ok(instance) => return Ok((instance, read_info)),
Err(err) => return Err(err),
}
}
pub fn from_str<T: DeserializeOwned + 'static, F: Format>(
&mut self,
str: impl AsRef<str>,
) -> std::io::Result<T> {
READ_CONTEXT.with(|thread_context| {
let context = ReadContext::new(self, false);
thread_context.set(Some(context.clone()));
let dbm = unsafe { &mut *context.database_manager };
let format: &F =
(dbm.format.as_ref() as &dyn Any)
.downcast_ref()
.ok_or(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"given type F does not match the format of self",
))?;
let result = format
.deserialize::<T>(str.as_ref().as_bytes())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
thread_context.set(None);
Ok(result)
})
}
}
impl From<DatabaseManager> for Box<dyn Format> {
fn from(value: DatabaseManager) -> Self {
return value.format;
}
}
impl From<DatabaseManager> for Cache {
fn from(value: DatabaseManager) -> Self {
return value.cache;
}
}
#[derive(Clone, Copy)]
pub(crate) struct WriteContext {
log: bool,
pub(crate) database_manager: *mut DatabaseManager,
pub(crate) write_options: *const WriteOptions,
}
thread_local!(pub(crate) static WRITE_CONTEXT: Cell<Option<WriteContext>> = Cell::new(None));
impl WriteContext {
pub(crate) fn new(
database_manager: &mut DatabaseManager,
write_options: &WriteOptions,
log: bool,
) -> Self {
return Self {
database_manager: std::ptr::from_mut(database_manager),
write_options: std::ptr::from_ref(write_options),
log,
};
}
pub(crate) fn write<T: DatabaseEntry>(&self, instance: &T) -> std::io::Result<PathBuf> {
RwInfo::set_log(self.log);
let dbm = unsafe { &mut *self.database_manager }; let write_options = unsafe { &*self.write_options };
let data = dbm
.format
.serialize_dyn(instance)
.map_err(|err| std::io::Error::new(ErrorKind::Other, err))?;
let mut name = write_options.name(instance);
if !dbm.file_ext().is_empty() {
name.push(".");
name.push(dbm.file_ext());
}
let folder_dir = dbm.dir().join(type_name::<T>());
if !folder_dir.exists() {
std::fs::create_dir_all(&folder_dir)?;
}
let full_file_path = folder_dir.join(name);
let file_exists = full_file_path.exists();
let file_path = match write_options.name_collisions {
NameCollisions::Overwrite => {
if file_exists {
RwInfo::log_overwritten_file_path(full_file_path.clone());
} else {
RwInfo::log_created_file_path(full_file_path.clone());
}
full_file_path
}
NameCollisions::KeepExisting => {
if file_exists {
RwInfo::log_kept_file_path(full_file_path.clone());
return Ok(full_file_path);
} else {
RwInfo::log_created_file_path(full_file_path.clone());
full_file_path
}
}
NameCollisions::AdjustName => {
if file_exists {
let mut counter = 0;
let mut trial_file_path: PathBuf;
loop {
let mut name = write_options.name(instance);
name.push(&format!("_{}", counter));
if !dbm.file_ext().is_empty() {
name.push(".");
name.push(dbm.file_ext());
}
trial_file_path = folder_dir.join(name);
if !trial_file_path.exists() {
break;
}
counter += 1;
}
RwInfo::log_created_file_path(trial_file_path.clone());
trial_file_path
} else {
RwInfo::log_created_file_path(full_file_path.clone());
full_file_path
}
}
};
let mut file = File::create(&file_path).map_err(|err| {
Error::new(
err.kind(),
format!("Could not create file {}", file_path.display()),
)
})?;
match file.write_all(&data) {
Ok(_) => {
return Ok(file_path);
}
Err(err) => {
remove_file(&file_path)?;
return Err(err);
}
};
}
}
#[derive(Clone, Copy)]
pub(crate) struct ReadContext {
log: bool,
pub(crate) database_manager: *mut DatabaseManager,
}
thread_local!(pub(crate) static READ_CONTEXT: Cell<Option<ReadContext>> = Cell::new(None));
impl ReadContext {
pub(crate) fn new(database_manager: &mut DatabaseManager, log: bool) -> Self {
return Self {
log,
database_manager: std::ptr::from_mut(database_manager),
};
}
pub(crate) fn read<T: DatabaseEntry>(&self, name: &OsStr) -> std::io::Result<T> {
RwInfo::set_log(self.log);
let dbm = unsafe { &mut *self.database_manager };
let file_path = dbm.full_path_unchecked((type_name::<T>(), name));
if !file_path.exists() {
return Err(Error::new(
std::io::ErrorKind::NotFound,
format!("Could not find file {}", file_path.display()),
));
}
let data = fs::read(file_path.as_path())?;
match dbm.format.deserialize_dyn(&data) {
Ok(val) => {
let val = val as Box<dyn Any>;
match val.downcast::<T>() {
Ok(val) => Ok(*val),
Err(_) => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("type is not {}", type_name::<T>()),
));
}
}
}
Err(err) => {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
err.to_string(),
));
}
}
}
}
thread_local!(static RW_INFO: RefCell<RwInfo> = RefCell::new(RwInfo::default()));
#[derive(Default)]
pub(crate) struct RwInfo {
log: bool,
overwritten_files: Vec<PathBuf>,
kept_files: Vec<PathBuf>,
created_files: Vec<PathBuf>,
checksum_mismatch: Vec<ChecksumMismatch>,
}
impl RwInfo {
fn set_log(log: bool) {
RW_INFO.with(|f| {
let rw_info = &mut *f.borrow_mut();
rw_info.log = log;
});
}
fn take_write_info() -> WriteInfo {
return RW_INFO.with(|f| {
let rw_info = &mut *f.borrow_mut();
return WriteInfo {
overwritten_files: mem::replace(&mut rw_info.overwritten_files, Vec::new()),
created_files: mem::replace(&mut rw_info.created_files, Vec::new()),
kept_files: mem::replace(&mut rw_info.kept_files, Vec::new()),
};
});
}
fn take_read_info() -> ReadInfo {
return RW_INFO.with(|f| {
let rw_info = &mut *f.borrow_mut();
return ReadInfo {
checksum_mismatch: mem::replace(&mut rw_info.checksum_mismatch, Vec::new()),
};
});
}
fn log_overwritten_file_path(path: PathBuf) {
RW_INFO.with(|f| {
let mut borrowed = f.borrow_mut();
if borrowed.log {
borrowed.overwritten_files.push(path);
}
});
}
fn log_created_file_path(path: PathBuf) {
RW_INFO.with(|f| {
let mut borrowed = f.borrow_mut();
if borrowed.log {
borrowed.created_files.push(path);
}
});
}
fn log_kept_file_path(path: PathBuf) {
RW_INFO.with(|f| {
let mut borrowed = f.borrow_mut();
if borrowed.log {
borrowed.kept_files.push(path);
}
});
}
pub(crate) fn log_checksum_mismatch(val: ChecksumMismatch) {
RW_INFO.with(|f| {
let mut borrowed = f.borrow_mut();
if borrowed.log {
borrowed.checksum_mismatch.push(val);
}
});
}
}
#[derive(DeserializeUntaggedVerboseError, Debug)]
pub(crate) enum LinkOrEntity<T> {
DatabaseLink(DatabaseLink),
Entity(T),
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct DatabaseLink {
pub name: String,
#[serde(default)]
pub checksum: Option<u32>,
}
impl DatabaseLink {
pub(crate) fn new<T: DatabaseEntry>(instance: &T, checksum: Option<u32>) -> Self {
DatabaseLink {
name: instance.name().to_string_lossy().to_string(),
checksum,
}
}
pub(crate) fn test_for_checksum_mismatch(
&self,
file_path: PathBuf,
) -> Option<ChecksumMismatch> {
let checksum_cached_in_link = self.checksum?;
let checksum_loaded_file = checksum(file_path.as_path())?;
return Some(ChecksumMismatch {
checksum_cached_in_link,
checksum_loaded_file,
file_path,
});
}
}
#[derive(Debug, Clone)]
pub struct WriteOptions {
pub name_collisions: NameCollisions,
pub write_mode: WriteMode,
pub alias: HashMap<OsString, OsString>,
}
impl WriteOptions {
fn name<T: DatabaseEntry>(&self, instance: &T) -> OsString {
return self
.alias
.get(instance.name())
.map(|string| string.as_os_str())
.unwrap_or(instance.name())
.to_os_string();
}
}
impl Default for WriteOptions {
fn default() -> Self {
Self {
name_collisions: Default::default(),
write_mode: Default::default(),
alias: Default::default(),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub enum NameCollisions {
Overwrite,
#[default]
KeepExisting,
AdjustName,
}
#[derive(Debug, Clone, Copy, Default)]
pub enum WriteMode {
Flat,
#[default]
Link,
}
#[derive(Debug, Clone)]
pub struct ReadInfo {
pub checksum_mismatch: Vec<ChecksumMismatch>,
}
#[derive(Debug, Clone)]
pub struct WriteInfo {
pub created_files: Vec<PathBuf>,
pub kept_files: Vec<PathBuf>,
pub overwritten_files: Vec<PathBuf>,
}
#[derive(Debug, Clone)]
pub struct ChecksumMismatch {
pub checksum_cached_in_link: u32,
pub checksum_loaded_file: u32,
pub file_path: PathBuf,
}
pub fn checksum(path: &Path) -> Option<u32> {
let f = File::open(path).ok()?;
let reader = BufReader::new(f);
return adler32::adler32(reader).ok();
}