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 database;
|
||||||
pub mod library;
|
pub mod library;
|
||||||
|
|
||||||
use std::fmt;
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt,
|
||||||
|
};
|
||||||
|
|
||||||
use database::IDatabase;
|
use database::IDatabase;
|
||||||
use library::{ILibrary, Query};
|
use library::{ILibrary, Item, Query};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@ -117,7 +120,8 @@ impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn rescan_library(&mut self) -> Result<(), Error> {
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,6 +133,72 @@ impl<LIB: ILibrary, DB: IDatabase> MusicHoard<LIB, DB> {
|
|||||||
pub fn get_collection(&self) -> &Collection {
|
pub fn get_collection(&self) -> &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)]
|
#[cfg(test)]
|
||||||
|
@ -1,17 +1,12 @@
|
|||||||
//! Module for interacting with the music library via
|
//! Module for interacting with the music library via
|
||||||
//! [beets](https://beets.readthedocs.io/en/stable/).
|
//! [beets](https://beets.readthedocs.io/en/stable/).
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
str,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
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;
|
pub mod executor;
|
||||||
|
|
||||||
@ -92,7 +87,7 @@ pub struct BeetsLibrary<BLE> {
|
|||||||
|
|
||||||
trait ILibraryPrivate {
|
trait ILibraryPrivate {
|
||||||
fn list_cmd_and_args(query: &Query) -> Vec<String>;
|
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> {
|
impl<BLE: IBeetsLibraryExecutor> BeetsLibrary<BLE> {
|
||||||
@ -104,10 +99,10 @@ impl<BLE: IBeetsLibraryExecutor> BeetsLibrary<BLE> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<BLE: IBeetsLibraryExecutor> ILibrary for 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 cmd = Self::list_cmd_and_args(query);
|
||||||
let output = self.executor.exec(&cmd)?;
|
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
|
cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
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> {
|
||||||
let mut artists: Vec<Artist> = vec![];
|
let mut items: Vec<Item> = vec![];
|
||||||
let mut album_ids = HashMap::<ArtistId, HashSet<AlbumId>>::new();
|
|
||||||
|
|
||||||
for line in list_output.iter().map(|s| s.as_ref()) {
|
for line in list_output.iter().map(|s| s.as_ref()) {
|
||||||
if line.is_empty() {
|
if line.is_empty() {
|
||||||
@ -138,69 +132,31 @@ impl<BLE: IBeetsLibraryExecutor> ILibraryPrivate for BeetsLibrary<BLE> {
|
|||||||
let album_title = split[2].to_string();
|
let album_title = split[2].to_string();
|
||||||
let track_number = split[3].parse::<u32>()?;
|
let track_number = split[3].parse::<u32>()?;
|
||||||
let track_title = split[4].to_string();
|
let track_title = split[4].to_string();
|
||||||
let track_artist = split[5].to_string();
|
let track_artist = split[5]
|
||||||
let track_format = split[6].to_string();
|
.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 track_bitrate = split[7].trim_end_matches("kbps").parse::<u32>()?;
|
||||||
|
|
||||||
let artist_id = ArtistId { name: album_artist };
|
items.push(Item {
|
||||||
|
album_artist,
|
||||||
let album_id = AlbumId {
|
album_year,
|
||||||
year: album_year,
|
album_title,
|
||||||
title: album_title,
|
track_number,
|
||||||
};
|
track_title,
|
||||||
|
track_artist,
|
||||||
let track = Track {
|
track_format,
|
||||||
number: track_number,
|
track_bitrate,
|
||||||
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],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(artists)
|
Ok(items)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use std::{collections::HashSet, fmt, num::ParseIntError, str::Utf8Error};
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use mockall::automock;
|
use mockall::automock;
|
||||||
|
|
||||||
use crate::Artist;
|
use crate::Format;
|
||||||
|
|
||||||
#[cfg(feature = "library-beets")]
|
#[cfg(feature = "library-beets")]
|
||||||
pub mod beets;
|
pub mod beets;
|
||||||
@ -14,7 +14,19 @@ pub mod beets;
|
|||||||
#[cfg_attr(test, automock)]
|
#[cfg_attr(test, automock)]
|
||||||
pub trait ILibrary {
|
pub trait ILibrary {
|
||||||
/// List lirbary items that match the a specific query.
|
/// 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.
|
/// Individual fields that can be queried on.
|
||||||
|
Loading…
Reference in New Issue
Block a user