Indexing WIP
This commit is contained in:
parent
2965cbdf7e
commit
2012258a72
13 changed files with 270 additions and 102 deletions
|
@ -108,22 +108,6 @@ impl Browser {
|
|||
Ok(songs)
|
||||
}
|
||||
|
||||
pub async fn get_random_albums(
|
||||
&self,
|
||||
count: i64,
|
||||
) -> Result<Vec<collection::Directory>, collection::Error> {
|
||||
// TODO move to Index
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
pub async fn get_recent_albums(
|
||||
&self,
|
||||
count: i64,
|
||||
) -> Result<Vec<collection::Directory>, collection::Error> {
|
||||
// TODO move to Index
|
||||
Ok(vec![])
|
||||
}
|
||||
|
||||
pub async fn search(&self, query: &str) -> Result<Vec<collection::File>, collection::Error> {
|
||||
let mut connection = self.db.connect().await?;
|
||||
let like_test = format!("%{}%", query);
|
||||
|
@ -285,28 +269,6 @@ mod test {
|
|||
assert_eq!(songs.len(), 7);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_get_random_albums() {
|
||||
let mut ctx = test::ContextBuilder::new(test_name!())
|
||||
.mount(TEST_MOUNT_NAME, "test-data/small-collection")
|
||||
.build()
|
||||
.await;
|
||||
ctx.updater.update().await.unwrap();
|
||||
let albums = ctx.browser.get_random_albums(1).await.unwrap();
|
||||
assert_eq!(albums.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_get_recent_albums() {
|
||||
let mut ctx = test::ContextBuilder::new(test_name!())
|
||||
.mount(TEST_MOUNT_NAME, "test-data/small-collection")
|
||||
.build()
|
||||
.await;
|
||||
ctx.updater.update().await.unwrap();
|
||||
let albums = ctx.browser.get_recent_albums(2).await.unwrap();
|
||||
assert_eq!(albums.len(), 2);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_get_a_song() {
|
||||
let mut ctx = test::ContextBuilder::new(test_name!())
|
||||
|
|
|
@ -1,32 +1,142 @@
|
|||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use rand::{rngs::ThreadRng, seq::IteratorRandom};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::app::collection;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct Index {
|
||||
lookups: Arc<RwLock<Lookups>>,
|
||||
}
|
||||
|
||||
impl Index {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
Self {
|
||||
lookups: Arc::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn replace_lookup_tables(&mut self, new_lookups: Lookups) {
|
||||
pub(super) async fn replace_lookup_tables(&mut self, new_lookups: Lookups) {
|
||||
let mut lock = self.lookups.write().await;
|
||||
*lock = new_lookups;
|
||||
}
|
||||
|
||||
pub async fn get_random_albums(
|
||||
&self,
|
||||
count: usize,
|
||||
) -> Result<Vec<collection::Album>, collection::Error> {
|
||||
let lookups = self.lookups.read().await;
|
||||
Ok(lookups
|
||||
.songs_by_albums
|
||||
.keys()
|
||||
.choose_multiple(&mut ThreadRng::default(), count)
|
||||
.iter()
|
||||
.filter_map(|k| lookups.get_album(k))
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub async fn get_recent_albums(
|
||||
&self,
|
||||
count: i64,
|
||||
) -> Result<Vec<collection::Album>, collection::Error> {
|
||||
// TODO implement
|
||||
Ok(vec![])
|
||||
}
|
||||
}
|
||||
|
||||
// TODO how can clients refer to an album?
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
struct AlbumKey {
|
||||
pub artists: Vec<String>,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Lookups {
|
||||
data: HashMap<String, String>,
|
||||
pub(super) struct Lookups {
|
||||
all_songs: HashMap<String, collection::Song>,
|
||||
songs_by_albums: HashMap<AlbumKey, HashSet<String>>, // TODO should this store collection::Album structs instead?
|
||||
}
|
||||
|
||||
impl Lookups {
|
||||
pub fn add_song(&mut self, _song: &collection::Song) {
|
||||
// todo!()
|
||||
pub fn add_song(&mut self, song: &collection::Song) {
|
||||
self.all_songs
|
||||
.insert(song.virtual_path.clone(), song.clone());
|
||||
|
||||
let album_artists = match song.album_artists.0.is_empty() {
|
||||
true => &song.artists.0,
|
||||
false => &song.album_artists.0,
|
||||
};
|
||||
|
||||
let album_key = AlbumKey {
|
||||
artists: album_artists.iter().cloned().collect(),
|
||||
name: song.album.clone(),
|
||||
};
|
||||
|
||||
let song_list = match self.songs_by_albums.get_mut(&album_key) {
|
||||
Some(l) => l,
|
||||
None => {
|
||||
self.songs_by_albums
|
||||
.insert(album_key.clone(), HashSet::new());
|
||||
self.songs_by_albums.get_mut(&album_key).unwrap()
|
||||
}
|
||||
};
|
||||
|
||||
song_list.insert(song.virtual_path.clone());
|
||||
}
|
||||
|
||||
pub fn get_album(&self, key: &AlbumKey) -> Option<collection::Album> {
|
||||
let Some(songs) = self.songs_by_albums.get(key) else {
|
||||
return None;
|
||||
};
|
||||
|
||||
let songs: Vec<&collection::Song> =
|
||||
songs.iter().filter_map(|s| self.all_songs.get(s)).collect();
|
||||
|
||||
Some(collection::Album {
|
||||
name: key.name.clone(),
|
||||
artwork: songs.iter().find_map(|s| s.artwork.clone()),
|
||||
artists: key.artists.iter().cloned().collect(),
|
||||
year: songs.iter().find_map(|s| s.year),
|
||||
date_added: songs
|
||||
.iter()
|
||||
.min_by_key(|s| s.date_added)
|
||||
.map(|s| s.date_added)
|
||||
.unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
use crate::app::test;
|
||||
use crate::test_name;
|
||||
|
||||
const TEST_MOUNT_NAME: &str = "root";
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_get_random_albums() {
|
||||
let mut ctx = test::ContextBuilder::new(test_name!())
|
||||
.mount(TEST_MOUNT_NAME, "test-data/small-collection")
|
||||
.build()
|
||||
.await;
|
||||
ctx.updater.update().await.unwrap();
|
||||
let albums = ctx.index.get_random_albums(1).await.unwrap();
|
||||
assert_eq!(albums.len(), 1);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn can_get_recent_albums() {
|
||||
let mut ctx = test::ContextBuilder::new(test_name!())
|
||||
.mount(TEST_MOUNT_NAME, "test-data/small-collection")
|
||||
.build()
|
||||
.await;
|
||||
ctx.updater.update().await.unwrap();
|
||||
let albums = ctx.index.get_recent_albums(2).await.unwrap();
|
||||
assert_eq!(albums.len(), 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,17 @@ impl<'q> sqlx::Encode<'q, Sqlite> for MultiString {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'q> sqlx::Decode<'q, Sqlite> for MultiString {
|
||||
fn decode(
|
||||
value: <Sqlite as sqlx::database::HasValueRef<'q>>::ValueRef,
|
||||
) -> Result<Self, sqlx::error::BoxDynError> {
|
||||
let s: &str = sqlx::Decode::<Sqlite>::decode(value)?;
|
||||
Ok(MultiString(
|
||||
s.split(MultiString::SEPARATOR).map(str::to_owned).collect(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl sqlx::Type<Sqlite> for MultiString {
|
||||
fn type_info() -> SqliteTypeInfo {
|
||||
<&str as sqlx::Type<Sqlite>>::type_info()
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use sqlx::prelude::FromRow;
|
||||
|
||||
use crate::{
|
||||
app::vfs::{self},
|
||||
db,
|
||||
};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, FromRow, PartialEq, Eq)]
|
||||
pub struct MultiString(pub Vec<String>);
|
||||
|
||||
impl MultiString {
|
||||
|
@ -43,7 +45,7 @@ pub enum File {
|
|||
Song(Song),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Song {
|
||||
pub id: i64,
|
||||
pub path: String,
|
||||
|
@ -72,3 +74,12 @@ pub struct Directory {
|
|||
pub virtual_path: String,
|
||||
pub virtual_parent: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct Album {
|
||||
pub name: Option<String>,
|
||||
pub artwork: Option<String>,
|
||||
pub artists: Vec<String>,
|
||||
pub year: Option<i64>,
|
||||
pub date_added: i64,
|
||||
}
|
||||
|
|
|
@ -47,6 +47,8 @@ impl Updater {
|
|||
}
|
||||
});
|
||||
|
||||
// TODO populate index w/ whatever is already in DB
|
||||
|
||||
updater
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ use crate::test::*;
|
|||
pub struct Context {
|
||||
pub db: DB,
|
||||
pub browser: collection::Browser,
|
||||
pub index: collection::Index,
|
||||
pub updater: collection::Updater,
|
||||
pub config_manager: config::Manager,
|
||||
pub ddns_manager: ddns::Manager,
|
||||
|
@ -81,6 +82,7 @@ impl ContextBuilder {
|
|||
Context {
|
||||
db,
|
||||
browser,
|
||||
index,
|
||||
updater,
|
||||
config_manager,
|
||||
ddns_manager,
|
||||
|
|
|
@ -33,6 +33,12 @@ impl FromRef<App> for app::collection::Browser {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromRef<App> for app::collection::Index {
|
||||
fn from_ref(app: &App) -> Self {
|
||||
app.index.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<App> for app::collection::Updater {
|
||||
fn from_ref(app: &App) -> Self {
|
||||
app.updater.clone()
|
||||
|
|
|
@ -269,7 +269,7 @@ fn collection_files_to_response(
|
|||
files
|
||||
.into_iter()
|
||||
.map(|f| f.into())
|
||||
.collect::<Vec<dto::CollectionFile>>(),
|
||||
.collect::<Vec<dto::BrowserEntry>>(),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -294,23 +294,20 @@ fn songs_to_response(files: Vec<collection::Song>, api_version: APIMajorVersion)
|
|||
}
|
||||
}
|
||||
|
||||
fn directories_to_response(
|
||||
files: Vec<collection::Directory>,
|
||||
api_version: APIMajorVersion,
|
||||
) -> Response {
|
||||
fn albums_to_response(albums: Vec<collection::Album>, api_version: APIMajorVersion) -> Response {
|
||||
match api_version {
|
||||
APIMajorVersion::V7 => Json(
|
||||
files
|
||||
albums
|
||||
.into_iter()
|
||||
.map(|f| f.into())
|
||||
.collect::<Vec<dto::v7::Directory>>(),
|
||||
)
|
||||
.into_response(),
|
||||
APIMajorVersion::V8 => Json(
|
||||
files
|
||||
albums
|
||||
.into_iter()
|
||||
.map(|f| f.into())
|
||||
.collect::<Vec<dto::Directory>>(),
|
||||
.collect::<Vec<dto::Album>>(),
|
||||
)
|
||||
.into_response(),
|
||||
}
|
||||
|
@ -371,25 +368,25 @@ async fn get_flatten(
|
|||
async fn get_random(
|
||||
_auth: Auth,
|
||||
api_version: APIMajorVersion,
|
||||
State(browser): State<collection::Browser>,
|
||||
State(index): State<collection::Index>,
|
||||
) -> Response {
|
||||
let directories = match browser.get_random_albums(20).await {
|
||||
let albums = match index.get_random_albums(20).await {
|
||||
Ok(d) => d,
|
||||
Err(e) => return APIError::from(e).into_response(),
|
||||
};
|
||||
directories_to_response(directories, api_version)
|
||||
albums_to_response(albums, api_version)
|
||||
}
|
||||
|
||||
async fn get_recent(
|
||||
_auth: Auth,
|
||||
api_version: APIMajorVersion,
|
||||
State(browser): State<collection::Browser>,
|
||||
State(index): State<collection::Index>,
|
||||
) -> Response {
|
||||
let directories = match browser.get_recent_albums(20).await {
|
||||
let albums = match index.get_recent_albums(20).await {
|
||||
Ok(d) => d,
|
||||
Err(e) => return APIError::from(e).into_response(),
|
||||
};
|
||||
directories_to_response(directories, api_version)
|
||||
albums_to_response(albums, api_version)
|
||||
}
|
||||
|
||||
async fn get_search_root(
|
||||
|
|
|
@ -302,7 +302,6 @@ pub struct Directory {
|
|||
pub year: Option<i64>,
|
||||
pub album: Option<String>,
|
||||
pub artwork: Option<String>,
|
||||
pub date_added: i64,
|
||||
}
|
||||
|
||||
impl From<collection::Directory> for Directory {
|
||||
|
@ -313,7 +312,21 @@ impl From<collection::Directory> for Directory {
|
|||
year: None,
|
||||
album: None,
|
||||
artwork: None,
|
||||
date_added: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<collection::Album> for Directory {
|
||||
fn from(a: collection::Album) -> Self {
|
||||
Self {
|
||||
path: todo!(), // TODO implement
|
||||
artist: match a.artists.is_empty() {
|
||||
true => None,
|
||||
false => Some(a.artists.join("")),
|
||||
},
|
||||
year: a.year,
|
||||
album: a.name,
|
||||
artwork: a.artwork,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -228,21 +228,6 @@ impl From<settings::Settings> for Settings {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum CollectionFile {
|
||||
Directory(Directory),
|
||||
Song(Song),
|
||||
}
|
||||
|
||||
impl From<collection::File> for CollectionFile {
|
||||
fn from(f: collection::File) -> Self {
|
||||
match f {
|
||||
collection::File::Directory(d) => Self::Directory(d.into()),
|
||||
collection::File::Song(s) => Self::Song(s.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Song {
|
||||
pub path: String,
|
||||
|
@ -296,16 +281,47 @@ impl From<collection::Song> for Song {
|
|||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Directory {
|
||||
pub struct BrowserEntry {
|
||||
pub path: String,
|
||||
pub is_directory: bool,
|
||||
}
|
||||
|
||||
impl From<collection::Directory> for Directory {
|
||||
fn from(d: collection::Directory) -> Self {
|
||||
Self {
|
||||
path: d.virtual_path,
|
||||
impl From<collection::File> for BrowserEntry {
|
||||
fn from(file: collection::File) -> Self {
|
||||
match file {
|
||||
collection::File::Directory(d) => Self {
|
||||
is_directory: true,
|
||||
path: d.virtual_path,
|
||||
},
|
||||
collection::File::Song(s) => Self {
|
||||
is_directory: false,
|
||||
path: s.virtual_path,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Preferencesshould have dto types
|
||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Album {
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub name: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub artwork: Option<String>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub artists: Vec<String>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub year: Option<i64>,
|
||||
}
|
||||
|
||||
impl From<collection::Album> for Album {
|
||||
fn from(a: collection::Album) -> Self {
|
||||
Self {
|
||||
name: a.name,
|
||||
artwork: a.artwork,
|
||||
artists: a.artists,
|
||||
year: a.year,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Preferences should have dto types
|
||||
|
|
|
@ -119,7 +119,7 @@ pub trait TestService {
|
|||
loop {
|
||||
let browse_request = protocol::browse::<V8>(Path::new(""));
|
||||
let response = self
|
||||
.fetch_json::<(), Vec<dto::CollectionFile>>(&browse_request)
|
||||
.fetch_json::<(), Vec<dto::BrowserEntry>>(&browse_request)
|
||||
.await;
|
||||
let entries = response.body();
|
||||
if !entries.is_empty() {
|
||||
|
|
|
@ -50,13 +50,13 @@ async fn trigger_index_golden_path() {
|
|||
|
||||
let request = protocol::random::<V8>();
|
||||
|
||||
let response = service.fetch_json::<_, Vec<dto::Directory>>(&request).await;
|
||||
let response = service.fetch_json::<_, Vec<dto::Album>>(&request).await;
|
||||
let entries = response.body();
|
||||
assert_eq!(entries.len(), 0);
|
||||
|
||||
service.index().await;
|
||||
|
||||
let response = service.fetch_json::<_, Vec<dto::Directory>>(&request).await;
|
||||
let response = service.fetch_json::<_, Vec<dto::Album>>(&request).await;
|
||||
let entries = response.body();
|
||||
assert_eq!(entries.len(), 3);
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ async fn browse_root() {
|
|||
|
||||
let request = protocol::browse::<V8>(&PathBuf::new());
|
||||
let response = service
|
||||
.fetch_json::<_, Vec<dto::CollectionFile>>(&request)
|
||||
.fetch_json::<_, Vec<dto::BrowserEntry>>(&request)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let entries = response.body();
|
||||
|
@ -42,7 +42,7 @@ async fn browse_directory() {
|
|||
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted"].iter().collect();
|
||||
let request = protocol::browse::<V8>(&path);
|
||||
let response = service
|
||||
.fetch_json::<_, Vec<dto::CollectionFile>>(&request)
|
||||
.fetch_json::<_, Vec<dto::BrowserEntry>>(&request)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let entries = response.body();
|
||||
|
@ -168,7 +168,7 @@ async fn random_golden_path() {
|
|||
service.login().await;
|
||||
|
||||
let request = protocol::random::<V8>();
|
||||
let response = service.fetch_json::<_, Vec<dto::Directory>>(&request).await;
|
||||
let response = service.fetch_json::<_, Vec<dto::Album>>(&request).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let entries = response.body();
|
||||
assert_eq!(entries.len(), 3);
|
||||
|
@ -184,7 +184,24 @@ async fn random_with_trailing_slash() {
|
|||
|
||||
let mut request = protocol::random::<V8>();
|
||||
add_trailing_slash(&mut request);
|
||||
let response = service.fetch_json::<_, Vec<dto::Directory>>(&request).await;
|
||||
let response = service.fetch_json::<_, Vec<dto::Album>>(&request).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let entries = response.body();
|
||||
assert_eq!(entries.len(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn random_golden_path_api_v7() {
|
||||
let mut service = ServiceType::new(&test_name!()).await;
|
||||
service.complete_initial_setup().await;
|
||||
service.login_admin().await;
|
||||
service.index().await;
|
||||
service.login().await;
|
||||
|
||||
let request = protocol::random::<V7>();
|
||||
let response = service
|
||||
.fetch_json::<_, Vec<dto::v7::Directory>>(&request)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let entries = response.body();
|
||||
assert_eq!(entries.len(), 3);
|
||||
|
@ -207,7 +224,7 @@ async fn recent_golden_path() {
|
|||
service.login().await;
|
||||
|
||||
let request = protocol::recent::<V8>();
|
||||
let response = service.fetch_json::<_, Vec<dto::Directory>>(&request).await;
|
||||
let response = service.fetch_json::<_, Vec<dto::Album>>(&request).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let entries = response.body();
|
||||
assert_eq!(entries.len(), 3);
|
||||
|
@ -223,7 +240,24 @@ async fn recent_with_trailing_slash() {
|
|||
|
||||
let mut request = protocol::recent::<V8>();
|
||||
add_trailing_slash(&mut request);
|
||||
let response = service.fetch_json::<_, Vec<dto::Directory>>(&request).await;
|
||||
let response = service.fetch_json::<_, Vec<dto::Album>>(&request).await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let entries = response.body();
|
||||
assert_eq!(entries.len(), 3);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn recent_golden_path_api_v7() {
|
||||
let mut service = ServiceType::new(&test_name!()).await;
|
||||
service.complete_initial_setup().await;
|
||||
service.login_admin().await;
|
||||
service.index().await;
|
||||
service.login().await;
|
||||
|
||||
let request = protocol::recent::<V7>();
|
||||
let response = service
|
||||
.fetch_json::<_, Vec<dto::v7::Directory>>(&request)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
let entries = response.body();
|
||||
assert_eq!(entries.len(), 3);
|
||||
|
@ -245,7 +279,7 @@ async fn search_without_query() {
|
|||
|
||||
let request = protocol::search::<V8>("");
|
||||
let response = service
|
||||
.fetch_json::<_, Vec<dto::CollectionFile>>(&request)
|
||||
.fetch_json::<_, Vec<dto::BrowserEntry>>(&request)
|
||||
.await;
|
||||
assert_eq!(response.status(), StatusCode::OK);
|
||||
}
|
||||
|
@ -260,14 +294,18 @@ async fn search_with_query() {
|
|||
|
||||
let request = protocol::search::<V8>("door");
|
||||
let response = service
|
||||
.fetch_json::<_, Vec<dto::CollectionFile>>(&request)
|
||||
.fetch_json::<_, Vec<dto::BrowserEntry>>(&request)
|
||||
.await;
|
||||
let results = response.body();
|
||||
assert_eq!(results.len(), 1);
|
||||
match results[0] {
|
||||
dto::CollectionFile::Song(ref s) => {
|
||||
assert_eq!(s.title, Some("Beyond The Door".into()))
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
|
||||
let path: PathBuf = [
|
||||
TEST_MOUNT_NAME,
|
||||
"Khemmis",
|
||||
"Hunted",
|
||||
"04 - Beyond The Door.mp3",
|
||||
]
|
||||
.iter()
|
||||
.collect();
|
||||
assert_eq!(results[0].path, path.to_string_lossy());
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue