1use std::{
2 ffi::OsString,
3 path::{Path, PathBuf},
4 time::Duration,
5};
6
7use anyhow::{Result, bail};
8use either::Either;
9use indoc::{formatdoc, indoc};
10use rusqlite::{Connection, OptionalExtension, Row, ToSql, named_params};
11
12use crate::new_database::{
13 artist_ops::{ArtistRead, common_row_to_artistread},
14 track_insert::{path_to_db_comp, validate_path},
15};
16
17use super::Integer;
18
19pub fn count_all_tracks(conn: &Connection) -> Result<Integer> {
21 let count = conn.query_row("SELECT COUNT(id) FROM tracks;", [], |v| v.get(0))?;
22
23 Ok(count)
24}
25
26pub fn count_all_track_metadata(conn: &Connection) -> Result<Integer> {
28 let count = conn.query_row("SELECT COUNT(track) FROM tracks_metadata;", [], |v| {
29 v.get(0)
30 })?;
31
32 Ok(count)
33}
34
35#[cfg(test)]
37pub(super) fn count_all_track_artist_mapping(conn: &Connection) -> Result<Integer> {
38 let count = conn.query_row("SELECT COUNT(track) FROM tracks_artists;", [], |v| v.get(0))?;
39
40 Ok(count)
41}
42
43#[derive(Debug, Clone, PartialEq)]
45pub struct AlbumRead {
46 pub id: Integer,
47
48 pub title: String,
49}
50
51#[derive(Debug, Clone, PartialEq)]
52pub struct TrackRead {
53 pub id: Integer,
54
55 pub file_dir: PathBuf,
57 pub file_stem: OsString,
58 pub file_ext: OsString,
59
60 pub duration: Option<Duration>,
62 pub last_position: Option<Duration>,
63 pub album: Option<AlbumRead>,
65
66 pub title: Option<String>,
68 pub genre: Option<String>,
69 pub artist_display: Option<String>,
70
71 pub artists: Vec<ArtistRead>,
73}
74
75impl TrackRead {
76 #[must_use]
78 pub fn as_pathbuf(&self) -> PathBuf {
79 let mut path = self.file_dir.clone();
80 let mut file_name = self.file_stem.clone();
81 file_name.reserve_exact(self.file_ext.len() + 1);
82 file_name.push(".");
83 file_name.push(&self.file_ext);
84 path.push(file_name);
85
86 path
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq)]
91pub enum RowOrdering {
92 IdAsc,
93 IdDesc,
94 AddedAsc,
95 AddedDesc,
96}
97
98impl RowOrdering {
99 fn as_sql(self) -> &'static str {
101 match self {
102 RowOrdering::IdAsc => "tracks.id ASC",
103 RowOrdering::IdDesc => "tracks.id DESC",
104 RowOrdering::AddedAsc => "tracks.added_at ASC",
105 RowOrdering::AddedDesc => "tracks.added_at DESC",
106 }
107 }
108}
109
110pub fn get_all_tracks(conn: &Connection, order: RowOrdering) -> Result<Vec<TrackRead>> {
116 let stmt = formatdoc! {"
117 SELECT
118 tracks.id AS track_id, tracks.file_dir, tracks.file_stem, tracks.file_ext, tracks.duration, tracks.last_position,
119 tracks_metadata.title AS track_title, tracks_metadata.artist_display, tracks_metadata.genre,
120 albums.id AS album_id, albums.title AS album_title
121 FROM tracks
122 LEFT JOIN tracks_metadata ON tracks.id = tracks_metadata.track
123 LEFT JOIN albums ON tracks.album = albums.id
124 ORDER BY {};
125 ",
126 order.as_sql()
127 };
128 let mut stmt = conn.prepare(&stmt)?;
129
130 let result: Vec<TrackRead> = stmt
131 .query_map(named_params! {}, |row| {
132 let trackread = common_row_to_trackread(conn, row);
133
134 Ok(trackread)
135 })?
136 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
137
138 Ok(result)
139}
140
141pub fn get_all_artists_for_track(conn: &Connection, track_id: Integer) -> Result<Vec<ArtistRead>> {
148 let mut stmt = conn.prepare(indoc! {"
149 SELECT artists.id AS artist_id, artists.artist FROM artists
150 INNER JOIN tracks_artists ON tracks_artists.track=:track_id
151 WHERE artists.id=tracks_artists.artist;
152 "})?;
153
154 let result: Vec<ArtistRead> = stmt
155 .query_map(named_params! {":track_id": track_id}, |row| {
156 let artist_read = common_row_to_artistread(row);
157
158 Ok(artist_read)
159 })?
160 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
161
162 Ok(result)
163}
164
165pub fn get_last_position(conn: &Connection, track: &Path) -> Result<Option<Duration>> {
171 let (file_dir, file_stem, file_ext) = path_to_db_comp(track)?;
172 let file_dir = file_dir.to_string_lossy();
173 let file_stem = file_stem.to_string_lossy();
174 let file_ext = file_ext.to_string_lossy();
175
176 let mut stmt = conn.prepare_cached(indoc!{"
177 SELECT last_position FROM tracks
178 WHERE tracks.file_dir=:file_dir AND tracks.file_stem=:file_stem AND tracks.file_ext=:file_ext;
179 "})?;
180
181 let result: Option<Integer> = stmt.query_row(
182 named_params! {":file_dir": file_dir, ":file_stem": file_stem, ":file_ext": file_ext},
183 |row| row.get(0),
184 )?;
185
186 let last_position = result.map(|v: Integer| {
187 let int = u64::try_from(v.max(0)).unwrap();
188 Duration::from_secs(int)
189 });
190
191 Ok(last_position)
192}
193
194pub fn set_last_position(conn: &Connection, track: &Path, to: Option<Duration>) -> Result<()> {
196 let (file_dir, file_stem, file_ext) = path_to_db_comp(track)?;
197 let file_dir = file_dir.to_string_lossy();
198 let file_stem = file_stem.to_string_lossy();
199 let file_ext = file_ext.to_string_lossy();
200
201 let last_position = to.map(|v| v.as_secs());
202
203 let mut stmt = conn.prepare_cached(indoc!{"
204 UPDATE tracks SET last_position=:last_position
205 WHERE tracks.file_dir=:file_dir AND tracks.file_stem=:file_stem AND tracks.file_ext=:file_ext;
206 "})?;
207
208 let affected = stmt.execute(named_params! {":file_dir": file_dir, ":file_stem": file_stem, ":file_ext": file_ext, ":last_position": last_position})?;
209
210 if affected == 0 {
212 bail!("Track not found");
213 }
214
215 Ok(())
216}
217
218pub fn get_tracks_from_album(
224 conn: &Connection,
225 album_title: &str,
226 album_artist: &str,
227 order: RowOrdering,
228) -> Result<Vec<TrackRead>> {
229 let stmt = formatdoc! {"
230 SELECT
231 tracks.id AS track_id, tracks.file_dir, tracks.file_stem, tracks.file_ext, tracks.duration, tracks.last_position,
232 tracks_metadata.title AS track_title, tracks_metadata.artist_display, tracks_metadata.genre,
233 albums.id AS album_id, albums.title AS album_title
234 FROM tracks
235 LEFT JOIN tracks_metadata ON tracks.id=tracks_metadata.track
236 LEFT JOIN albums ON tracks.album = albums.id
237 WHERE albums.title=:album_title AND albums.artist_display=:album_artist
238 ORDER BY {};
239 ",
240 order.as_sql()
241 };
242 let mut stmt = conn.prepare(&stmt)?;
243
244 let result: Vec<TrackRead> = stmt
245 .query_map(
246 named_params! {":album_title": album_title, ":album_artist": album_artist},
247 |row| {
248 let trackread = common_row_to_trackread(conn, row);
249
250 Ok(trackread)
251 },
252 )?
253 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
254
255 Ok(result)
256}
257
258pub fn get_tracks_from_artist(
264 conn: &Connection,
265 artist: &str,
266 order: RowOrdering,
267) -> Result<Vec<TrackRead>> {
268 let stmt = formatdoc! {"
269 SELECT
270 tracks.id AS track_id, tracks.file_dir, tracks.file_stem, tracks.file_ext, tracks.duration, tracks.last_position,
271 tracks_metadata.title AS track_title, tracks_metadata.artist_display, tracks_metadata.genre,
272 albums.id AS album_id, albums.title AS album_title
273 FROM tracks
274 LEFT JOIN tracks_metadata ON tracks.id=tracks_metadata.track
275 LEFT JOIN albums ON tracks.album = albums.id
276 INNER JOIN tracks_artists ON tracks.id = tracks_artists.track
277 INNER JOIN artists ON artists.id=tracks_artists.artist
278 WHERE artists.artist=:artist
279 ORDER BY {};
280 ",
281 order.as_sql()
282 };
283 let mut stmt = conn.prepare(&stmt)?;
284
285 let result: Vec<TrackRead> = stmt
286 .query_map(named_params! {":artist": artist}, |row| {
287 let trackread = common_row_to_trackread(conn, row);
288
289 Ok(trackread)
290 })?
291 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
292
293 Ok(result)
294}
295
296pub fn get_tracks_from_genre(
304 conn: &Connection,
305 genre: Option<&str>,
306 order: RowOrdering,
307) -> Result<Vec<TrackRead>> {
308 let (where_clause, params): (&str, &[(&str, &str)]) = if let Some(genre) = genre {
309 ("tracks_metadata.genre=:genre", &[(":genre", genre)])
310 } else {
311 ("tracks_metadata.genre IS NULL", &[])
312 };
313
314 let stmt = formatdoc! {"
315 SELECT
316 tracks.id AS track_id, tracks.file_dir, tracks.file_stem, tracks.file_ext, tracks.duration, tracks.last_position,
317 tracks_metadata.title AS track_title, tracks_metadata.artist_display, tracks_metadata.genre,
318 albums.id AS album_id, albums.title AS album_title
319 FROM tracks
320 INNER JOIN tracks_metadata ON tracks.id=tracks_metadata.track
321 LEFT JOIN albums ON tracks.album = albums.id
322 WHERE {where_clause}
323 ORDER BY {};
324 ",
325 order.as_sql()
326 };
327 let mut stmt = conn.prepare(&stmt)?;
328
329 let result: Vec<TrackRead> = stmt
330 .query_map(params, |row| {
331 let trackread = common_row_to_trackread(conn, row);
332
333 Ok(trackread)
334 })?
335 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
336
337 Ok(result)
338}
339
340pub fn get_tracks_from_directory(
346 conn: &Connection,
347 dir: &Path,
348 order: RowOrdering,
349) -> Result<Vec<TrackRead>> {
350 validate_path(dir)?;
351 let dir = dir.to_string_lossy();
352
353 let stmt = formatdoc! {"
354 SELECT
355 tracks.id AS track_id, tracks.file_dir, tracks.file_stem, tracks.file_ext, tracks.duration, tracks.last_position,
356 tracks_metadata.title AS track_title, tracks_metadata.artist_display, tracks_metadata.genre,
357 albums.id AS album_id, albums.title AS album_title
358 FROM tracks
359 LEFT JOIN tracks_metadata ON tracks.id=tracks_metadata.track
360 LEFT JOIN albums ON tracks.album = albums.id
361 WHERE tracks.file_dir=:dir
362 ORDER BY {};
363 ",
364 order.as_sql()
365 };
366 let mut stmt = conn.prepare(&stmt)?;
367
368 let result: Vec<TrackRead> = stmt
369 .query_map(named_params! {":dir": dir}, |row| {
370 let trackread = common_row_to_trackread(conn, row);
371
372 Ok(trackread)
373 })?
374 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
375
376 Ok(result)
377}
378
379pub fn get_tracks_from_genre_like(
385 conn: &Connection,
386 genre_like: &str,
387 order: RowOrdering,
388) -> Result<Vec<TrackRead>> {
389 let stmt = formatdoc! {"
390 SELECT
391 tracks.id AS track_id, tracks.file_dir, tracks.file_stem, tracks.file_ext, tracks.duration, tracks.last_position,
392 tracks_metadata.title AS track_title, tracks_metadata.artist_display, tracks_metadata.genre,
393 albums.id AS album_id, albums.title AS album_title
394 FROM tracks
395 INNER JOIN tracks_metadata ON tracks.id = tracks_metadata.track
396 LEFT JOIN albums ON tracks.album = albums.id
397 WHERE tracks_metadata.genre LIKE :genre_like
398 ORDER BY {};
399 ",
400 order.as_sql()
401 };
402 let mut stmt = conn.prepare(&stmt)?;
403
404 let result: Vec<TrackRead> = stmt
405 .query_map(named_params! {":genre_like": genre_like}, |row| {
406 let trackread = common_row_to_trackread(conn, row);
407
408 Ok(trackread)
409 })?
410 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
411
412 Ok(result)
413}
414
415pub fn get_track_from_path(conn: &Connection, path: &Path) -> Result<TrackRead> {
421 let (file_dir, file_stem, file_ext) = path_to_db_comp(path)?;
422 let file_dir = file_dir.to_string_lossy();
423 let file_stem = file_stem.to_string_lossy();
424 let file_ext = file_ext.to_string_lossy();
425
426 let mut stmt = conn.prepare(indoc! {"
427 SELECT
428 tracks.id AS track_id, tracks.file_dir, tracks.file_stem, tracks.file_ext, tracks.duration, tracks.last_position,
429 tracks_metadata.title AS track_title, tracks_metadata.artist_display, tracks_metadata.genre,
430 albums.id AS album_id, albums.title AS album_title
431 FROM tracks
432 INNER JOIN tracks_metadata ON tracks.id=tracks_metadata.track
433 LEFT JOIN albums ON tracks.album = albums.id
434 WHERE tracks.file_dir=:file_dir AND tracks.file_stem=:file_stem AND tracks.file_ext=:file_ext;
435 ",
436 })?;
437
438 let result: TrackRead = stmt.query_row(
439 named_params! {":file_dir": file_dir, ":file_stem": file_stem, ":file_ext": file_ext},
440 |row| {
441 let trackread = common_row_to_trackread(conn, row);
442
443 Ok(trackread)
444 },
445 )?;
446
447 Ok(result)
448}
449
450fn common_row_to_trackread(conn: &Connection, row: &Row<'_>) -> TrackRead {
454 let file_dir = row
455 .get("file_dir")
456 .map(|v: String| PathBuf::from(v))
457 .unwrap();
458 let file_stem = row
459 .get("file_stem")
460 .map(|v: String| OsString::from(v))
461 .unwrap();
462 let file_ext = row
463 .get("file_ext")
464 .map(|v: String| OsString::from(v))
465 .unwrap();
466 let id = row.get("track_id").unwrap();
467
468 let duration = row.get("duration").ok().map(|v: Integer| {
469 let int = u64::try_from(v.max(0)).unwrap();
470 Duration::from_secs(int)
471 });
472 let last_position = row.get("last_position").ok().map(|v: Integer| {
473 let int = u64::try_from(v.max(0)).unwrap();
474 Duration::from_secs(int)
475 });
476 let title = row.get("track_title").unwrap_or_default();
477 let genre = row.get("genre").unwrap_or_default();
478 let artist_display = row.get("artist_display").unwrap_or_default();
479
480 let album_id = row.get("album_id").ok();
481 let album_title = row.get("album_title").ok();
482
483 let album = if let (Some(album_id), Some(album_title)) = (album_id, album_title) {
484 Some(AlbumRead {
485 id: album_id,
486 title: album_title,
487 })
488 } else {
489 None
490 };
491
492 let artists = match get_all_artists_for_track(conn, id) {
493 Ok(v) => v,
494 Err(err) => {
495 warn!("Error resolving artists for a track: {err:#?}");
496 Vec::new()
497 }
498 };
499
500 TrackRead {
501 id,
502 file_dir,
503 file_stem,
504 file_ext,
505 duration,
506 last_position,
507 album,
508 title,
509 genre,
510 artist_display,
511 artists,
512 }
513}
514
515pub fn track_exists(conn: &Connection, track: &Path) -> Result<bool> {
521 let (file_dir, file_stem, file_ext) = path_to_db_comp(track)?;
522 let file_dir = file_dir.to_string_lossy();
523 let file_stem = file_stem.to_string_lossy();
524 let file_ext = file_ext.to_string_lossy();
525
526 let mut stmt = conn.prepare(indoc!{"
527 SELECT tracks.id
528 FROM tracks
529 WHERE tracks.file_dir=:file_dir AND tracks.file_stem=:file_stem AND tracks.file_ext=:file_ext;
530 "})?;
531
532 let exists = stmt.exists(
533 named_params! {":file_dir": file_dir, ":file_stem": file_stem, ":file_ext": file_ext},
534 )?;
535
536 Ok(exists)
537}
538
539pub fn all_distinct_genres(conn: &Connection) -> Result<Vec<String>> {
546 let mut stmt = conn.prepare(indoc! {"
547 SELECT DISTINCT tracks_metadata.genre
548 FROM tracks_metadata
549 ",
550 })?;
551
552 let result: Vec<String> = stmt
553 .query_map(named_params! {}, |row| {
554 let res = row.get(0).unwrap_or_else(|_| "[unknown]".to_string());
555 Ok(res)
556 })?
557 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
558
559 Ok(result)
560}
561
562pub fn all_distinct_directories(conn: &Connection) -> Result<Vec<String>> {
568 let mut stmt = conn.prepare(indoc! {"
569 SELECT DISTINCT tracks.file_dir
570 FROM tracks
571 ",
572 })?;
573
574 let result: Vec<String> = stmt
575 .query_map(named_params! {}, |row| {
576 let res = row.get(0).unwrap();
577 Ok(res)
578 })?
579 .collect::<Result<Vec<_>, rusqlite::Error>>()?;
580
581 Ok(result)
582}
583
584pub fn delete_tracks_artists_mapping_for(
592 conn: &Connection,
593 track: Either<&Path, Integer>,
594) -> Result<usize> {
595 let (where_clause, params): (&str, &[(&str, &dyn ToSql)]) = match track {
596 Either::Left(path) => {
597 let (file_dir, file_stem, file_ext) = path_to_db_comp(path)?;
598
599 let where_c = indoc! {"
600 (
601 SELECT tracks.id FROM tracks
602 WHERE tracks.file_dir=:file_dir AND tracks.file_stem=:file_stem AND tracks.file_ext=:file_ext
603 )
604 "};
605
606 (
608 where_c,
609 &[
610 (":file_dir", &file_dir.to_str().unwrap()),
611 (":file_stem", &file_stem.to_str().unwrap()),
612 (":file_ext", &file_ext.to_str().unwrap()),
613 ],
614 )
615 }
616 Either::Right(ref id) => (":track_id", &[(":track_id", id)]),
617 };
618
619 let stmt = formatdoc! {"
620 DELETE FROM tracks_artists
621 WHERE tracks_artists.track = {where_clause};
622 "};
623
624 let mut stmt = conn.prepare_cached(&stmt)?;
625
626 let affected = stmt.execute(params).optional()?.unwrap_or_default();
627
628 Ok(affected)
629}
630
631#[cfg(test)]
632mod tests {
633 use std::{
634 ffi::{OsStr, OsString},
635 path::{Path, PathBuf},
636 time::Duration,
637 };
638
639 use either::Either;
640 use pretty_assertions::assert_eq;
641
642 use crate::{
643 new_database::{
644 album_insert::AlbumInsertable,
645 artist_insert::ArtistInsertable,
646 test_utils::{gen_database, test_path},
647 track_insert::TrackInsertable,
648 track_ops::{
649 AlbumRead, ArtistRead, RowOrdering, TrackRead, all_distinct_directories,
650 all_distinct_genres, count_all_track_artist_mapping,
651 delete_tracks_artists_mapping_for, get_all_tracks, get_last_position,
652 get_track_from_path, get_tracks_from_album, get_tracks_from_artist,
653 get_tracks_from_directory, get_tracks_from_genre, get_tracks_from_genre_like,
654 set_last_position, track_exists,
655 },
656 },
657 track::TrackMetadata,
658 };
659
660 use super::get_all_artists_for_track;
661
662 #[test]
663 fn artists_for_track() {
664 let db = gen_database();
665
666 let track = TrackInsertable {
667 file_dir: Path::new("/somewhere"),
668 file_stem: OsStr::new("file"),
669 file_ext: OsStr::new("ext"),
670 duration: Some(Duration::from_secs(10)),
671 last_position: None,
672 album: Some(Either::Left(
673 AlbumInsertable {
674 title: "AlbumA",
675 artist_display: "ArtistA",
676 artists: vec![Either::Left(ArtistInsertable { artist: "ArtistA" }.into())],
677 }
678 .into(),
679 )),
680 title: Some("file test"),
681 genre: None,
682 artist_display: Some("ArtistA feat. ArtistB"),
683 artists: vec![
684 Either::Left(ArtistInsertable { artist: "ArtistA" }.into()),
685 Either::Left(ArtistInsertable { artist: "ArtistB" }.into()),
686 ],
687 };
688 let track_id = track.try_insert_or_update(&db.get_connection()).unwrap();
689
690 let mut all_artists: Vec<String> =
691 get_all_artists_for_track(&db.get_connection(), track_id)
692 .unwrap()
693 .into_iter()
694 .map(|v| v.name)
695 .collect();
696 all_artists.sort();
698
699 assert_eq!(all_artists, &["ArtistA", "ArtistB"]);
700 }
701
702 #[test]
703 fn all_tracks() {
704 let db = gen_database();
705
706 let track = TrackInsertable {
707 file_dir: Path::new("/somewhere"),
708 file_stem: OsStr::new("file"),
709 file_ext: OsStr::new("ext"),
710 duration: Some(Duration::from_secs(10)),
711 last_position: None,
712 album: Some(Either::Left(
713 AlbumInsertable {
714 title: "AlbumA",
715 artist_display: "ArtistA",
716 artists: vec![Either::Left(ArtistInsertable { artist: "ArtistA" }.into())],
717 }
718 .into(),
719 )),
720 title: Some("file test"),
721 genre: None,
722 artist_display: Some("ArtistA"),
723 artists: vec![Either::Left(ArtistInsertable { artist: "ArtistA" }.into())],
724 };
725 let _track_id = track.try_insert_or_update(&db.get_connection()).unwrap();
726
727 let all_tracks = get_all_tracks(&db.get_connection(), RowOrdering::IdAsc).unwrap();
728
729 assert_eq!(
730 all_tracks,
731 &[TrackRead {
732 id: 1,
733 file_dir: PathBuf::from("/somewhere"),
734 file_stem: OsString::from("file"),
735 file_ext: OsString::from("ext"),
736 duration: Some(Duration::from_secs(10)),
737 last_position: None,
738 album: Some(AlbumRead {
739 id: 1,
740 title: "AlbumA".to_string()
741 }),
742 title: Some("file test".to_string()),
743 genre: None,
744 artist_display: Some("ArtistA".to_string()),
745 artists: vec![ArtistRead {
746 id: 1,
747 name: "ArtistA".to_string()
748 }]
749 }]
750 );
751 }
752
753 #[test]
754 fn last_position_some() {
755 let db = gen_database();
756
757 let track = TrackInsertable {
758 file_dir: &test_path(Path::new("/somewhere")),
759 file_stem: OsStr::new("file"),
760 file_ext: OsStr::new("ext"),
761 duration: Some(Duration::from_secs(10)),
762 last_position: Some(Duration::from_secs(5)),
763 album: Some(Either::Left(
764 AlbumInsertable {
765 title: "AlbumA",
766 artist_display: "ArtistA",
767 artists: vec![Either::Left(ArtistInsertable { artist: "ArtistA" }.into())],
768 }
769 .into(),
770 )),
771 title: Some("file test"),
772 genre: None,
773 artist_display: Some("ArtistA"),
774 artists: vec![Either::Left(ArtistInsertable { artist: "ArtistA" }.into())],
775 };
776 let path = &test_path(Path::new("/somewhere/file.ext"));
777 let _track_id = track.try_insert_or_update(&db.get_connection()).unwrap();
778
779 let last_position = get_last_position(&db.get_connection(), path).unwrap();
780
781 assert_eq!(last_position, Some(Duration::from_secs(5)));
782
783 set_last_position(&db.get_connection(), path, None).unwrap();
784
785 let last_position = get_last_position(&db.get_connection(), path).unwrap();
786
787 assert_eq!(last_position, None);
788 }
789
790 #[test]
791 fn last_position_none() {
792 let db = gen_database();
793
794 let track = TrackInsertable {
795 file_dir: &test_path(Path::new("/somewhere")),
796 file_stem: OsStr::new("file"),
797 file_ext: OsStr::new("ext"),
798 duration: Some(Duration::from_secs(10)),
799 last_position: None,
800 album: Some(Either::Left(
801 AlbumInsertable {
802 title: "AlbumA",
803 artist_display: "ArtistA",
804 artists: vec![Either::Left(ArtistInsertable { artist: "ArtistA" }.into())],
805 }
806 .into(),
807 )),
808 title: Some("file test"),
809 genre: None,
810 artist_display: Some("ArtistA"),
811 artists: vec![Either::Left(ArtistInsertable { artist: "ArtistA" }.into())],
812 };
813 let path = &test_path(Path::new("/somewhere/file.ext"));
814 let _track_id = track.try_insert_or_update(&db.get_connection()).unwrap();
815
816 let last_position = get_last_position(&db.get_connection(), path).unwrap();
817
818 assert_eq!(last_position, None);
819
820 set_last_position(&db.get_connection(), path, Some(Duration::from_secs(5))).unwrap();
821
822 let last_position = get_last_position(&db.get_connection(), path).unwrap();
823
824 assert_eq!(last_position, Some(Duration::from_secs(5)));
825 }
826
827 #[test]
828 fn last_position_not_found() {
829 let db = gen_database();
830
831 let path = &test_path(Path::new("/somewhere/file.ext"));
832
833 let err = get_last_position(&db.get_connection(), path).unwrap_err();
835 let err = err.downcast::<rusqlite::Error>().unwrap();
836
837 assert_eq!(err, rusqlite::Error::QueryReturnedNoRows);
838
839 let err = set_last_position(&db.get_connection(), path, None).unwrap_err();
841
842 assert!(err.to_string().contains("Track not found"));
843 }
844
845 #[test]
846 fn tracks_by_album() {
847 let db = gen_database();
848
849 let metadata = TrackMetadata {
850 album: Some("AlbumA".to_string()),
851 album_artist: Some("ArtistA".to_string()),
852 album_artists: Some(vec!["ArtistA".to_string()]),
853 artist: Some("ArtistA".to_string()),
854 artists: Some(vec!["ArtistA".to_string()]),
855 title: Some("FileA1".to_string()),
856 duration: Some(Duration::from_secs(10)),
857 ..Default::default()
858 };
859 let path = &test_path(Path::new("/somewhere/fileA1.ext"));
860 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
861 let _ = insertable
862 .try_insert_or_update(&db.get_connection())
863 .unwrap();
864
865 let metadata = TrackMetadata {
866 album: Some("AlbumA".to_string()),
867 album_artist: Some("ArtistA".to_string()),
868 album_artists: Some(vec!["ArtistA".to_string()]),
869 artist: Some("ArtistA".to_string()),
870 artists: Some(vec!["ArtistA".to_string()]),
871 title: Some("FileA2".to_string()),
872 duration: Some(Duration::from_secs(10)),
873 ..Default::default()
874 };
875 let path = &test_path(Path::new("/somewhere/fileA2.ext"));
876 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
877 let _ = insertable
878 .try_insert_or_update(&db.get_connection())
879 .unwrap();
880
881 let metadata = TrackMetadata {
882 album: Some("AlbumB".to_string()),
883 album_artist: Some("ArtistA".to_string()),
884 album_artists: Some(vec!["ArtistA".to_string()]),
885 artist: Some("ArtistA".to_string()),
886 artists: Some(vec!["ArtistA".to_string()]),
887 title: Some("FileB1".to_string()),
888 duration: Some(Duration::from_secs(10)),
889 ..Default::default()
890 };
891 let path = &test_path(Path::new("/somewhere/fileB1.ext"));
892 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
893 let _ = insertable
894 .try_insert_or_update(&db.get_connection())
895 .unwrap();
896
897 let res = get_tracks_from_album(
898 &db.get_connection(),
899 "AlbumA",
900 "ArtistA",
901 RowOrdering::IdAsc,
902 )
903 .unwrap();
904 let res: Vec<String> = res.into_iter().map(|v| v.title.unwrap()).collect();
905
906 assert_eq!(&res, &["FileA1", "FileA2"]);
907 }
908
909 #[test]
910 fn tracks_by_artist() {
911 let db = gen_database();
912
913 let metadata = TrackMetadata {
914 album: Some("AlbumA".to_string()),
915 album_artist: Some("ArtistA".to_string()),
916 album_artists: Some(vec!["ArtistA".to_string()]),
917 artist: Some("ArtistA".to_string()),
918 artists: Some(vec!["ArtistA".to_string()]),
919 title: Some("FileA1".to_string()),
920 duration: Some(Duration::from_secs(10)),
921 ..Default::default()
922 };
923 let path = &test_path(Path::new("/somewhere/fileA1.ext"));
924 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
925 let _ = insertable
926 .try_insert_or_update(&db.get_connection())
927 .unwrap();
928
929 let metadata = TrackMetadata {
930 album: Some("AlbumA".to_string()),
931 album_artist: Some("ArtistB".to_string()),
932 album_artists: Some(vec!["ArtistB".to_string()]),
933 artist: Some("ArtistB".to_string()),
934 artists: Some(vec!["ArtistB".to_string()]),
935 title: Some("FileA2".to_string()),
936 duration: Some(Duration::from_secs(10)),
937 ..Default::default()
938 };
939 let path = &test_path(Path::new("/somewhere/fileA2.ext"));
940 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
941 let _ = insertable
942 .try_insert_or_update(&db.get_connection())
943 .unwrap();
944
945 let metadata = TrackMetadata {
946 album: Some("AlbumB".to_string()),
947 album_artist: Some("ArtistB".to_string()),
948 album_artists: Some(vec!["ArtistB".to_string()]),
949 artist: Some("ArtistB".to_string()),
950 artists: Some(vec!["ArtistB".to_string()]),
951 title: Some("FileB1".to_string()),
952 duration: Some(Duration::from_secs(10)),
953 ..Default::default()
954 };
955 let path = &test_path(Path::new("/somewhere/fileB1.ext"));
956 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
957 let _ = insertable
958 .try_insert_or_update(&db.get_connection())
959 .unwrap();
960
961 let res =
962 get_tracks_from_artist(&db.get_connection(), "ArtistB", RowOrdering::IdAsc).unwrap();
963 let res: Vec<String> = res.into_iter().map(|v| v.title.unwrap()).collect();
964
965 assert_eq!(&res, &["FileA2", "FileB1"]);
966 }
967
968 #[test]
969 fn tracks_by_genre() {
970 let db = gen_database();
971
972 let metadata = TrackMetadata {
973 artist: Some("ArtistA".to_string()),
974 artists: Some(vec!["ArtistA".to_string()]),
975 title: Some("FileA1".to_string()),
976 duration: Some(Duration::from_secs(10)),
977 genre: Some("Rock".to_string()),
978 ..Default::default()
979 };
980 let path = &test_path(Path::new("/somewhere/fileA1.ext"));
981 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
982 let _ = insertable
983 .try_insert_or_update(&db.get_connection())
984 .unwrap();
985
986 let metadata = TrackMetadata {
987 artist: Some("ArtistA".to_string()),
988 artists: Some(vec!["ArtistA".to_string()]),
989 title: Some("FileA2".to_string()),
990 duration: Some(Duration::from_secs(10)),
991 genre: Some("Pop".to_string()),
992 ..Default::default()
993 };
994 let path = &test_path(Path::new("/somewhere/fileA2.ext"));
995 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
996 let _ = insertable
997 .try_insert_or_update(&db.get_connection())
998 .unwrap();
999
1000 let metadata = TrackMetadata {
1001 artist: Some("ArtistA".to_string()),
1002 artists: Some(vec!["ArtistA".to_string()]),
1003 title: Some("FileB1".to_string()),
1004 duration: Some(Duration::from_secs(10)),
1005 genre: None,
1006 ..Default::default()
1007 };
1008 let path = &test_path(Path::new("/somewhere/fileB1.ext"));
1009 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1010 let _ = insertable
1011 .try_insert_or_update(&db.get_connection())
1012 .unwrap();
1013
1014 let res =
1015 get_tracks_from_genre(&db.get_connection(), Some("Rock"), RowOrdering::IdAsc).unwrap();
1016 let res: Vec<String> = res.into_iter().map(|v| v.title.unwrap()).collect();
1017
1018 assert_eq!(&res, &["FileA1"]);
1019 }
1020
1021 #[test]
1022 fn tracks_by_genre_like() {
1023 let db = gen_database();
1024
1025 let metadata = TrackMetadata {
1026 artist: Some("ArtistA".to_string()),
1027 artists: Some(vec!["ArtistA".to_string()]),
1028 title: Some("FileA1".to_string()),
1029 duration: Some(Duration::from_secs(10)),
1030 genre: Some("Rock".to_string()),
1031 ..Default::default()
1032 };
1033 let path = &test_path(Path::new("/somewhere/fileA1.ext"));
1034 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1035 let _ = insertable
1036 .try_insert_or_update(&db.get_connection())
1037 .unwrap();
1038
1039 let metadata = TrackMetadata {
1040 artist: Some("ArtistA".to_string()),
1041 artists: Some(vec!["ArtistA".to_string()]),
1042 title: Some("FileA2".to_string()),
1043 duration: Some(Duration::from_secs(10)),
1044 genre: Some("Pop".to_string()),
1045 ..Default::default()
1046 };
1047 let path = &test_path(Path::new("/somewhere/fileA2.ext"));
1048 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1049 let _ = insertable
1050 .try_insert_or_update(&db.get_connection())
1051 .unwrap();
1052
1053 let metadata = TrackMetadata {
1054 artist: Some("ArtistA".to_string()),
1055 artists: Some(vec!["ArtistA".to_string()]),
1056 title: Some("FileB1".to_string()),
1057 duration: Some(Duration::from_secs(10)),
1058 genre: None,
1059 ..Default::default()
1060 };
1061 let path = &test_path(Path::new("/somewhere/fileB1.ext"));
1062 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1063 let _ = insertable
1064 .try_insert_or_update(&db.get_connection())
1065 .unwrap();
1066
1067 let res =
1068 get_tracks_from_genre_like(&db.get_connection(), "%pop%", RowOrdering::IdAsc).unwrap();
1069 let res: Vec<String> = res.into_iter().map(|v| v.title.unwrap()).collect();
1070
1071 assert_eq!(&res, &["FileA2"]);
1072 }
1073
1074 #[test]
1075 fn tracks_by_genre_null() {
1076 let db = gen_database();
1077
1078 let metadata = TrackMetadata {
1079 artist: Some("ArtistA".to_string()),
1080 artists: Some(vec!["ArtistA".to_string()]),
1081 title: Some("FileA1".to_string()),
1082 duration: Some(Duration::from_secs(10)),
1083 genre: Some("Rock".to_string()),
1084 ..Default::default()
1085 };
1086 let path = &test_path(Path::new("/somewhere/fileA1.ext"));
1087 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1088 let _ = insertable
1089 .try_insert_or_update(&db.get_connection())
1090 .unwrap();
1091
1092 let metadata = TrackMetadata {
1093 artist: Some("ArtistA".to_string()),
1094 artists: Some(vec!["ArtistA".to_string()]),
1095 title: Some("FileA2".to_string()),
1096 duration: Some(Duration::from_secs(10)),
1097 genre: Some("Pop".to_string()),
1098 ..Default::default()
1099 };
1100 let path = &test_path(Path::new("/somewhere/fileA2.ext"));
1101 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1102 let _ = insertable
1103 .try_insert_or_update(&db.get_connection())
1104 .unwrap();
1105
1106 let metadata = TrackMetadata {
1107 artist: Some("ArtistA".to_string()),
1108 artists: Some(vec!["ArtistA".to_string()]),
1109 title: Some("FileB1".to_string()),
1110 duration: Some(Duration::from_secs(10)),
1111 genre: None,
1112 ..Default::default()
1113 };
1114 let path = &test_path(Path::new("/somewhere/fileB1.ext"));
1115 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1116 let _ = insertable
1117 .try_insert_or_update(&db.get_connection())
1118 .unwrap();
1119
1120 let res = get_tracks_from_genre(&db.get_connection(), None, RowOrdering::IdAsc).unwrap();
1121 let res: Vec<String> = res.into_iter().map(|v| v.title.unwrap()).collect();
1122
1123 assert_eq!(&res, &["FileB1"]);
1124 }
1125
1126 #[test]
1127 fn genre_distinct() {
1128 let db = gen_database();
1129
1130 let metadata = TrackMetadata {
1131 artist: Some("ArtistA".to_string()),
1132 artists: Some(vec!["ArtistA".to_string()]),
1133 title: Some("FileA1".to_string()),
1134 duration: Some(Duration::from_secs(10)),
1135 genre: Some("Rock".to_string()),
1136 ..Default::default()
1137 };
1138 let path = &test_path(Path::new("/somewhere/fileA1.ext"));
1139 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1140 let _ = insertable
1141 .try_insert_or_update(&db.get_connection())
1142 .unwrap();
1143
1144 let metadata = TrackMetadata {
1145 artist: Some("ArtistA".to_string()),
1146 artists: Some(vec!["ArtistA".to_string()]),
1147 title: Some("FileA2".to_string()),
1148 duration: Some(Duration::from_secs(10)),
1149 genre: Some("Pop".to_string()),
1150 ..Default::default()
1151 };
1152 let path = &test_path(Path::new("/somewhere/fileA2.ext"));
1153 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1154 let _ = insertable
1155 .try_insert_or_update(&db.get_connection())
1156 .unwrap();
1157
1158 let metadata = TrackMetadata {
1159 artist: Some("ArtistA".to_string()),
1160 artists: Some(vec!["ArtistA".to_string()]),
1161 title: Some("FileB1".to_string()),
1162 duration: Some(Duration::from_secs(10)),
1163 genre: None,
1164 ..Default::default()
1165 };
1166 let path = &test_path(Path::new("/somewhere/fileB1.ext"));
1167 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1168 let _ = insertable
1169 .try_insert_or_update(&db.get_connection())
1170 .unwrap();
1171
1172 let metadata = TrackMetadata {
1173 artist: Some("ArtistA".to_string()),
1174 artists: Some(vec!["ArtistA".to_string()]),
1175 title: Some("FileB2".to_string()),
1176 duration: Some(Duration::from_secs(10)),
1177 genre: Some("Rock".to_string()),
1178 ..Default::default()
1179 };
1180 let path = &test_path(Path::new("/somewhere/fileB2.ext"));
1181 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1182 let _ = insertable
1183 .try_insert_or_update(&db.get_connection())
1184 .unwrap();
1185
1186 let res = all_distinct_genres(&db.get_connection()).unwrap();
1187
1188 assert_eq!(&res, &["Rock", "Pop", "[unknown]"]);
1189 }
1190
1191 #[test]
1192 fn exists() {
1193 let db = gen_database();
1194
1195 let metadata = TrackMetadata {
1196 title: Some("FileA1".to_string()),
1197 duration: Some(Duration::from_secs(10)),
1198 ..Default::default()
1199 };
1200 let path = &test_path(Path::new("/somewhere/fileA1.ext"));
1201 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1202 let _ = insertable
1203 .try_insert_or_update(&db.get_connection())
1204 .unwrap();
1205
1206 let res = track_exists(&db.get_connection(), path).unwrap();
1207
1208 assert!(res);
1209
1210 let res = track_exists(
1211 &db.get_connection(),
1212 &test_path(Path::new("/somewhere/else.ext")),
1213 )
1214 .unwrap();
1215
1216 assert!(!res);
1217 }
1218
1219 #[test]
1220 fn single_track() {
1221 let db = gen_database();
1222
1223 let metadata = TrackMetadata {
1224 title: Some("FileA1".to_string()),
1225 duration: Some(Duration::from_secs(10)),
1226 ..Default::default()
1227 };
1228 let path = &test_path(Path::new("/somewhere/fileA1.ext"));
1229 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1230 let _ = insertable
1231 .try_insert_or_update(&db.get_connection())
1232 .unwrap();
1233
1234 let res = get_track_from_path(&db.get_connection(), path).unwrap();
1235
1236 assert_eq!(res.title, Some("FileA1".to_string()));
1237
1238 let err = get_track_from_path(
1239 &db.get_connection(),
1240 &test_path(Path::new("/somewhere/else.ext")),
1241 )
1242 .unwrap_err();
1243 let err = err.downcast::<rusqlite::Error>().unwrap();
1244
1245 assert_eq!(err, rusqlite::Error::QueryReturnedNoRows);
1246 }
1247
1248 #[test]
1249 fn track_read_to_path() {
1250 let read = TrackRead {
1251 id: 0,
1252 file_dir: PathBuf::from("/path/to/somewhere"),
1253 file_stem: OsString::from("filename"),
1254 file_ext: OsString::from("ext"),
1255 duration: None,
1256 last_position: None,
1257 album: None,
1258 title: None,
1259 genre: None,
1260 artist_display: None,
1261 artists: Vec::new(),
1262 };
1263
1264 assert_eq!(
1265 read.as_pathbuf(),
1266 PathBuf::from("/path/to/somewhere/filename.ext")
1267 );
1268 }
1269
1270 #[test]
1271 fn distinct_directories() {
1272 let db = gen_database();
1273
1274 let metadata = TrackMetadata {
1275 album: Some("AlbumA".to_string()),
1276 album_artist: Some("ArtistA".to_string()),
1277 album_artists: Some(vec!["ArtistA".to_string()]),
1278 artist: Some("ArtistA".to_string()),
1279 artists: Some(vec!["ArtistA".to_string()]),
1280 title: Some("FileA1".to_string()),
1281 duration: Some(Duration::from_secs(10)),
1282 ..Default::default()
1283 };
1284 let path = &test_path(Path::new("/somewhere/dirA/fileA1.ext"));
1285 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1286 let _ = insertable
1287 .try_insert_or_update(&db.get_connection())
1288 .unwrap();
1289
1290 let metadata = TrackMetadata {
1291 album: Some("AlbumA".to_string()),
1292 album_artist: Some("ArtistA".to_string()),
1293 album_artists: Some(vec!["ArtistA".to_string()]),
1294 artist: Some("ArtistA".to_string()),
1295 artists: Some(vec!["ArtistA".to_string()]),
1296 title: Some("FileA2".to_string()),
1297 duration: Some(Duration::from_secs(10)),
1298 ..Default::default()
1299 };
1300 let path = &test_path(Path::new("/somewhere/dirA/fileA2.ext"));
1301 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1302 let _ = insertable
1303 .try_insert_or_update(&db.get_connection())
1304 .unwrap();
1305
1306 let metadata = TrackMetadata {
1307 album: Some("AlbumB".to_string()),
1308 album_artist: Some("ArtistA".to_string()),
1309 album_artists: Some(vec!["ArtistA".to_string()]),
1310 artist: Some("ArtistA".to_string()),
1311 artists: Some(vec!["ArtistA".to_string()]),
1312 title: Some("FileB1".to_string()),
1313 duration: Some(Duration::from_secs(10)),
1314 ..Default::default()
1315 };
1316 let path = &test_path(Path::new("/somewhere/dirB/fileB1.ext"));
1317 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1318 let _ = insertable
1319 .try_insert_or_update(&db.get_connection())
1320 .unwrap();
1321
1322 let res = all_distinct_directories(&db.get_connection()).unwrap();
1323
1324 assert_eq!(
1325 &res,
1326 &[
1327 test_path(Path::new("/somewhere/dirA")).to_string_lossy(),
1328 test_path(Path::new("/somewhere/dirB")).to_string_lossy()
1329 ]
1330 );
1331 }
1332
1333 #[test]
1334 fn tracks_by_directory() {
1335 let db = gen_database();
1336
1337 let metadata = TrackMetadata {
1338 album: Some("AlbumA".to_string()),
1339 album_artist: Some("ArtistA".to_string()),
1340 album_artists: Some(vec!["ArtistA".to_string()]),
1341 artist: Some("ArtistA".to_string()),
1342 artists: Some(vec!["ArtistA".to_string()]),
1343 title: Some("FileA1".to_string()),
1344 duration: Some(Duration::from_secs(10)),
1345 ..Default::default()
1346 };
1347 let path = &test_path(Path::new("/somewhere/dirA/fileA1.ext"));
1348 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1349 let _ = insertable
1350 .try_insert_or_update(&db.get_connection())
1351 .unwrap();
1352
1353 let metadata = TrackMetadata {
1354 album: Some("AlbumA".to_string()),
1355 album_artist: Some("ArtistA".to_string()),
1356 album_artists: Some(vec!["ArtistA".to_string()]),
1357 artist: Some("ArtistA".to_string()),
1358 artists: Some(vec!["ArtistA".to_string()]),
1359 title: Some("FileA2".to_string()),
1360 duration: Some(Duration::from_secs(10)),
1361 ..Default::default()
1362 };
1363 let path = &test_path(Path::new("/somewhere/dirA/fileA2.ext"));
1364 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1365 let _ = insertable
1366 .try_insert_or_update(&db.get_connection())
1367 .unwrap();
1368
1369 let metadata = TrackMetadata {
1370 album: Some("AlbumB".to_string()),
1371 album_artist: Some("ArtistA".to_string()),
1372 album_artists: Some(vec!["ArtistA".to_string()]),
1373 artist: Some("ArtistA".to_string()),
1374 artists: Some(vec!["ArtistA".to_string()]),
1375 title: Some("FileB1".to_string()),
1376 duration: Some(Duration::from_secs(10)),
1377 ..Default::default()
1378 };
1379 let path = &test_path(Path::new("/somewhere/dirB/fileB1.ext"));
1380 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1381 let _ = insertable
1382 .try_insert_or_update(&db.get_connection())
1383 .unwrap();
1384
1385 let res = get_tracks_from_directory(
1386 &db.get_connection(),
1387 &test_path(Path::new("/somewhere/dirA")),
1388 RowOrdering::IdAsc,
1389 )
1390 .unwrap();
1391 let res: Vec<String> = res.into_iter().map(|v| v.title.unwrap()).collect();
1392
1393 assert_eq!(&res, &["FileA1", "FileA2"]);
1394 }
1395
1396 #[test]
1397 fn delete_tracks_artists_mapping() {
1398 let db = gen_database();
1399
1400 let metadata = TrackMetadata {
1401 artist: Some("ArtistA feat. ArtistB".to_string()),
1402 artists: Some(vec!["ArtistA".to_string(), "ArtistB".to_string()]),
1403 title: Some("FileA1".to_string()),
1404 duration: Some(Duration::from_secs(10)),
1405 ..Default::default()
1406 };
1407 let path_a1 = &test_path(Path::new("/somewhere/fileA1.ext"));
1408 let insertable = TrackInsertable::try_from_track(path_a1, &metadata).unwrap();
1409 let _ = insertable
1410 .try_insert_or_update(&db.get_connection())
1411 .unwrap();
1412
1413 let metadata = TrackMetadata {
1414 artist: Some("ArtistA feat. ArtistB".to_string()),
1415 artists: Some(vec!["ArtistA".to_string(), "ArtistB".to_string()]),
1416 title: Some("FileB1".to_string()),
1417 duration: Some(Duration::from_secs(10)),
1418 ..Default::default()
1419 };
1420 let path = &test_path(Path::new("/somewhere/fileB1.ext"));
1421 let insertable = TrackInsertable::try_from_track(path, &metadata).unwrap();
1422 let track2_id = insertable
1423 .try_insert_or_update(&db.get_connection())
1424 .unwrap();
1425
1426 let mapping_counts = count_all_track_artist_mapping(&db.get_connection()).unwrap();
1427
1428 assert_eq!(mapping_counts, 4);
1429
1430 let affected =
1431 delete_tracks_artists_mapping_for(&db.get_connection(), Either::Left(path_a1)).unwrap();
1432
1433 assert_eq!(affected, 2);
1434
1435 let mapping_counts = count_all_track_artist_mapping(&db.get_connection()).unwrap();
1436
1437 assert_eq!(mapping_counts, 2);
1438
1439 let affected =
1440 delete_tracks_artists_mapping_for(&db.get_connection(), Either::Right(track2_id))
1441 .unwrap();
1442
1443 assert_eq!(affected, 2);
1444
1445 let mapping_counts = count_all_track_artist_mapping(&db.get_connection()).unwrap();
1446
1447 assert_eq!(mapping_counts, 0);
1448 }
1449}