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)
|
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> {
|
pub async fn search(&self, query: &str) -> Result<Vec<collection::File>, collection::Error> {
|
||||||
let mut connection = self.db.connect().await?;
|
let mut connection = self.db.connect().await?;
|
||||||
let like_test = format!("%{}%", query);
|
let like_test = format!("%{}%", query);
|
||||||
|
@ -285,28 +269,6 @@ mod test {
|
||||||
assert_eq!(songs.len(), 7);
|
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]
|
#[tokio::test]
|
||||||
async fn can_get_a_song() {
|
async fn can_get_a_song() {
|
||||||
let mut ctx = test::ContextBuilder::new(test_name!())
|
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 tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::app::collection;
|
use crate::app::collection;
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone)]
|
||||||
pub struct Index {
|
pub struct Index {
|
||||||
lookups: Arc<RwLock<Lookups>>,
|
lookups: Arc<RwLock<Lookups>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Index {
|
impl Index {
|
||||||
pub fn new() -> Self {
|
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;
|
let mut lock = self.lookups.write().await;
|
||||||
*lock = new_lookups;
|
*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)]
|
#[derive(Default)]
|
||||||
pub struct Lookups {
|
pub(super) struct Lookups {
|
||||||
data: HashMap<String, String>,
|
all_songs: HashMap<String, collection::Song>,
|
||||||
|
songs_by_albums: HashMap<AlbumKey, HashSet<String>>, // TODO should this store collection::Album structs instead?
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Lookups {
|
impl Lookups {
|
||||||
pub fn add_song(&mut self, _song: &collection::Song) {
|
pub fn add_song(&mut self, song: &collection::Song) {
|
||||||
// todo!()
|
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 {
|
impl sqlx::Type<Sqlite> for MultiString {
|
||||||
fn type_info() -> SqliteTypeInfo {
|
fn type_info() -> SqliteTypeInfo {
|
||||||
<&str as sqlx::Type<Sqlite>>::type_info()
|
<&str as sqlx::Type<Sqlite>>::type_info()
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use sqlx::prelude::FromRow;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
app::vfs::{self},
|
app::vfs::{self},
|
||||||
db,
|
db,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, FromRow, PartialEq, Eq)]
|
||||||
pub struct MultiString(pub Vec<String>);
|
pub struct MultiString(pub Vec<String>);
|
||||||
|
|
||||||
impl MultiString {
|
impl MultiString {
|
||||||
|
@ -43,7 +45,7 @@ pub enum File {
|
||||||
Song(Song),
|
Song(Song),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
@ -72,3 +74,12 @@ pub struct Directory {
|
||||||
pub virtual_path: String,
|
pub virtual_path: String,
|
||||||
pub virtual_parent: Option<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
|
updater
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ use crate::test::*;
|
||||||
pub struct Context {
|
pub struct Context {
|
||||||
pub db: DB,
|
pub db: DB,
|
||||||
pub browser: collection::Browser,
|
pub browser: collection::Browser,
|
||||||
|
pub index: collection::Index,
|
||||||
pub updater: collection::Updater,
|
pub updater: collection::Updater,
|
||||||
pub config_manager: config::Manager,
|
pub config_manager: config::Manager,
|
||||||
pub ddns_manager: ddns::Manager,
|
pub ddns_manager: ddns::Manager,
|
||||||
|
@ -81,6 +82,7 @@ impl ContextBuilder {
|
||||||
Context {
|
Context {
|
||||||
db,
|
db,
|
||||||
browser,
|
browser,
|
||||||
|
index,
|
||||||
updater,
|
updater,
|
||||||
config_manager,
|
config_manager,
|
||||||
ddns_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 {
|
impl FromRef<App> for app::collection::Updater {
|
||||||
fn from_ref(app: &App) -> Self {
|
fn from_ref(app: &App) -> Self {
|
||||||
app.updater.clone()
|
app.updater.clone()
|
||||||
|
|
|
@ -269,7 +269,7 @@ fn collection_files_to_response(
|
||||||
files
|
files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|f| f.into())
|
.map(|f| f.into())
|
||||||
.collect::<Vec<dto::CollectionFile>>(),
|
.collect::<Vec<dto::BrowserEntry>>(),
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
}
|
}
|
||||||
|
@ -294,23 +294,20 @@ fn songs_to_response(files: Vec<collection::Song>, api_version: APIMajorVersion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn directories_to_response(
|
fn albums_to_response(albums: Vec<collection::Album>, api_version: APIMajorVersion) -> Response {
|
||||||
files: Vec<collection::Directory>,
|
|
||||||
api_version: APIMajorVersion,
|
|
||||||
) -> Response {
|
|
||||||
match api_version {
|
match api_version {
|
||||||
APIMajorVersion::V7 => Json(
|
APIMajorVersion::V7 => Json(
|
||||||
files
|
albums
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|f| f.into())
|
.map(|f| f.into())
|
||||||
.collect::<Vec<dto::v7::Directory>>(),
|
.collect::<Vec<dto::v7::Directory>>(),
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
APIMajorVersion::V8 => Json(
|
APIMajorVersion::V8 => Json(
|
||||||
files
|
albums
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|f| f.into())
|
.map(|f| f.into())
|
||||||
.collect::<Vec<dto::Directory>>(),
|
.collect::<Vec<dto::Album>>(),
|
||||||
)
|
)
|
||||||
.into_response(),
|
.into_response(),
|
||||||
}
|
}
|
||||||
|
@ -371,25 +368,25 @@ async fn get_flatten(
|
||||||
async fn get_random(
|
async fn get_random(
|
||||||
_auth: Auth,
|
_auth: Auth,
|
||||||
api_version: APIMajorVersion,
|
api_version: APIMajorVersion,
|
||||||
State(browser): State<collection::Browser>,
|
State(index): State<collection::Index>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let directories = match browser.get_random_albums(20).await {
|
let albums = match index.get_random_albums(20).await {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(e) => return APIError::from(e).into_response(),
|
Err(e) => return APIError::from(e).into_response(),
|
||||||
};
|
};
|
||||||
directories_to_response(directories, api_version)
|
albums_to_response(albums, api_version)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_recent(
|
async fn get_recent(
|
||||||
_auth: Auth,
|
_auth: Auth,
|
||||||
api_version: APIMajorVersion,
|
api_version: APIMajorVersion,
|
||||||
State(browser): State<collection::Browser>,
|
State(index): State<collection::Index>,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let directories = match browser.get_recent_albums(20).await {
|
let albums = match index.get_recent_albums(20).await {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
Err(e) => return APIError::from(e).into_response(),
|
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(
|
async fn get_search_root(
|
||||||
|
|
|
@ -302,7 +302,6 @@ pub struct Directory {
|
||||||
pub year: Option<i64>,
|
pub year: Option<i64>,
|
||||||
pub album: Option<String>,
|
pub album: Option<String>,
|
||||||
pub artwork: Option<String>,
|
pub artwork: Option<String>,
|
||||||
pub date_added: i64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<collection::Directory> for Directory {
|
impl From<collection::Directory> for Directory {
|
||||||
|
@ -313,7 +312,21 @@ impl From<collection::Directory> for Directory {
|
||||||
year: None,
|
year: None,
|
||||||
album: None,
|
album: None,
|
||||||
artwork: 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)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Song {
|
pub struct Song {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
@ -296,16 +281,47 @@ impl From<collection::Song> for Song {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct Directory {
|
pub struct BrowserEntry {
|
||||||
pub path: String,
|
pub path: String,
|
||||||
|
pub is_directory: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<collection::Directory> for Directory {
|
impl From<collection::File> for BrowserEntry {
|
||||||
fn from(d: collection::Directory) -> Self {
|
fn from(file: collection::File) -> Self {
|
||||||
Self {
|
match file {
|
||||||
path: d.virtual_path,
|
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 {
|
loop {
|
||||||
let browse_request = protocol::browse::<V8>(Path::new(""));
|
let browse_request = protocol::browse::<V8>(Path::new(""));
|
||||||
let response = self
|
let response = self
|
||||||
.fetch_json::<(), Vec<dto::CollectionFile>>(&browse_request)
|
.fetch_json::<(), Vec<dto::BrowserEntry>>(&browse_request)
|
||||||
.await;
|
.await;
|
||||||
let entries = response.body();
|
let entries = response.body();
|
||||||
if !entries.is_empty() {
|
if !entries.is_empty() {
|
||||||
|
|
|
@ -50,13 +50,13 @@ async fn trigger_index_golden_path() {
|
||||||
|
|
||||||
let request = protocol::random::<V8>();
|
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();
|
let entries = response.body();
|
||||||
assert_eq!(entries.len(), 0);
|
assert_eq!(entries.len(), 0);
|
||||||
|
|
||||||
service.index().await;
|
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();
|
let entries = response.body();
|
||||||
assert_eq!(entries.len(), 3);
|
assert_eq!(entries.len(), 3);
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ async fn browse_root() {
|
||||||
|
|
||||||
let request = protocol::browse::<V8>(&PathBuf::new());
|
let request = protocol::browse::<V8>(&PathBuf::new());
|
||||||
let response = service
|
let response = service
|
||||||
.fetch_json::<_, Vec<dto::CollectionFile>>(&request)
|
.fetch_json::<_, Vec<dto::BrowserEntry>>(&request)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let entries = response.body();
|
let entries = response.body();
|
||||||
|
@ -42,7 +42,7 @@ async fn browse_directory() {
|
||||||
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted"].iter().collect();
|
let path: PathBuf = [TEST_MOUNT_NAME, "Khemmis", "Hunted"].iter().collect();
|
||||||
let request = protocol::browse::<V8>(&path);
|
let request = protocol::browse::<V8>(&path);
|
||||||
let response = service
|
let response = service
|
||||||
.fetch_json::<_, Vec<dto::CollectionFile>>(&request)
|
.fetch_json::<_, Vec<dto::BrowserEntry>>(&request)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let entries = response.body();
|
let entries = response.body();
|
||||||
|
@ -168,7 +168,7 @@ async fn random_golden_path() {
|
||||||
service.login().await;
|
service.login().await;
|
||||||
|
|
||||||
let request = protocol::random::<V8>();
|
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);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let entries = response.body();
|
let entries = response.body();
|
||||||
assert_eq!(entries.len(), 3);
|
assert_eq!(entries.len(), 3);
|
||||||
|
@ -184,7 +184,24 @@ async fn random_with_trailing_slash() {
|
||||||
|
|
||||||
let mut request = protocol::random::<V8>();
|
let mut request = protocol::random::<V8>();
|
||||||
add_trailing_slash(&mut request);
|
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);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let entries = response.body();
|
let entries = response.body();
|
||||||
assert_eq!(entries.len(), 3);
|
assert_eq!(entries.len(), 3);
|
||||||
|
@ -207,7 +224,7 @@ async fn recent_golden_path() {
|
||||||
service.login().await;
|
service.login().await;
|
||||||
|
|
||||||
let request = protocol::recent::<V8>();
|
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);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let entries = response.body();
|
let entries = response.body();
|
||||||
assert_eq!(entries.len(), 3);
|
assert_eq!(entries.len(), 3);
|
||||||
|
@ -223,7 +240,24 @@ async fn recent_with_trailing_slash() {
|
||||||
|
|
||||||
let mut request = protocol::recent::<V8>();
|
let mut request = protocol::recent::<V8>();
|
||||||
add_trailing_slash(&mut request);
|
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);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
let entries = response.body();
|
let entries = response.body();
|
||||||
assert_eq!(entries.len(), 3);
|
assert_eq!(entries.len(), 3);
|
||||||
|
@ -245,7 +279,7 @@ async fn search_without_query() {
|
||||||
|
|
||||||
let request = protocol::search::<V8>("");
|
let request = protocol::search::<V8>("");
|
||||||
let response = service
|
let response = service
|
||||||
.fetch_json::<_, Vec<dto::CollectionFile>>(&request)
|
.fetch_json::<_, Vec<dto::BrowserEntry>>(&request)
|
||||||
.await;
|
.await;
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
|
@ -260,14 +294,18 @@ async fn search_with_query() {
|
||||||
|
|
||||||
let request = protocol::search::<V8>("door");
|
let request = protocol::search::<V8>("door");
|
||||||
let response = service
|
let response = service
|
||||||
.fetch_json::<_, Vec<dto::CollectionFile>>(&request)
|
.fetch_json::<_, Vec<dto::BrowserEntry>>(&request)
|
||||||
.await;
|
.await;
|
||||||
let results = response.body();
|
let results = response.body();
|
||||||
assert_eq!(results.len(), 1);
|
assert_eq!(results.len(), 1);
|
||||||
match results[0] {
|
|
||||||
dto::CollectionFile::Song(ref s) => {
|
let path: PathBuf = [
|
||||||
assert_eq!(s.title, Some("Beyond The Door".into()))
|
TEST_MOUNT_NAME,
|
||||||
}
|
"Khemmis",
|
||||||
_ => panic!(),
|
"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