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