chilen_backend/music_lib/
mod.rs1pub(crate) mod covers;
2pub(crate) mod indexer;
3pub mod state;
4#[cfg(test)]
5mod tests;
6
7use std::{
8 fs::{File, create_dir_all, read},
9 io::Write,
10 path::{Path, PathBuf},
11 sync::{Arc, RwLock},
12};
13
14use log::{error, trace};
15use m3u8_rs::{MediaPlaylist, MediaSegment};
16
17use crate::{
18 Error,
19 music_lib::state::{MUSIC_LIBRARY, Track, save_library, unwrap_lib_mut, unwrap_lib_ref},
20};
21
22pub(crate) static DATA_DIR: RwLock<Option<PathBuf>> = RwLock::new(None);
23pub(crate) static MUSIC_DIR: RwLock<Option<PathBuf>> = RwLock::new(None);
24pub(crate) static CACHE_DIR: RwLock<Option<PathBuf>> = RwLock::new(None);
25
26fn init_dir(dir: &PathBuf) -> Result<(), Error> {
27 if dir.is_dir() {
28 let perms = match dir.metadata() {
29 Ok(md) => md.permissions(),
30 Err(e) => {
31 error!("Could not read the metadata of {dir:?}: {e}");
32 return Err(Error::PathInaccessible(dir.to_path_buf()));
33 }
34 };
35 if perms.readonly() {
36 error!("The directory {dir:?} is readonly");
37 return Err(Error::PathReadonly(dir.to_path_buf()));
38 }
39 Ok(())
40 } else {
41 let exists = match dir.try_exists() {
42 Ok(exists) => exists,
43 Err(e) => {
44 error!("Can't check whether {dir:?} exists: {e}");
45 return Err(Error::PathInaccessible(dir.to_path_buf()));
46 }
47 };
48 if exists {
49 error!("The path is not a directory: {dir:?}");
50 Err(Error::NotADirectory(dir.to_path_buf()))
51 } else {
52 trace!("The directory at {dir:?} does not exist. Attempting to create a new one");
53 if let Err(e) = create_dir_all(dir) {
54 error!("Could not create the directory: {e}");
55 return Err(Error::DirectoryCreationFailed(
56 dir.to_path_buf(),
57 e.to_string(),
58 ));
59 }
60 trace!("Created a new directory at {dir:?}");
61 Ok(())
62 }
63 }
64}
65
66pub(crate) fn set_dirs(
67 data_dir: PathBuf,
68 cache_dir: PathBuf,
69 music_dir: PathBuf,
70) -> Result<(), Error> {
71 init_dir(&cache_dir)?;
72 init_dir(&data_dir)?;
73 if music_dir.is_dir() {
74 if let Err(e) = music_dir.metadata() {
75 error!("Could not read the metadata of {:?}: {e}", music_dir);
76 return Err(Error::PathInaccessible(music_dir));
77 }
78 } else {
79 error!("The music library is not a directory or does not exist: {music_dir:?}");
80 return Err(Error::NotADirectory(music_dir));
81 }
82 *DATA_DIR.write().unwrap() = Some(data_dir);
83 *CACHE_DIR.write().unwrap() = Some(cache_dir);
84 *MUSIC_DIR.write().unwrap() = Some(music_dir);
85
86 trace!("Successfully set paths");
87
88 Ok(())
89}
90
91pub(crate) fn tracks_from_paths(
104 track_paths: &[PathBuf],
105 allow_failure: bool,
107) -> Result<Vec<Track>, Error> {
108 let guard = MUSIC_LIBRARY.read().unwrap();
109 let lib = unwrap_lib_ref(guard.as_ref())?;
110 let mut out = Vec::with_capacity(track_paths.len());
111
112 for path in track_paths {
113 if let Some(track) = lib.find_track_by_path(path) {
114 out.push(track.as_ref().clone());
115 } else if !allow_failure {
116 return Err(Error::UnknownTrack);
117 }
118 }
119
120 Ok(out)
121}
122
123pub(crate) fn tracks_from_hashes(hashes: Vec<u64>) -> Result<Vec<Arc<Track>>, Error> {
124 let guard = MUSIC_LIBRARY.read().unwrap();
125 let lib = unwrap_lib_ref(guard.as_ref())?;
126 lib.tracks_from_hashes(hashes)
127}
128
129pub(crate) fn tracks_from_m3u8(path: &PathBuf) -> Result<Vec<PathBuf>, Error> {
131 trace!("Loading an M3U8 playlist from {path:?}");
132 let data = match read(path) {
133 Ok(data) => data,
134 Err(e) => {
135 error!("Could not read the playlist file at {path:?}: {e}");
136 return Err(Error::PathDoesNotExist);
137 }
138 };
139 let content = String::from_utf8_lossy(&data);
140 match m3u8_rs::parser::parse_media_playlist(&content) {
141 Ok((_, pl)) => {
142 let base_path = path.parent().unwrap_or(Path::new("./"));
143 let track_paths: Vec<_> = pl
144 .segments
145 .into_iter()
146 .map(|s| {
147 let mut path = PathBuf::from(base_path);
148 path.push(s.uri.components());
149 path
150 })
151 .collect();
152 Ok(track_paths)
153 }
154 Err(e) => {
155 error!("Could not parse the M3U playlist: {e}");
156 Err(Error::PlaylistParsingError)
157 }
158 }
159}
160
161pub fn export_playlist_to_m3u8(name: String, path: &Path) -> Result<(), Error> {
163 trace!("Exporting a playlist to an M3U8 file in {path:?}");
164 let guard = MUSIC_LIBRARY.read().unwrap();
165 let lib = unwrap_lib_ref(guard.as_ref())?;
166 let pl = match lib.find_playlist(&name) {
167 Some(pl) => pl,
168 None => return Err(Error::UnknownPlaylist(name)),
169 };
170 let music_dir = MUSIC_DIR.read().unwrap().as_ref().unwrap().clone();
171 let segments = pl
172 .tracks
173 .iter()
174 .map(|t| MediaSegment {
175 uri: {
176 if path.parent() == Some(&music_dir) {
177 match t.path.strip_prefix(music_dir.clone()) {
178 Ok(path) => path.to_path_buf(),
179 Err(_) => path.to_path_buf(),
180 }
181 } else {
182 t.path.clone()
183 }
184 },
185 duration: t.duration,
186 title: t.title.clone(),
187 })
188 .collect();
189 drop(guard);
190 let media_playlist = MediaPlaylist { segments };
191 let mut file = match File::create(path) {
192 Ok(file) => file,
193 Err(e) => {
194 error!("Could not open the m3u8 export file for writing: {e}");
195 return Err(Error::PlaylistExportFailed);
196 }
197 };
198 match file.write_all(media_playlist.serialize().as_bytes()) {
199 Ok(_) => Ok(()),
200 Err(e) => {
201 error!("Could not write the playlist contents to a file: {e}");
202 Err(Error::PlaylistExportFailed)
203 }
204 }
205}
206
207pub fn create_playlist(name: String, track_paths: &Option<Vec<PathBuf>>) -> Result<(), Error> {
208 let mut guard = MUSIC_LIBRARY.write().unwrap();
209 let lib = unwrap_lib_mut(guard.as_mut())?;
210 let name = name.trim();
211 let name = if name.is_empty() {
212 lib.get_default_playlist_name()
213 } else {
214 name.to_string()
215 };
216 lib.create_playlist(name, track_paths)?;
217
218 drop(guard);
219 save_library()
220}
221
222pub fn rename_playlist(source: &str, target: &str) -> Result<(), Error> {
223 let mut guard = MUSIC_LIBRARY.write().unwrap();
224 let lib = unwrap_lib_mut(guard.as_mut())?;
225 lib.rename_playlist(source, target)?;
226 drop(guard);
227 save_library()
228}
229
230pub fn import_playlist_from_m3u8(name: Option<String>, file: &PathBuf) -> Result<(), Error> {
231 let mut guard = MUSIC_LIBRARY.write().unwrap();
232 let lib = unwrap_lib_mut(guard.as_mut())?;
233 lib.import_m3u8_playlist(file, name)?;
234
235 drop(guard);
236 save_library()
237}
238
239pub fn delete_playlists(playlists: Vec<String>) -> Result<(), Error> {
240 let mut guard = MUSIC_LIBRARY.write().unwrap();
241 let lib = unwrap_lib_mut(guard.as_mut())?;
242 lib.remove_playlists(playlists)?;
243
244 drop(guard);
245 save_library()
246}
247
248pub fn add_tracks(playlist: &str, tracks: Vec<PathBuf>) -> Result<(), Error> {
249 let mut guard = MUSIC_LIBRARY.write().unwrap();
250 let lib = unwrap_lib_mut(guard.as_mut())?;
251 lib.add_tracks(playlist, tracks)?;
252
253 drop(guard);
254 save_library()
255}
256
257pub fn remove_tracks(playlist: &str, tracks: Vec<usize>) -> Result<(), Error> {
259 let mut guard = MUSIC_LIBRARY.write().unwrap();
260 let lib = unwrap_lib_mut(guard.as_mut())?;
261 lib.remove_tracks(playlist, tracks)?;
262
263 drop(guard);
264 save_library()?;
265 Ok(())
266}