chilen_backend/music_lib/
mod.rs1pub mod indexer;
2pub mod sorting;
3
4mod state;
5#[cfg(test)]
6mod tests;
7
8use std::{
9 fs::{File, create_dir_all, read},
10 io::Write,
11 path::{Path, PathBuf},
12 sync::{Arc, RwLock},
13};
14
15use log::{error, trace};
16use m3u8_rs::{MediaPlaylist, MediaSegment};
17use serde::{Deserialize, Serialize};
18
19use crate::Error;
20
21pub use state::*;
22
23pub(crate) static DATA_DIR: RwLock<Option<PathBuf>> = RwLock::new(None);
24pub(crate) static MUSIC_DIR: RwLock<Option<PathBuf>> = RwLock::new(None);
25pub(crate) static CACHE_DIR: RwLock<Option<PathBuf>> = RwLock::new(None);
26
27pub trait AsTrack {
28 fn as_track(&self) -> &Track;
29}
30impl AsTrack for Arc<Track> {
31 fn as_track(&self) -> &Track {
32 self
33 }
34}
35impl AsTrack for &Arc<Track> {
36 fn as_track(&self) -> &Track {
37 self
38 }
39}
40impl AsTrack for &Track {
41 fn as_track(&self) -> &Track {
42 self
43 }
44}
45
46pub trait AsAlbum {
47 fn as_album(&self) -> &Album;
48}
49impl AsAlbum for Arc<Album> {
50 fn as_album(&self) -> &Album {
51 self
52 }
53}
54impl AsAlbum for &Arc<Album> {
55 fn as_album(&self) -> &Album {
56 self
57 }
58}
59impl AsAlbum for &Album {
60 fn as_album(&self) -> &Album {
61 self
62 }
63}
64
65pub trait AsArtist {
66 fn as_artist(&self) -> &Artist;
67}
68impl AsArtist for Arc<Artist> {
69 fn as_artist(&self) -> &Artist {
70 self
71 }
72}
73impl AsArtist for &Arc<Artist> {
74 fn as_artist(&self) -> &Artist {
75 self
76 }
77}
78impl AsArtist for &Artist {
79 fn as_artist(&self) -> &Artist {
80 self
81 }
82}
83
84pub trait AsPlaylist {
85 fn as_playlist(&self) -> &Playlist;
86}
87impl AsPlaylist for Arc<Playlist> {
88 fn as_playlist(&self) -> &Playlist {
89 self
90 }
91}
92impl AsPlaylist for &Arc<Playlist> {
93 fn as_playlist(&self) -> &Playlist {
94 self
95 }
96}
97impl AsPlaylist for &Playlist {
98 fn as_playlist(&self) -> &Playlist {
99 self
100 }
101}
102
103pub trait AsGenre {
104 fn as_genre(&self) -> &Genre;
105}
106impl AsGenre for Arc<Genre> {
107 fn as_genre(&self) -> &Genre {
108 self
109 }
110}
111impl AsGenre for &Arc<Genre> {
112 fn as_genre(&self) -> &Genre {
113 self
114 }
115}
116impl AsGenre for &Genre {
117 fn as_genre(&self) -> &Genre {
118 self
119 }
120}
121
122#[derive(Debug, Default, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
123pub struct ValueSeparators {
124 pub artist: Vec<String>,
125 pub genre: Vec<String>,
126}
127
128impl ValueSeparators {
129 pub fn new(separators: Vec<String>) -> Self {
130 Self {
131 artist: separators.clone(),
132 genre: separators,
133 }
134 }
135}
136
137#[cfg_attr(test, derive(Default))]
138#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
139pub struct Config {
140 pub value_separators: ValueSeparators,
141 pub indexer: indexer::Config,
142}
143
144fn init_dir(dir: &PathBuf) -> Result<(), Error> {
145 if dir.is_dir() {
146 let perms = match dir.metadata() {
147 Ok(md) => md.permissions(),
148 Err(e) => {
149 error!("Could not read the metadata of {dir:?}: {e}");
150 return Err(Error::PathInaccessible(dir.to_path_buf()));
151 }
152 };
153 if perms.readonly() {
154 error!("The directory {dir:?} is readonly");
155 return Err(Error::PathReadonly(dir.to_path_buf()));
156 }
157 Ok(())
158 } else {
159 let exists = match dir.try_exists() {
160 Ok(exists) => exists,
161 Err(e) => {
162 error!("Can't check whether {dir:?} exists: {e}");
163 return Err(Error::PathInaccessible(dir.to_path_buf()));
164 }
165 };
166 if exists {
167 error!("The path is not a directory: {dir:?}");
168 Err(Error::NotADirectory(dir.to_path_buf()))
169 } else {
170 trace!("The directory at {dir:?} does not exist. Attempting to create a new one");
171 if let Err(e) = create_dir_all(dir) {
172 error!("Could not create the directory: {e}");
173 return Err(Error::DirectoryCreationFailed(
174 dir.to_path_buf(),
175 e.to_string(),
176 ));
177 }
178 trace!("Created a new directory at {dir:?}");
179 Ok(())
180 }
181 }
182}
183
184pub(crate) fn set_dirs(
185 data_dir: PathBuf,
186 cache_dir: PathBuf,
187 music_dir: PathBuf,
188) -> Result<(), Error> {
189 init_dir(&cache_dir)?;
190 init_dir(&data_dir)?;
191 if music_dir.is_dir() {
192 if let Err(e) = music_dir.metadata() {
193 error!("Could not read the metadata of {:?}: {e}", music_dir);
194 return Err(Error::PathInaccessible(music_dir));
195 }
196 } else {
197 error!("The music library is not a directory or does not exist: {music_dir:?}");
198 return Err(Error::NotADirectory(music_dir));
199 }
200 *DATA_DIR.write().unwrap() = Some(data_dir);
201 *CACHE_DIR.write().unwrap() = Some(cache_dir);
202 *MUSIC_DIR.write().unwrap() = Some(music_dir);
203
204 trace!("Successfully set paths");
205
206 Ok(())
207}
208
209pub(crate) fn tracks_from_paths(
222 track_paths: &[PathBuf],
223 allow_failure: bool,
225) -> Result<Vec<Arc<Track>>, Error> {
226 let guard = MUSIC_LIBRARY.read().unwrap();
227 let lib = unwrap_lib_ref(guard.as_ref())?;
228 let mut out = Vec::with_capacity(track_paths.len());
229
230 for path in track_paths {
231 if let Some(track) = lib.find_track_by_path(&path.to_string_lossy()) {
232 out.push(track);
233 } else if !allow_failure {
234 return Err(Error::UnknownTrackPath(path.to_path_buf()));
235 }
236 }
237
238 Ok(out)
239}
240
241pub(crate) fn find_tracks_by_hashes(hashes: Vec<u64>) -> Result<HashMatchingResult, Error> {
242 let guard = MUSIC_LIBRARY.read().unwrap();
243 let lib = unwrap_lib_ref(guard.as_ref())?;
244 Ok(lib.find_tracks_by_hashes(&hashes))
245}
246
247pub(crate) fn tracks_from_m3u8(path: &PathBuf) -> Result<Vec<PathBuf>, Error> {
249 trace!("Loading an M3U8 playlist from {path:?}");
250 let data = match read(path) {
251 Ok(data) => data,
252 Err(e) => {
253 error!("Could not read the playlist file at {path:?}: {e}");
254 return Err(Error::PathDoesNotExist);
255 }
256 };
257 let content = String::from_utf8_lossy(&data);
258 match m3u8_rs::parser::parse_media_playlist(&content) {
259 Ok((_, pl)) => {
260 let base_path = path.parent().unwrap_or(Path::new("./"));
261 let track_paths: Vec<_> = pl
262 .segments
263 .into_iter()
264 .map(|s| {
265 let mut path = PathBuf::from(base_path);
266 path.push(s.uri.components());
267 path
268 })
269 .collect();
270 Ok(track_paths)
271 }
272 Err(e) => {
273 error!("Could not parse the M3U playlist: {e}");
274 Err(Error::PlaylistParsingError)
275 }
276 }
277}
278
279pub fn export_playlist_to_m3u8(name: &str, path: &Path) -> Result<(), Error> {
282 trace!("Exporting a playlist to an M3U8 file in {path:?}");
283 let guard = MUSIC_LIBRARY.read().unwrap();
284 let lib = unwrap_lib_ref(guard.as_ref())?;
285 let pl = match lib.find_playlist_by_name(name) {
286 Some(pl) => pl,
287 None => return Err(Error::UnknownPlaylist(name.to_string())),
288 };
289 let music_dir = MUSIC_DIR.read().unwrap().as_ref().unwrap().clone();
290 let segments = pl
291 .tracks
292 .iter()
293 .map(|t| MediaSegment {
294 uri: {
295 if path.parent() == Some(&music_dir) {
296 match t.path.strip_prefix(music_dir.clone()) {
297 Ok(path) => path.to_path_buf(),
298 Err(_) => path.to_path_buf(),
299 }
300 } else {
301 t.path.clone()
302 }
303 },
304 duration: t.duration,
305 title: t.title.clone().map(|t| t.into_string()),
306 })
307 .collect();
308 drop(guard);
309 let media_playlist = MediaPlaylist { segments };
310 let mut file = match File::create(path) {
311 Ok(file) => file,
312 Err(e) => {
313 error!("Could not open the m3u8 export file for writing: {e}");
314 return Err(Error::PlaylistExportFailed);
315 }
316 };
317 match file.write_all(media_playlist.serialize().as_bytes()) {
318 Ok(_) => Ok(()),
319 Err(e) => {
320 error!("Could not write the playlist contents to a file: {e}");
321 Err(Error::PlaylistExportFailed)
322 }
323 }
324}
325
326pub fn create_playlist(name: &str, track_paths: &Option<Vec<PathBuf>>) -> Result<(), Error> {
327 let mut guard = MUSIC_LIBRARY.write().unwrap();
328 let lib = unwrap_lib_mut(guard.as_mut())?;
329 let name = name.trim();
330 let name = if name.is_empty() {
331 lib.get_default_playlist_name()
332 } else {
333 name.to_string()
334 };
335 lib.create_playlist(&name, track_paths)?;
336
337 drop(guard);
338 save_library()
339}
340
341pub fn rename_playlist(source: &str, target: &str) -> Result<(), Error> {
342 let mut guard = MUSIC_LIBRARY.write().unwrap();
343 let lib = unwrap_lib_mut(guard.as_mut())?;
344 lib.rename_playlist(source, target)?;
345 drop(guard);
346 save_library()
347}
348
349pub fn import_playlist_from_m3u8(name: Option<String>, file: &PathBuf) -> Result<(), Error> {
350 let mut guard = MUSIC_LIBRARY.write().unwrap();
351 let lib = unwrap_lib_mut(guard.as_mut())?;
352 lib.import_m3u8_playlist(file, name)?;
353
354 drop(guard);
355 save_library()
356}
357
358pub fn delete_playlists(playlists: Vec<&str>) -> Result<(), Error> {
359 let mut guard = MUSIC_LIBRARY.write().unwrap();
360 let lib = unwrap_lib_mut(guard.as_mut())?;
361 lib.remove_playlists(playlists)?;
362
363 drop(guard);
364 save_library()
365}
366
367pub fn add_tracks(playlist: &str, tracks: &[u64]) -> Result<(), Error> {
368 let mut guard = MUSIC_LIBRARY.write().unwrap();
369 let lib = unwrap_lib_mut(guard.as_mut())?;
370 lib.add_tracks(playlist, tracks)?;
371
372 drop(guard);
373 save_library()
374}
375
376pub fn remove_tracks_by_indices(playlist: &str, tracks: Vec<usize>) -> Result<(), Error> {
378 let mut guard = MUSIC_LIBRARY.write().unwrap();
379 let lib = unwrap_lib_mut(guard.as_mut())?;
380 lib.remove_tracks(playlist, tracks)?;
381
382 drop(guard);
383 save_library()?;
384 Ok(())
385}
386
387pub fn add_favorite(item: impl Into<LibraryItem>) -> Result<(), Error> {
391 trace!("Adding an item to favorites");
392
393 let mut guard = MUSIC_LIBRARY.write().unwrap();
394 let lib = unwrap_lib_mut(guard.as_mut())?;
395 lib.add_favorite(item.into())?;
396 drop(guard);
397 save_library()
398}
399
400pub fn remove_favorite(item: &LibraryItem) -> Result<(), Error> {
404 trace!("Removing an item from favorites");
405
406 let mut guard = MUSIC_LIBRARY.write().unwrap();
407 let lib = unwrap_lib_mut(guard.as_mut())?;
408 lib.remove_favorite(item)?;
409
410 drop(guard);
411 save_library()
412}
413
414pub fn toggle_favorite(item: impl Into<LibraryItem>) -> Result<(), Error> {
418 let item = item.into();
419
420 let mut guard = MUSIC_LIBRARY.write().unwrap();
421 let lib = unwrap_lib_mut(guard.as_mut())?;
422
423 if lib.favorites.contains(&item) {
424 lib.remove_favorite(&item)?;
425 } else {
426 lib.add_favorite(item)?;
427 }
428
429 drop(guard);
430 save_library()
431}
432
433pub fn is_favorite(item: &LibraryItem) -> Result<bool, Error> {
437 let mut guard = MUSIC_LIBRARY.write().unwrap();
438 let lib = unwrap_lib_mut(guard.as_mut())?;
439 Ok(lib.favorites.contains(item))
440}