Library now returns vector of Item
This commit is contained in:
parent
d20a9a9dec
commit
1e1dbe8688
76
src/lib.rs
76
src/lib.rs
@ -3,10 +3,13 @@
|
||||
pub mod database;
|
||||
pub mod library;
|
||||
|
||||
use std::fmt;
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
fmt,
|
||||
};
|
||||
|
||||
use database::IDatabase;
|
||||
use library::{ILibrary, Query};
|
||||
use library::{ILibrary, Item, Query};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -117,7 +120,8 @@ impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
|
||||
}
|
||||
|
||||
pub fn rescan_library(&mut self) -> Result<(), Error> {
|
||||
self.collection = self.library.list(&Query::new())?;
|
||||
let items = self.library.list(&Query::new())?;
|
||||
self.collection = Self::items_to_artists(items);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -129,6 +133,72 @@ impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
|
||||
pub fn get_collection(&self) -> &Collection {
|
||||
&self.collection
|
||||
}
|
||||
|
||||
fn items_to_artists(items: Vec<Item>) -> Vec<Artist> {
|
||||
let mut artists: Vec<Artist> = vec![];
|
||||
let mut album_ids = HashMap::<ArtistId, HashSet<AlbumId>>::new();
|
||||
|
||||
for item in items.into_iter() {
|
||||
let artist_id = ArtistId { name: item.album_artist };
|
||||
|
||||
let album_id = AlbumId {
|
||||
year: item.album_year,
|
||||
title: item.album_title,
|
||||
};
|
||||
|
||||
let track = Track {
|
||||
number: item.track_number,
|
||||
title: item.track_title,
|
||||
artist: item.track_artist,
|
||||
quality: Quality {
|
||||
format: item.track_format,
|
||||
bitrate: item.track_bitrate,
|
||||
},
|
||||
};
|
||||
|
||||
let artist = if album_ids.contains_key(&artist_id) {
|
||||
// Assume results are in some order which means they will likely be grouped by
|
||||
// artist. Therefore, we look from the back since the last inserted artist is most
|
||||
// likely the one we are looking for.
|
||||
artists
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|a| a.id == artist_id)
|
||||
.unwrap()
|
||||
} else {
|
||||
album_ids.insert(artist_id.clone(), HashSet::<AlbumId>::new());
|
||||
artists.push(Artist {
|
||||
id: artist_id.clone(),
|
||||
albums: vec![],
|
||||
});
|
||||
artists.last_mut().unwrap()
|
||||
};
|
||||
|
||||
if album_ids[&artist_id].contains(&album_id) {
|
||||
// Assume results are in some order which means they will likely be grouped by
|
||||
// album. Therefore, we look from the back since the last inserted album is most
|
||||
// likely the one we are looking for.
|
||||
let album = artist
|
||||
.albums
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|a| a.id == album_id)
|
||||
.unwrap();
|
||||
album.tracks.push(track);
|
||||
} else {
|
||||
album_ids
|
||||
.get_mut(&artist_id)
|
||||
.unwrap()
|
||||
.insert(album_id.clone());
|
||||
artist.albums.push(Album {
|
||||
id: album_id,
|
||||
tracks: vec![track],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
artists
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,17 +1,12 @@
|
||||
//! Module for interacting with the music library via
|
||||
//! [beets](https://beets.readthedocs.io/en/stable/).
|
||||
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
str,
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
use crate::{Album, AlbumId, Artist, ArtistId, Format, Quality, Track};
|
||||
use crate::Format;
|
||||
|
||||
use super::{Error, Field, ILibrary, Query};
|
||||
use super::{Error, Field, ILibrary, Item, Query};
|
||||
|
||||
pub mod executor;
|
||||
|
||||
@ -92,7 +87,7 @@ pub struct BeetsLibrary<BLE> {
|
||||
|
||||
trait ILibraryPrivate {
|
||||
fn list_cmd_and_args(query: &Query) -> Vec<String>;
|
||||
fn list_to_artists<S: AsRef<str>>(list_output: &[S]) -> Result<Vec<Artist>, Error>;
|
||||
fn list_to_items<S: AsRef<str>>(list_output: &[S]) -> Result<Vec<Item>, Error>;
|
||||
}
|
||||
|
||||
impl<BLE: IBeetsLibraryExecutor> BeetsLibrary<BLE> {
|
||||
@ -104,10 +99,10 @@ impl<BLE: IBeetsLibraryExecutor> BeetsLibrary<BLE> {
|
||||
}
|
||||
|
||||
impl<BLE: IBeetsLibraryExecutor> ILibrary for BeetsLibrary<BLE> {
|
||||
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error> {
|
||||
fn list(&mut self, query: &Query) -> Result<Vec<Item>, Error> {
|
||||
let cmd = Self::list_cmd_and_args(query);
|
||||
let output = self.executor.exec(&cmd)?;
|
||||
Self::list_to_artists(&output)
|
||||
Self::list_to_items(&output)
|
||||
}
|
||||
}
|
||||
|
||||
@ -119,9 +114,8 @@ impl<BLE: IBeetsLibraryExecutor> ILibraryPrivate for BeetsLibrary<BLE> {
|
||||
cmd
|
||||
}
|
||||
|
||||
fn list_to_artists<S: AsRef<str>>(list_output: &[S]) -> Result<Vec<Artist>, Error> {
|
||||
let mut artists: Vec<Artist> = vec![];
|
||||
let mut album_ids = HashMap::<ArtistId, HashSet<AlbumId>>::new();
|
||||
fn list_to_items<S: AsRef<str>>(list_output: &[S]) -> Result<Vec<Item>, Error> {
|
||||
let mut items: Vec<Item> = vec![];
|
||||
|
||||
for line in list_output.iter().map(|s| s.as_ref()) {
|
||||
if line.is_empty() {
|
||||
@ -138,69 +132,31 @@ impl<BLE: IBeetsLibraryExecutor> ILibraryPrivate for BeetsLibrary<BLE> {
|
||||
let album_title = split[2].to_string();
|
||||
let track_number = split[3].parse::<u32>()?;
|
||||
let track_title = split[4].to_string();
|
||||
let track_artist = split[5].to_string();
|
||||
let track_format = split[6].to_string();
|
||||
let track_artist = split[5]
|
||||
.to_string()
|
||||
.split("; ")
|
||||
.map(|s| s.to_owned())
|
||||
.collect();
|
||||
let track_format = match split[6].to_string().as_str() {
|
||||
TRACK_FORMAT_FLAC => Format::Flac,
|
||||
TRACK_FORMAT_MP3 => Format::Mp3,
|
||||
_ => return Err(Error::Invalid(line.to_string())),
|
||||
};
|
||||
let track_bitrate = split[7].trim_end_matches("kbps").parse::<u32>()?;
|
||||
|
||||
let artist_id = ArtistId { name: album_artist };
|
||||
|
||||
let album_id = AlbumId {
|
||||
year: album_year,
|
||||
title: album_title,
|
||||
};
|
||||
|
||||
let track = Track {
|
||||
number: track_number,
|
||||
title: track_title,
|
||||
artist: track_artist.split("; ").map(|s| s.to_owned()).collect(),
|
||||
quality: Quality {
|
||||
format: match track_format.as_ref() {
|
||||
TRACK_FORMAT_FLAC => Format::Flac,
|
||||
TRACK_FORMAT_MP3 => Format::Mp3,
|
||||
_ => return Err(Error::Invalid(line.to_string())),
|
||||
},
|
||||
bitrate: track_bitrate,
|
||||
},
|
||||
};
|
||||
|
||||
let artist = if album_ids.contains_key(&artist_id) {
|
||||
// Beets returns results in order so we look from the back.
|
||||
artists
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|a| a.id == artist_id)
|
||||
.unwrap()
|
||||
} else {
|
||||
album_ids.insert(artist_id.clone(), HashSet::<AlbumId>::new());
|
||||
artists.push(Artist {
|
||||
id: artist_id.clone(),
|
||||
albums: vec![],
|
||||
});
|
||||
artists.last_mut().unwrap()
|
||||
};
|
||||
|
||||
if album_ids[&artist_id].contains(&album_id) {
|
||||
// Beets returns results in order so we look from the back.
|
||||
let album = artist
|
||||
.albums
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|a| a.id == album_id)
|
||||
.unwrap();
|
||||
album.tracks.push(track);
|
||||
} else {
|
||||
album_ids
|
||||
.get_mut(&artist_id)
|
||||
.unwrap()
|
||||
.insert(album_id.clone());
|
||||
artist.albums.push(Album {
|
||||
id: album_id,
|
||||
tracks: vec![track],
|
||||
});
|
||||
}
|
||||
items.push(Item {
|
||||
album_artist,
|
||||
album_year,
|
||||
album_title,
|
||||
track_number,
|
||||
track_title,
|
||||
track_artist,
|
||||
track_format,
|
||||
track_bitrate,
|
||||
});
|
||||
}
|
||||
|
||||
Ok(artists)
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use std::{collections::HashSet, fmt, num::ParseIntError, str::Utf8Error};
|
||||
#[cfg(test)]
|
||||
use mockall::automock;
|
||||
|
||||
use crate::Artist;
|
||||
use crate::Format;
|
||||
|
||||
#[cfg(feature = "library-beets")]
|
||||
pub mod beets;
|
||||
@ -14,7 +14,19 @@ pub mod beets;
|
||||
#[cfg_attr(test, automock)]
|
||||
pub trait ILibrary {
|
||||
/// List lirbary items that match the a specific query.
|
||||
fn list(&mut self, query: &Query) -> Result<Vec<Artist>, Error>;
|
||||
fn list(&mut self, query: &Query) -> Result<Vec<Item>, Error>;
|
||||
}
|
||||
|
||||
/// An item from the library. An item corresponds to an individual file (usually a single track).
|
||||
pub struct Item {
|
||||
pub album_artist: String,
|
||||
pub album_year: u32,
|
||||
pub album_title: String,
|
||||
pub track_number: u32,
|
||||
pub track_title: String,
|
||||
pub track_artist: Vec<String>,
|
||||
pub track_format: Format,
|
||||
pub track_bitrate: u32,
|
||||
}
|
||||
|
||||
/// Individual fields that can be queried on.
|
||||
|
Loading…
Reference in New Issue
Block a user