1use std::{
2 ops::Deref,
3 path::PathBuf,
4 sync::{LazyLock, Mutex},
5};
6
7use lunar_lib::database::{
8 ConflictableTransactionResult, Database, DatabaseEntry, DatabaseError, SledDb, Transactional,
9 caching::DbCache,
10};
11
12use crate::library::collection::STATIC_COLLECTIONS;
13
14mod entry_extensions;
15pub use entry_extensions::*;
16
17mod tx_extensions;
18pub use tx_extensions::*;
19
20mod patchable;
21pub(crate) use patchable::*;
22
23mod mergeable;
24pub use mergeable::*;
25
26mod createable;
27pub use createable::*;
28
29mod deleteable;
30pub use deleteable::*;
31
32mod resolveable;
33pub use resolveable::*;
34
35use crate::{
36 data_dir,
37 library::{
38 album::Album,
39 artist::Artist,
40 collection::{Collection, STATIC_COLLECTIONS_VERSION},
41 track::Track,
42 },
43};
44
45pub mod validator;
46
47#[derive(Clone)]
48pub struct LibraryDb {
49 db: SledDb,
50}
51
52impl Deref for LibraryDb {
53 type Target = SledDb;
54
55 fn deref(&self) -> &Self::Target {
56 &self.db
57 }
58}
59
60impl Database for LibraryDb {
61 const RETRY_MAX_ATTEMPTS: Option<usize> = None;
62
63 const RETRY_DURATION: std::time::Duration = std::time::Duration::from_millis(1);
64
65 fn new(db: SledDb) -> Self {
66 Self { db }
67 }
68
69 fn db(&self) -> &lunar_lib::database::SledDb {
70 &self.db
71 }
72
73 fn pre_open(db: &LibraryDb) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
74 ensure_hardcoded_collections(db)?;
75 Ok(())
76 }
77
78 fn path() -> PathBuf {
79 data_dir().join("library_data")
80 }
81}
82
83pub(crate) static TRACK_CACHE: LazyLock<Mutex<DbCache<Track>>> =
84 LazyLock::new(|| Mutex::new(DbCache::new()));
85
86pub(crate) static ALBUM_CACHE: LazyLock<Mutex<DbCache<Album>>> =
87 LazyLock::new(|| Mutex::new(DbCache::new()));
88
89pub(crate) static ARTIST_CACHE: LazyLock<Mutex<DbCache<Artist>>> =
90 LazyLock::new(|| Mutex::new(DbCache::new()));
91
92pub(crate) static COLLECTION_CACHE: LazyLock<Mutex<DbCache<Collection>>> =
93 LazyLock::new(|| Mutex::new(DbCache::new()));
94
95fn ensure_hardcoded_collections(
96 db: &LibraryDb,
97) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
98 const KEY: &[u8] = b"hardcoded_collections_version";
99
100 if db
101 .get(KEY)?
102 .is_none_or(|v| *v != STATIC_COLLECTIONS_VERSION.to_be_bytes())
103 {
104 let db_tree = &*db.db;
105 let collection_tree = Collection::__tree(db);
106
107 (db_tree, &collection_tree).transaction(
108 |(db_tree, collection_tree)| -> ConflictableTransactionResult<(), DatabaseError> {
109 for collection in STATIC_COLLECTIONS.iter() {
110 let collection = *collection;
111
112 let version: [u8; 4] = Collection::VERSION_NUMBER.to_be_bytes();
113
114 let mut buffer = Vec::from(version);
115 buffer.push(collection.read_only() as u8);
116
117 let buf = postcard::to_extend(collection, buffer)
118 .expect("Postcard failed to serialize. This cannot happen unless a serializer failed");
119
120 collection_tree.insert(&*collection.id(), &*buf)?;
121 }
122
123 db_tree.insert(KEY, &STATIC_COLLECTIONS_VERSION.to_be_bytes())?;
124
125 Ok(())
126 },
127 )?;
128 }
129
130 Ok(())
131}