chilen_ipc/library.rs
1use std::{path::PathBuf, time::Duration};
2
3use serde::{Deserialize, Serialize};
4
5// TODO: Add more tags for this
6/// Struct representing a track from the [music library](MusicLibrary).
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
8pub struct Track {
9 /// The path to the audio file.
10 pub path: PathBuf,
11 /// The path to the extracted cover file.
12 pub cover_path: Option<PathBuf>,
13 /// The duration of the track.
14 pub duration: Duration,
15 // TODO: Add an option to split the tag with separators like ",", ";", "/", etc.
16 /// The track artist.
17 pub artist: Option<String>,
18 /// The track title.
19 pub title: Option<String>,
20 /// The track album.
21 pub album: Option<String>,
22 // TODO: Same as with artist
23 /// The track genre.
24 pub genre: Option<String>,
25 // TODO: Add easy access to synced lyrics
26 /// Possibly synchronized lyrics text.
27 pub lyrics: Option<String>,
28 /// Contents of the comment tag.
29 pub comment: Option<String>,
30 pub track: Option<u32>,
31 pub track_total: Option<u32>,
32 pub disc: Option<u32>,
33 pub disc_total: Option<u32>,
34 /// Release year.
35 pub year: Option<u32>,
36}
37
38impl std::fmt::Display for Track {
39 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40 write!(
41 f,
42 "{} - {} ({})",
43 self.artist.clone().unwrap_or(String::from("Unknown")),
44 self.title.clone().unwrap_or(String::from("Unknown")),
45 self.path.to_string_lossy()
46 )
47 }
48}
49
50/// Struct representing a playlist in the [music library](MusicLibrary).
51#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
52pub struct Playlist {
53 /// The name of the playlist.
54 ///
55 /// Playlist names are unique.
56 pub name: String,
57 /// The content of the playlist.
58 ///
59 /// All tracks must already be in the [music library](MusicLibrary). If a track is removed from
60 /// the library (eg. by removing an audio file from the music directory and reloading the
61 /// library), it will also be removed from all the playlists.
62 pub tracks: Vec<Track>,
63}
64
65/// Struct representing the contents of the music library.
66#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
67pub struct MusicLibrary {
68 /// The list of playlists in the music library.
69 pub playlists: Vec<Playlist>,
70 /// The list of all tracks in the music library.
71 pub tracks: Vec<Track>,
72}
73
74/// Subcommand of [`Command`](crate::Command) for managing the music library.
75///
76/// The expected response may be different depending on the command sent. If it isn't specified in
77/// the variant documentation, assume [`Response::Ok`](crate::Response::Ok) is the expected
78/// response.
79#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
80pub enum LibraryCommand {
81 /// Create a new playlist, optionally with some tracks in it.
82 NewPlaylist {
83 /// The name for the new playlist. Must not already exist in the music library.
84 ///
85 /// If a playlist with the specified name already exists,
86 /// [`LibraryError::PlaylistExists`] will be returned, and no changes to the
87 /// [music library](MusicLibrary) will be made.
88 name: String,
89 /// Optional list of paths to tracks to be added to the playlist.
90 ///
91 /// This command will fail if any of the tracks are not registered in the
92 /// [music library](MusicLibrary), or if the list contains duplicates.
93 tracks: Option<Vec<PathBuf>>,
94 },
95 /// Import a playlist from an M3U8 file.
96 ///
97 /// This is currently unimplemented and will cause the `daemon` to panic.
98 PlaylistFromM3U8 {
99 /// The name for the imported playlist. Must not already exist in the music library.
100 ///
101 /// If a playlist with the specified name already exists,
102 /// [`LibraryError::PlaylistExists`] will be returned, and no changes to the
103 /// [music library](MusicLibrary) will be made.
104 name: String,
105 /// The path to the M3U8 file to import.
106 m3u8_file: PathBuf,
107 },
108 /// Delete playlists from the music library.
109 DeletePlaylists {
110 /// List of the playlists to delete.
111 ///
112 /// If any of the provided playlists don't exist in the music library,
113 /// [`LibraryError::NoSuchPlaylist`] will be returned, and no changes to the
114 /// [music library](MusicLibrary) will be made.
115 names: Vec<String>,
116 },
117 /// Add tracks to an already existing playlist.
118 AddTracksToPlaylist {
119 /// The name of the playlist to add tracks to.
120 ///
121 /// If a playlist with the specified name doesn't exist in the music library,
122 /// [`LibraryError::NoSuchPlaylist`] will be returned, and no changes to the
123 /// [music library](MusicLibrary) will be made.
124 name: String,
125 /// List of paths to tracks to add to the playlist.
126 ///
127 /// **Note:** the `daemon` will return an error if any of the tracks are not registered in
128 /// the music library or if the list contains duplicates.
129 tracks: Vec<PathBuf>,
130 },
131 /// Remove tracks from a playlist.
132 RemoveTracksFromPlaylist {
133 /// The name of the playlist to remove tracks from.
134 ///
135 /// If a playlist with the specified name doesn't exist in the music library,
136 /// [`LibraryError::NoSuchPlaylist`] will be returned, and no changes to the
137 /// [music library](MusicLibrary) will be made.
138 name: String,
139 /// The list of track indices in the playlist to remove.
140 ///
141 /// Eg. to remove the first track you would pass `[0]`, to remove the first three
142 /// `[0, 1, 2]`, etc.
143 ///
144 /// If one or more of the indices is out of range, [`LibraryError::IndexOutOfBounds`]
145 /// will be returned, and no changes to the [music library](MusicLibrary) will be made.
146 ids: Vec<usize>,
147 },
148 /// Get the contents of the [music library](MusicLibrary).
149 ///
150 /// The `daemon` will respond to this with [`Response::Library`](crate::Response::Library) if
151 /// successful.
152 GetLibrary,
153 /// Reload the library and rebuild the cache ignoring already cached covers.
154 ///
155 /// Will take more time than just reloading the cache.
156 Rebuild,
157 /// Reload the library using cached data if possible.
158 ///
159 /// This can be used to discover newly added tracks.
160 Reload,
161}
162
163/// An error originating from the music library module of the `daemon`.
164#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
165pub enum LibraryError {
166 /// Could not complete the operation because a [playlist](Playlist) with the provided name
167 /// already exists.
168 PlaylistExists,
169 /// Could not perform the operation because the [music library](MusicLibrary) is not
170 /// initialized.
171 ///
172 /// This can happen if a command is sent to early and the music library is not yet initialized.
173 LibraryNotInitialized,
174 /// There is no [playlist](Playlist) in the [music library](MusicLibrary) with the provided
175 /// name.
176 NoSuchPlaylist,
177 /// The provided item index was out of bounds.
178 IndexOutOfBounds,
179 /// The provided list contained duplicate values.
180 DuplicateItems,
181 /// The provided track is not registered in the library.
182 NoSuchTrack,
183 /// Could not read the contents of the library state file.
184 StateNotReadable,
185 /// Could not write the library state to a file.
186 StateWriteFailed,
187 /// The library state path is not a file.
188 StateNotAFile,
189}
190
191impl std::fmt::Display for LibraryError {
192 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
193 match self {
194 Self::PlaylistExists => write!(f, "Playlist with this name already exists"),
195 Self::LibraryNotInitialized => write!(f, "The music library is not initialized"),
196 Self::NoSuchPlaylist => write!(f, "There is no playlist with this name"),
197 Self::IndexOutOfBounds => write!(f, "The provided item index was out of bounds"),
198 Self::NoSuchTrack => {
199 write!(f, "The provided track is not registered in the library")
200 }
201 Self::DuplicateItems => write!(f, "The provided vector contained duplicate values"),
202 Self::StateNotReadable => {
203 write!(f, "Could not read the contents of the library state file")
204 }
205 Self::StateWriteFailed => write!(f, "Could not write the library state to a file"),
206 Self::StateNotAFile => write!(f, "The library state path is not a file"),
207 }
208 }
209}