use std::{
fs,
sync::{LazyLock, RwLock},
};
use serde::{Serialize, de::DeserializeOwned};
use sled::{CompareAndSwapError, Db, IVec, Tree, transaction::TransactionError};
use crate::data_dir;
pub type Result<T> = std::result::Result<T, DatabaseError>;
mod sled_ops;
pub(crate) use sled_ops::*;
mod database_entry;
pub use database_entry::*;
mod patchable;
pub use patchable::*;
mod mergeable;
pub use mergeable::*;
mod createable;
pub use createable::*;
mod deleteable;
pub use deleteable::*;
mod transaction_args;
pub use transaction_args::*;
#[derive(thiserror::Error, Debug)]
pub enum DatabaseError {
#[error("A key was not found in the database when it was expected")]
MissingEntry,
#[error("A key was found in the database when it was not expected")]
AlreadyInDatabase,
#[error("More than one key was found in the database when only one was expected")]
TooManyMatches,
#[error("A record was out of date. v{0} was expected but v{1} was found")]
OutdatedVesion(u32, u32),
#[error("Ciborium serialize error: {0}")]
CiboriumSer(#[from] ciborium::ser::Error<std::io::Error>),
#[error("Ciborium deserialize error: {0}")]
CiboriumDe(#[from] ciborium::de::Error<std::io::Error>),
#[error("Sled error: {0}")]
Sled(#[from] sled::Error),
#[error("{0}")]
CompareAndSwapError(#[from] CompareAndSwapError),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("IoError: {0}")]
Io(#[from] std::io::Error),
}
impl<E> From<TransactionError<E>> for DatabaseError
where
E: Into<DatabaseError>,
{
fn from(value: TransactionError<E>) -> Self {
match value {
TransactionError::Abort(err) => err.into(),
TransactionError::Storage(error) => error.into(),
}
}
}
static LIBRARY_DB: LazyLock<RwLock<Option<Db>>> = LazyLock::new(|| RwLock::new(None));
fn library_db() -> Db {
let mut db = LIBRARY_DB.write().unwrap();
db.get_or_insert_with(|| {
let database_dir = data_dir().join("library_data");
sled::open(database_dir).expect("Failed to open database")
})
.clone()
}
pub(crate) fn track_tree() -> Tree {
library_db()
.open_tree("track")
.expect("Failed to open 'track' tree")
}
pub(crate) fn album_tree() -> Tree {
library_db()
.open_tree("album")
.expect("Failed to open 'album' tree")
}
pub(crate) fn artist_tree() -> Tree {
library_db()
.open_tree("artist")
.expect("Failed to open 'artist' tree")
}
pub fn drop_db() -> sled::Result<()> {
{
let mut db_lock = LIBRARY_DB.write().unwrap();
if let Some(db) = db_lock.as_ref() {
db.flush()?;
}
*db_lock = None;
}
let database_dir = data_dir().join("library_data");
fs::remove_dir_all(&database_dir)?;
Ok(())
}
pub fn db_flush() -> sled::Result<()> {
library_db().flush()?;
Ok(())
}
pub(crate) fn deserialize_from_ivec<T: DeserializeOwned>(raw: IVec) -> T {
ciborium::from_reader(&raw[..]).expect("Ciborium failed to deserialize bytes. This can only happen if bytes were deserialized correctly, or out-of-date bytes were fetched")
}
pub(crate) fn serialize_to_ivec<T: Serialize>(item: &T) -> IVec {
let mut buf = Vec::new();
ciborium::into_writer(item, &mut buf)
.expect("Ciborium failed to serialize. This cannot happen unless a serializer failed");
IVec::from(buf)
}