Library now returns vector of Item

This commit is contained in:
Wojciech Kozlowski 2023-05-14 16:14:30 +02:00
parent d20a9a9dec
commit 1e1dbe8688
3 changed files with 115 additions and 77 deletions

View File

@ -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)]

View File

@ -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_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() {
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())),
},
bitrate: track_bitrate,
},
};
let track_bitrate = split[7].trim_end_matches("kbps").parse::<u32>()?;
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)
}
}

View File

@ -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.