Skip to main content

selene_core/
database.rs

1use std::{fs, ops::Deref, thread, time::Duration};
2
3use lunar_lib::trace;
4use serde::{Serialize, de::DeserializeOwned};
5use sled::{CompareAndSwapError, Db, IVec, Tree, transaction::TransactionError};
6
7use crate::{data_dir, database::validator::DatabaseReferenceError};
8
9pub type Result<T> = std::result::Result<T, DatabaseError>;
10
11pub mod writer;
12
13mod sled_ops;
14pub(crate) use sled_ops::*;
15
16mod database_entry;
17pub use database_entry::*;
18
19mod patchable;
20pub use patchable::*;
21
22mod mergeable;
23pub use mergeable::*;
24
25mod createable;
26pub use createable::*;
27
28mod deleteable;
29pub use deleteable::*;
30
31mod transaction_args;
32pub use transaction_args::*;
33
34pub mod validator;
35
36#[derive(thiserror::Error, Debug)]
37pub enum DatabaseError {
38    #[error("A key was not found in the database when it was expected")]
39    MissingEntry,
40
41    #[error("A key was found in the database when it was not expected")]
42    AlreadyInDatabase,
43
44    #[error("More than one key was found in the database when only one was expected")]
45    TooManyMatches,
46
47    #[error("A record was out of date. v{0} was expected but v{1} was found")]
48    OutdatedVesion(u32, u32),
49
50    #[error("Ciborium serialize error: {0}")]
51    CiboriumSer(#[from] ciborium::ser::Error<std::io::Error>),
52
53    #[error("Ciborium deserialize error: {0}")]
54    CiboriumDe(#[from] ciborium::de::Error<std::io::Error>),
55
56    #[error("Sled error: {0}")]
57    Sled(#[from] sled::Error),
58
59    #[error("{0}")]
60    CompareAndSwapError(#[from] CompareAndSwapError),
61
62    #[error("Invalid input: {0}")]
63    InvalidInput(String),
64
65    #[error("IoError: {0}")]
66    Io(#[from] std::io::Error),
67
68    #[error("A CAS Transaction was retried too many times")]
69    TooManyRetries,
70
71    #[error("{0}")]
72    ValidationError(#[from] DatabaseReferenceError),
73}
74
75impl<E> From<TransactionError<E>> for DatabaseError
76where
77    E: Into<DatabaseError>,
78{
79    fn from(value: TransactionError<E>) -> Self {
80        match value {
81            TransactionError::Abort(err) => err.into(),
82            TransactionError::Storage(error) => error.into(),
83        }
84    }
85}
86
87/// Open the library database
88///
89/// # Warning
90///
91/// This function locks the database, preventing anything else, including the daemon, from reading or read. Do not hold the database if you do not need to
92pub fn library_db() -> Db {
93    trace!("Trying to aquire database");
94    let database_dir = data_dir().join("library_data");
95
96    loop {
97        match sled::open(&database_dir) {
98            Ok(db) => return db,
99            Err(sled::Error::Io(err))
100                if matches!(err.kind(), std::io::ErrorKind::Other)
101                    && err.to_string().contains("could not acquire lock on ") =>
102            {
103                trace!("Waiting to aquire database");
104                thread::sleep(Duration::from_millis(500));
105                continue;
106            }
107            Err(err) => panic!("Failed to open database: {err:#?}"),
108        }
109    }
110}
111
112pub(crate) fn track_tree(db: &Db) -> Tree {
113    db.open_tree("track").expect("Failed to open 'track' tree")
114}
115pub(crate) fn album_tree(db: &Db) -> Tree {
116    db.open_tree("album").expect("Failed to open 'album' tree")
117}
118pub(crate) fn artist_tree(db: &Db) -> Tree {
119    db.open_tree("artist")
120        .expect("Failed to open 'artist' tree")
121}
122pub(crate) fn collection_tree(db: &Db) -> Tree {
123    db.open_tree("collection")
124        .expect("Failed to open 'collection' tree")
125}
126
127/// Attempts to delete the entire contents of the database
128///
129/// This function deletes all contents of the database by replacing the existing database entry with [`None`], then deleting the database directory. This will create a new database the next time it is accessed
130/// This function should only be used if you know that there are no functions currently trying to read, write, are holding references to the database, like a [`Tree`]
131pub fn drop_db() -> sled::Result<()> {
132    library_db().flush()?;
133
134    let database_dir = data_dir().join("library_data");
135
136    if let Err(err) = fs::remove_dir_all(&database_dir)
137        && !matches!(err.kind(), std::io::ErrorKind::NotFound)
138    {
139        return Err(err.into());
140    };
141
142    Ok(())
143}
144
145pub fn db_flush() -> sled::Result<()> {
146    library_db().flush()?;
147    Ok(())
148}
149
150pub(crate) fn deserialize_from_ivec<T: DeserializeOwned>(raw: IVec) -> T {
151    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")
152}
153
154pub(crate) fn serialize_to_ivec<T: Serialize>(item: &T) -> IVec {
155    let mut buf = Vec::new();
156    ciborium::into_writer(item, &mut buf)
157        .expect("Ciborium failed to serialize. This cannot happen unless a serializer failed");
158    IVec::from(buf)
159}
160
161pub trait EntryId: Deref<Target = blake3::Hash> + Copy + Eq {
162    type Entry: DatabaseEntry;
163
164    fn as_bytes(&self) -> &[u8; 32] {
165        self.deref().as_bytes()
166    }
167}