1use std::{
2 ffi::OsStr,
3 path::Path,
4 time::{Duration, UNIX_EPOCH},
5};
6
7use indoc::indoc;
8use rusqlite::{named_params, Connection, Row};
9
10use crate::track::{Track, TrackMetadata};
11
12#[derive(Clone, Debug, PartialEq)]
14pub struct TrackDB {
15 pub id: u64,
16 pub artist: String,
17 pub title: String,
18 pub album: String,
19 pub genre: String,
20 pub file: String,
21 pub duration: Duration,
22 pub name: String,
23 pub ext: String,
24 pub directory: String,
25 pub last_modified: String,
26 pub last_position: Duration,
27}
28
29impl TrackDB {
30 pub fn try_from_row_id(row: &Row<'_>) -> Result<Self, rusqlite::Error> {
34 let d_u64: u64 = row.get(6)?;
35 let last_position_u64: u64 = row.get(11)?;
36 Ok(TrackDB {
37 id: row.get(0)?,
38 artist: row.get(1)?,
39 title: row.get(2)?,
40 album: row.get(3)?,
41 genre: row.get(4)?,
42 file: row.get(5)?,
43 duration: Duration::from_secs(d_u64),
44 name: row.get(7)?,
45 ext: row.get(8)?,
46 directory: row.get(9)?,
47 last_modified: row.get(10)?,
48 last_position: Duration::from_secs(last_position_u64),
49 })
50 }
51
52 pub fn try_from_row_named(row: &Row<'_>) -> Result<Self, rusqlite::Error> {
54 let d_u64: u64 = row.get("duration")?;
56 let last_position_u64: u64 = row.get("last_position")?;
57 Ok(TrackDB {
58 id: row.get("id")?,
59 artist: row.get("artist")?,
60 title: row.get("title")?,
61 album: row.get("album")?,
62 genre: row.get("genre")?,
63 file: row.get("file")?,
64 duration: Duration::from_secs(d_u64),
65 name: row.get("name")?,
66 ext: row.get("ext")?,
67 directory: row.get("directory")?,
68 last_modified: row.get("last_modified")?,
69 last_position: Duration::from_secs(last_position_u64),
70 })
71 }
72}
73
74#[derive(Clone, Debug)]
78pub struct TrackDBInsertable<'a> {
79 pub artist: &'a str,
82 pub title: &'a str,
83 pub album: &'a str,
84 pub genre: &'a str,
85 pub file: &'a str,
86 pub duration: Duration,
87 pub name: &'a str,
88 pub ext: &'a str,
89 pub directory: &'a str,
90 pub last_modified: String,
91 pub last_position: Duration,
92}
93
94pub mod const_unknown {
96 use crate::const_str;
97
98 const_str! {
99 UNKNOWN_ARTIST "Unknown Artist",
100 UNKNOWN_TITLE "Unknown Title",
101 UNKNOWN_ALBUM "empty",
102 UNKNOWN_GENRE "no type",
103 UNKNOWN_FILE "Unknown File",
104 }
105
106 #[allow(unused)]
111 pub const OLD_UNSUPPORTED: &str = "Unsupported?";
112
113 }
116use const_unknown::{UNKNOWN_ALBUM, UNKNOWN_ARTIST, UNKNOWN_FILE, UNKNOWN_GENRE, UNKNOWN_TITLE};
117
118impl<'a> TrackDBInsertable<'a> {
119 pub fn from_track_metadata(value: &'a TrackMetadata, path: &'a Path) -> Self {
120 let name = path.file_stem().and_then(OsStr::to_str).unwrap_or_default();
121 let ext = path.extension().and_then(OsStr::to_str).unwrap_or_default();
122 let directory = path.parent().and_then(Path::to_str).unwrap_or_default();
123
124 Self {
125 artist: value.artist.as_deref().unwrap_or(UNKNOWN_ARTIST),
126 title: value.title.as_deref().unwrap_or(UNKNOWN_TITLE),
127 album: value.album.as_deref().unwrap_or(UNKNOWN_ALBUM),
128 genre: value.genre.as_deref().unwrap_or(UNKNOWN_GENRE),
129 file: path.to_str().unwrap_or(UNKNOWN_FILE),
130 duration: value.duration.unwrap_or_default(),
131 name,
132 ext,
133 directory,
134 last_modified: value
135 .file_times
136 .as_ref()
137 .and_then(|v| v.modified)
138 .and_then(|v| v.duration_since(UNIX_EPOCH).ok())
139 .unwrap_or_default()
140 .as_secs()
141 .to_string(),
142 last_position: Duration::default(),
143 }
144 }
145}
146
147impl TrackDBInsertable<'_> {
148 #[inline]
150 pub fn insert_track(&self, con: &Connection) -> Result<usize, rusqlite::Error> {
151 con.execute(indoc! {"
152 INSERT INTO tracks (artist, title, album, genre, file, duration, name, ext, directory, last_modified, last_position)
153 VALUES (:artist, :title, :album, :genre, :file, :duration, :name, :ext, :directory, :last_modified, :last_position);
154 "},
155 named_params![
156 ":artist": &self.artist,
157 ":title": &self.title,
158 ":album": &self.album,
159 ":genre": &self.genre,
160 ":file": &self.file,
161 ":duration": &self.duration.as_secs(),
162 ":name": &self.name,
163 ":ext": &self.ext,
164 ":directory": &self.directory,
165 ":last_modified": &self.last_modified,
166 ":last_position": &self.last_position.as_secs().to_string(),
167 ],
168 )
169 }
170}
171
172pub trait Indexable {
177 fn meta_file(&self) -> Option<&str>;
178 fn meta_title(&self) -> Option<&str>;
179 fn meta_album(&self) -> Option<&str>;
180 fn meta_artist(&self) -> Option<&str>;
181 fn meta_duration(&self) -> Duration;
182}
183
184impl Indexable for TrackDB {
185 fn meta_file(&self) -> Option<&str> {
186 if self.file == UNKNOWN_FILE {
187 return None;
188 }
189 Some(&self.file)
190 }
191 fn meta_title(&self) -> Option<&str> {
192 if self.title == UNKNOWN_TITLE {
193 return None;
194 }
195 Some(&self.title)
196 }
197 fn meta_album(&self) -> Option<&str> {
198 if self.album == UNKNOWN_ALBUM {
199 return None;
200 }
201 Some(&self.album)
202 }
203 fn meta_artist(&self) -> Option<&str> {
204 if self.artist == UNKNOWN_ARTIST {
205 return None;
206 }
207 Some(&self.artist)
208 }
209
210 fn meta_duration(&self) -> Duration {
211 self.duration
212 }
213}
214
215impl Indexable for &TrackDB {
216 fn meta_file(&self) -> Option<&str> {
217 if self.file == UNKNOWN_FILE {
218 return None;
219 }
220 Some(&self.file)
221 }
222
223 fn meta_title(&self) -> Option<&str> {
224 if self.title == UNKNOWN_TITLE {
225 return None;
226 }
227 Some(&self.title)
228 }
229
230 fn meta_album(&self) -> Option<&str> {
231 if self.album == UNKNOWN_ALBUM {
232 return None;
233 }
234 Some(&self.album)
235 }
236
237 fn meta_artist(&self) -> Option<&str> {
238 if self.artist == UNKNOWN_ARTIST {
239 return None;
240 }
241 Some(&self.artist)
242 }
243
244 fn meta_duration(&self) -> Duration {
245 self.duration
246 }
247}
248
249impl Indexable for Track {
250 fn meta_file(&self) -> Option<&str> {
251 self.as_track().and_then(|v| v.path().to_str())
252 }
253
254 fn meta_title(&self) -> Option<&str> {
255 self.title()
256 }
257
258 fn meta_album(&self) -> Option<&str> {
259 self.as_track().and_then(|v| v.album())
260 }
261
262 fn meta_artist(&self) -> Option<&str> {
263 self.artist()
264 }
265
266 fn meta_duration(&self) -> Duration {
267 self.duration().unwrap_or_default()
268 }
269}
270
271impl Indexable for &Track {
272 fn meta_file(&self) -> Option<&str> {
273 self.as_track().and_then(|v| v.path().to_str())
274 }
275
276 fn meta_title(&self) -> Option<&str> {
277 self.title()
278 }
279
280 fn meta_album(&self) -> Option<&str> {
281 self.as_track().and_then(|v| v.album())
282 }
283
284 fn meta_artist(&self) -> Option<&str> {
285 self.artist()
286 }
287
288 fn meta_duration(&self) -> Duration {
289 self.duration().unwrap_or_default()
290 }
291}