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}