Skip to main content

selene_core/
database.rs

1use std::{
2    ops::Deref,
3    path::{Path, PathBuf},
4    sync::LazyLock,
5};
6
7use lunar_lib::database::{
8    ConflictableTransactionResult, Database, DatabaseEntry, DatabaseError, Db, Transactional, Tree,
9};
10
11pub mod entry_extensions;
12pub mod tx_extensions;
13
14mod patchable;
15pub(crate) use patchable::*;
16
17mod mergeable;
18pub use mergeable::*;
19
20mod createable;
21pub use createable::*;
22
23mod deleteable;
24pub use deleteable::*;
25
26use crate::{
27    data_dir,
28    library::collection::{Collection, STATIC_COLLECTIONS, STATIC_COLLECTIONS_VERSION},
29};
30
31pub mod validator;
32
33#[derive(Clone)]
34pub struct LibraryDb {
35    db: Db,
36}
37
38impl Deref for LibraryDb {
39    type Target = Db;
40
41    fn deref(&self) -> &Self::Target {
42        &self.db
43    }
44}
45
46static LIBRARY_DB_PATH: LazyLock<PathBuf> = LazyLock::new(|| data_dir().join("library_data"));
47
48impl Database for LibraryDb {
49    const RETRY_MAX_ATTEMPTS: Option<usize> = None;
50
51    const RETRY_DURATION: std::time::Duration = std::time::Duration::from_millis(1);
52
53    fn new(db: Db) -> Self {
54        Self { db }
55    }
56
57    fn pre_open(db: &LibraryDb) -> Result<(), Box<dyn std::error::Error>> {
58        ensure_hardcoded_collections(db)?;
59        Ok(())
60    }
61
62    fn path() -> &'static Path {
63        &LIBRARY_DB_PATH
64    }
65}
66
67pub(crate) fn track_tree(db: &LibraryDb) -> Tree {
68    db.open_tree("track").expect("Failed to open 'track' tree")
69}
70pub(crate) fn album_tree(db: &LibraryDb) -> Tree {
71    db.open_tree("album").expect("Failed to open 'album' tree")
72}
73pub(crate) fn artist_tree(db: &LibraryDb) -> Tree {
74    db.open_tree("artist")
75        .expect("Failed to open 'artist' tree")
76}
77pub(crate) fn collection_tree(db: &LibraryDb) -> Tree {
78    db.open_tree("collection")
79        .expect("Failed to open 'collection' tree")
80}
81
82fn ensure_hardcoded_collections(db: &LibraryDb) -> Result<(), Box<dyn std::error::Error>> {
83    const KEY: &[u8] = b"hardcoded_collections_version";
84
85    if db
86        .get(KEY)?
87        .is_none_or(|v| *v != STATIC_COLLECTIONS_VERSION.to_be_bytes())
88    {
89        let db_tree = &***db;
90        let collection_tree = Collection::tree(db);
91
92        (db_tree, &collection_tree).transaction(
93            |(db_tree, collection_tree)| -> ConflictableTransactionResult<(), DatabaseError> {
94                for collection in STATIC_COLLECTIONS.iter() {
95                    let collection = *collection;
96
97                    let mut buf = Vec::new();
98                    ciborium::into_writer(collection, &mut buf).unwrap();
99
100                    collection_tree.insert(&*collection.id(), &*buf)?;
101                }
102
103                db_tree.insert(KEY, &STATIC_COLLECTIONS_VERSION.to_be_bytes())?;
104
105                Ok(())
106            },
107        )?;
108    }
109
110    Ok(())
111}