feat(api): add filter query parameter

revert changes
This commit is contained in:
Tsiry Sandratraina
2023-04-02 17:51:04 +03:00
parent b4e056e994
commit 472876da90
17 changed files with 247 additions and 64 deletions

View File

@@ -114,7 +114,12 @@ impl Addon for Dlna {
#[async_trait]
impl Browseable for Dlna {
async fn albums(&mut self, offset: i32, limit: i32) -> Result<Vec<Album>, Error> {
async fn albums(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Album>, Error> {
if let Some(client) = &self.media_server_client {
client
.browse("musicdb://albums", "BrowseDirectChildren")
@@ -125,7 +130,12 @@ impl Browseable for Dlna {
Err(Error::msg("No device connected"))
}
async fn artists(&mut self, offset: i32, limit: i32) -> Result<Vec<Artist>, Error> {
async fn artists(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Artist>, Error> {
if let Some(client) = &self.media_server_client {
client
.browse("musicdb://artists", "BrowseDirectChildren")
@@ -136,7 +146,12 @@ impl Browseable for Dlna {
Err(Error::msg("No device connected"))
}
async fn tracks(&mut self, offset: i32, limit: i32) -> Result<Vec<Track>, Error> {
async fn tracks(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Track>, Error> {
if let Some(client) = &self.media_server_client {
client
.browse("musicdb://songs", "BrowseDirectChildren")

View File

@@ -75,15 +75,30 @@ impl StreamingAddon for Kodi {
#[async_trait]
impl Browseable for Kodi {
async fn albums(&mut self, offset: i32, limit: i32) -> Result<Vec<Album>, Error> {
async fn albums(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Album>, Error> {
todo!()
}
async fn artists(&mut self, offset: i32, limit: i32) -> Result<Vec<Artist>, Error> {
async fn artists(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Artist>, Error> {
todo!()
}
async fn tracks(&mut self, offset: i32, limit: i32) -> Result<Vec<Track>, Error> {
async fn tracks(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Track>, Error> {
todo!()
}

View File

@@ -32,9 +32,24 @@ pub trait LyricsAddon {
#[async_trait]
pub trait Browseable {
async fn albums(&mut self, offset: i32, limit: i32) -> Result<Vec<Album>, Error>;
async fn artists(&mut self, offset: i32, limit: i32) -> Result<Vec<Artist>, Error>;
async fn tracks(&mut self, offset: i32, limit: i32) -> Result<Vec<Track>, Error>;
async fn albums(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Album>, Error>;
async fn artists(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Artist>, Error>;
async fn tracks(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Track>, Error>;
async fn playlists(&mut self, offset: i32, limit: i32) -> Result<Vec<Playlist>, Error>;
async fn album(&mut self, id: &str) -> Result<Album, Error>;
async fn artist(&mut self, id: &str) -> Result<Artist, Error>;

View File

@@ -78,35 +78,50 @@ impl StreamingAddon for Local {
#[async_trait]
impl Browseable for Local {
async fn albums(&mut self, offset: i32, limit: i32) -> Result<Vec<Album>, Error> {
async fn albums(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Album>, Error> {
let response = self
.client
.as_mut()
.unwrap()
.library
.albums(offset, limit)
.albums(filter, offset, limit)
.await?;
Ok(response.into_iter().map(Into::into).collect())
}
async fn artists(&mut self, offset: i32, limit: i32) -> Result<Vec<Artist>, Error> {
async fn artists(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Artist>, Error> {
let response = self
.client
.as_mut()
.unwrap()
.library
.artists(offset, limit)
.artists(filter, offset, limit)
.await?;
Ok(response.into_iter().map(Into::into).collect())
}
async fn tracks(&mut self, offset: i32, limit: i32) -> Result<Vec<Track>, Error> {
async fn tracks(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Track>, Error> {
let response = self
.client
.as_mut()
.unwrap()
.library
.songs(offset, limit)
.songs(filter, offset, limit)
.await?;
Ok(response.into_iter().map(Into::into).collect())
}

View File

@@ -25,8 +25,21 @@ impl LibraryClient {
Ok(response.into_inner().album)
}
pub async fn albums(&mut self, offset: i32, limit: i32) -> Result<Vec<Album>, Error> {
let request = tonic::Request::new(GetAlbumsRequest { offset, limit });
pub async fn albums(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Album>, Error> {
let filter = match filter {
Some(filter) => filter,
None => "".to_string(),
};
let request = tonic::Request::new(GetAlbumsRequest {
offset,
limit,
filter,
});
let response = self.client.get_albums(request).await?;
Ok(response.into_inner().albums.into_iter().collect())
}
@@ -37,14 +50,40 @@ impl LibraryClient {
Ok(response.into_inner().artist)
}
pub async fn artists(&mut self, offset: i32, limit: i32) -> Result<Vec<Artist>, Error> {
let request = tonic::Request::new(GetArtistsRequest { offset, limit });
pub async fn artists(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Artist>, Error> {
let filter = match filter {
Some(filter) => filter,
None => "".to_string(),
};
let request = tonic::Request::new(GetArtistsRequest {
offset,
limit,
filter,
});
let response = self.client.get_artists(request).await?;
Ok(response.into_inner().artists.into_iter().collect())
}
pub async fn songs(&mut self, offset: i32, limit: i32) -> Result<Vec<Track>, Error> {
let request = tonic::Request::new(GetTracksRequest { offset, limit });
pub async fn songs(
&mut self,
filter: Option<String>,
offset: i32,
limit: i32,
) -> Result<Vec<Track>, Error> {
let filter = match filter {
Some(filter) => filter,
None => "".to_string(),
};
let request = tonic::Request::new(GetTracksRequest {
offset,
limit,
filter,
});
let response = self.client.get_tracks(request).await?;
Ok(response.into_inner().tracks.into_iter().collect())
}

View File

@@ -21,7 +21,7 @@ async fn albums() -> Result<(), Box<dyn std::error::Error>> {
let port = env::var("MUSIC_PLAYER_PORT").unwrap_or_else(|_| "50051".to_string());
let host = "0.0.0.0".to_owned();
let mut client = LibraryClient::new(host, port.parse().unwrap()).await?;
let response = client.albums(0, 100).await?;
let response = client.albums(None, 0, 100).await?;
assert_eq!(response.len(), 1);
assert_eq!(response[0].id, "216ccc791352fbbffc11268b984db19a");
assert_eq!(response[0].title, "2014 Forest Hills Drive");
@@ -47,7 +47,7 @@ async fn artists() -> Result<(), Box<dyn std::error::Error>> {
let port = env::var("MUSIC_PLAYER_PORT").unwrap_or_else(|_| "50051".to_string());
let host = env::var("MUSIC_PLAYER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string());
let mut client = LibraryClient::new(host, port.parse().unwrap()).await?;
let response = client.artists(0, 100).await?;
let response = client.artists(None, 0, 100).await?;
assert_eq!(response.len(), 1);
assert_eq!(response[0].id, "b03cc90c455d92d8e9a0ce331e6de54d");
assert_eq!(response[0].name, "J. Cole");
@@ -59,7 +59,7 @@ async fn songs() -> Result<(), Box<dyn std::error::Error>> {
let port = env::var("MUSIC_PLAYER_PORT").unwrap_or_else(|_| "50051".to_string());
let host = env::var("MUSIC_PLAYER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string());
let mut client = LibraryClient::new(host, port.parse().unwrap()).await?;
let response = client.songs(0, 100).await?;
let response = client.songs(None, 0, 100).await?;
assert_eq!(response.len(), 2);
assert_eq!(response[0].id, "dd77dd0ea2de5208e4987001a59ba8e4");
assert_eq!(response[0].title, "Fire Squad");

View File

@@ -23,6 +23,7 @@ impl LibraryQuery {
async fn tracks(
&self,
ctx: &Context<'_>,
filter: Option<String>,
offset: Option<i32>,
limit: Option<i32>,
) -> Result<Vec<Track>, Error> {
@@ -32,7 +33,7 @@ impl LibraryQuery {
if device.client.is_some() {
let source = device.client.as_mut().unwrap();
let result = source
.tracks(offset.unwrap_or(0), limit.unwrap_or(100))
.tracks(filter, offset.unwrap_or(0), limit.unwrap_or(100))
.await?;
let base_url = device
@@ -57,7 +58,7 @@ impl LibraryQuery {
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let results = TrackRepository::new(db.lock().await.get_connection())
.find_all(100)
.find_all(filter, 100)
.await?;
Ok(results.into_iter().map(Into::into).collect())
@@ -66,6 +67,7 @@ impl LibraryQuery {
async fn artists(
&self,
ctx: &Context<'_>,
filter: Option<String>,
offset: Option<i32>,
limit: Option<i32>,
) -> Result<Vec<Artist>, Error> {
@@ -75,7 +77,7 @@ impl LibraryQuery {
if device.client.is_some() {
let source = device.client.as_mut().unwrap();
let artists = source
.artists(offset.unwrap_or(0), limit.unwrap_or(100))
.artists(filter, offset.unwrap_or(0), limit.unwrap_or(100))
.await?;
let base_url = device
@@ -100,7 +102,7 @@ impl LibraryQuery {
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let results = ArtistRepository::new(db.lock().await.get_connection())
.find_all()
.find_all(filter)
.await?;
Ok(results.into_iter().map(Into::into).collect())
@@ -109,6 +111,7 @@ impl LibraryQuery {
async fn albums(
&self,
ctx: &Context<'_>,
filter: Option<String>,
offset: Option<i32>,
limit: Option<i32>,
) -> Result<Vec<Album>, Error> {
@@ -118,7 +121,7 @@ impl LibraryQuery {
if device.client.is_some() {
let source = device.client.as_mut().unwrap();
let albums = source
.albums(offset.unwrap_or(0), limit.unwrap_or(100))
.albums(filter, offset.unwrap_or(0), limit.unwrap_or(100))
.await?;
let base_url = device
@@ -144,7 +147,7 @@ impl LibraryQuery {
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
let results = AlbumRepository::new(db.lock().await.get_connection())
.find_all()
.find_all(filter)
.await?;
Ok(results.into_iter().map(Into::into).collect())

View File

@@ -17,6 +17,7 @@ message SearchResponse {}
message GetAlbumsRequest {
int32 limit = 1;
int32 offset = 2;
string filter = 3;
}
message GetAlbumsResponse { repeated metadata.v1alpha1.Album albums = 1; }
@@ -24,6 +25,7 @@ message GetAlbumsResponse { repeated metadata.v1alpha1.Album albums = 1; }
message GetArtistsRequest {
int32 limit = 1;
int32 offset = 2;
string filter = 3;
}
message GetArtistsResponse { repeated metadata.v1alpha1.Artist artists = 1; }
@@ -31,6 +33,7 @@ message GetArtistsResponse { repeated metadata.v1alpha1.Artist artists = 1; }
message GetTracksRequest {
int32 limit = 1;
int32 offset = 2;
string filter = 3;
}
message GetTracksResponse { repeated metadata.v1alpha1.Track tracks = 1; }

View File

@@ -899,6 +899,8 @@ pub struct GetAlbumsRequest {
pub limit: i32,
#[prost(int32, tag = "2")]
pub offset: i32,
#[prost(string, tag = "3")]
pub filter: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -913,6 +915,8 @@ pub struct GetArtistsRequest {
pub limit: i32,
#[prost(int32, tag = "2")]
pub offset: i32,
#[prost(string, tag = "3")]
pub filter: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
@@ -927,6 +931,8 @@ pub struct GetTracksRequest {
pub limit: i32,
#[prost(int32, tag = "2")]
pub offset: i32,
#[prost(string, tag = "3")]
pub filter: ::prost::alloc::string::String,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]

View File

@@ -1,10 +1,10 @@
use futures::future::FutureExt;
use music_player_entity::{album, artist, artist_tracks, track};
use music_player_scanner::scan_directory;
use music_player_storage::{repo::album::AlbumRepository, searcher::Searcher};
use music_player_storage::repo::artist::ArtistRepository;
use music_player_storage::repo::track::TrackRepository;
use music_player_storage::Database;
use music_player_storage::{repo::album::AlbumRepository, searcher::Searcher};
use sea_orm::ActiveModelTrait;
use std::sync::Arc;
use tokio::sync::Mutex;
@@ -82,10 +82,15 @@ impl LibraryService for Library {
async fn get_artists(
&self,
_request: tonic::Request<GetArtistsRequest>,
request: tonic::Request<GetArtistsRequest>,
) -> Result<tonic::Response<GetArtistsResponse>, tonic::Status> {
let request = request.into_inner();
let filter = match request.filter.as_str() {
"" => None,
_ => Some(request.filter),
};
let results = ArtistRepository::new(&self.db.lock().await.get_connection())
.find_all()
.find_all(filter)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;
@@ -97,10 +102,15 @@ impl LibraryService for Library {
async fn get_albums(
&self,
_request: tonic::Request<GetAlbumsRequest>,
request: tonic::Request<GetAlbumsRequest>,
) -> Result<tonic::Response<GetAlbumsResponse>, tonic::Status> {
let request = request.into_inner();
let filter = match request.filter.as_str() {
"" => None,
_ => Some(request.filter),
};
let results = AlbumRepository::new(&self.db.lock().await.get_connection())
.find_all()
.find_all(filter)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;
@@ -112,10 +122,15 @@ impl LibraryService for Library {
async fn get_tracks(
&self,
_request: tonic::Request<GetTracksRequest>,
request: tonic::Request<GetTracksRequest>,
) -> Result<tonic::Response<GetTracksResponse>, tonic::Status> {
let request = request.into_inner();
let filter = match request.filter.as_str() {
"" => None,
_ => Some(request.filter),
};
let tracks = TrackRepository::new(&self.db.lock().await.get_connection())
.find_all(100)
.find_all(filter, 100)
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;

View File

@@ -85,6 +85,7 @@ async fn get_artists() -> Result<(), Box<dyn std::error::Error>> {
let request = tonic::Request::new(GetArtistsRequest {
offset: 0,
limit: 10,
filter: "".to_string(),
});
let response = client.get_artists(request).await?;
let response = response.into_inner();
@@ -118,6 +119,7 @@ async fn get_albums() {
let request = tonic::Request::new(GetAlbumsRequest {
offset: 0,
limit: 10,
filter: "".to_string(),
});
let response = client.get_albums(request).await.unwrap();
let response = response.into_inner();
@@ -150,6 +152,7 @@ async fn get_tracks() {
let request = tonic::Request::new(GetTracksRequest {
offset: 0,
limit: 10,
filter: "".to_string(),
});
let response = client.get_tracks(request).await.unwrap();
let response = response.into_inner();

View File

@@ -85,7 +85,7 @@ pub async fn parse_args(matches: ArgMatches) -> Result<(), Box<dyn std::error::E
return Ok(());
}
let result = client.albums(0, 10000).await?;
let result = client.albums(None, 0, 10000).await?;
let mut builder = Builder::default();
builder.set_columns(["id", "name"]);
@@ -103,7 +103,7 @@ pub async fn parse_args(matches: ArgMatches) -> Result<(), Box<dyn std::error::E
if let Some(_) = matches.subcommand_matches("artists") {
let mut client = LibraryClient::new(settings.host.clone(), settings.port).await?;
let result = client.artists(0, 10000).await?;
let result = client.artists(None, 0, 10000).await?;
let mut builder = Builder::default();
builder.set_columns(["id", "name"]);
@@ -204,7 +204,7 @@ pub async fn parse_args(matches: ArgMatches) -> Result<(), Box<dyn std::error::E
if let Some(_matches) = matches.subcommand_matches("tracks") {
let mut client = LibraryClient::new(settings.host.clone(), settings.port).await?;
let result = client.songs(0, 10000).await?;
let result = client.songs(None, 0, 10000).await?;
let mut builder = Builder::default();
builder.set_columns(["id", "title"]);

View File

@@ -46,6 +46,7 @@ use music_player_webui::start_webui;
use network::{IoEvent, Network};
use owo_colors::OwoColorize;
use scan::auto_scan_music_library;
use sea_orm::{ConnectionTrait, DbBackend, Statement};
use tokio::sync::Mutex;
use tui::{
backend::{Backend, CrosstermBackend},
@@ -191,6 +192,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
mode = env::var("MUSIC_PLAYER_MODE").unwrap_or(mode);
if mode == "server" {
migration::run().await;
let db = Database::new().await;
let conn = db.get_connection();
conn.execute(Statement::from_string(
DbBackend::Sqlite,
"PRAGMA case_sensitive_like=OFF;".to_owned(),
))
.await?;
}
let db_conn = Database::new().await;

View File

@@ -91,7 +91,7 @@ impl<'a> Network<'a> {
}
async fn get_tracks(&mut self) -> Result<(), Error> {
let tracks = self.library.songs(0, 10000).await?;
let tracks = self.library.songs(None, 0, 10000).await?;
let mut app = self.app.lock().await;
app.track_table = TrackTable {
tracks,
@@ -101,7 +101,7 @@ impl<'a> Network<'a> {
}
async fn get_albums(&mut self) -> Result<(), Error> {
let albums = self.library.albums(0, 10000).await?;
let albums = self.library.albums(None, 0, 10000).await?;
let mut app = self.app.lock().await;
app.album_table = AlbumTable {
albums,
@@ -133,7 +133,7 @@ impl<'a> Network<'a> {
}
async fn get_artists(&mut self) -> Result<(), Error> {
let artists = self.library.artists(0, 10000).await?;
let artists = self.library.artists(None, 0, 10000).await?;
let mut app = self.app.lock().await;
app.artist_table = ArtistTable {
artists,

View File

@@ -1,6 +1,6 @@
use anyhow::Error;
use music_player_entity::{album as album_entity, artist as artist_entity, track as track_entity};
use sea_orm::{DatabaseConnection, EntityTrait, ModelTrait, QueryOrder};
use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, ModelTrait, QueryFilter, QueryOrder};
pub struct AlbumRepository {
db: DatabaseConnection,
@@ -35,11 +35,26 @@ impl AlbumRepository {
Ok(album)
}
pub async fn find_all(&self) -> Result<Vec<album_entity::Model>, Error> {
let results = album_entity::Entity::find()
.order_by_asc(album_entity::Column::Title)
.all(&self.db)
.await?;
Ok(results)
pub async fn find_all(
&self,
filter: Option<String>,
) -> Result<Vec<album_entity::Model>, Error> {
match filter {
Some(filter) => {
let results = album_entity::Entity::find()
.filter(album_entity::Column::Title.like(format!("%{}%", filter).as_str()))
.order_by_asc(album_entity::Column::Title)
.all(&self.db)
.await?;
Ok(results)
}
None => {
let results = album_entity::Entity::find()
.order_by_asc(album_entity::Column::Title)
.all(&self.db)
.await?;
Ok(results)
}
}
}
}

View File

@@ -47,11 +47,26 @@ impl ArtistRepository {
Ok(artist)
}
pub async fn find_all(&self) -> Result<Vec<artist_entity::Model>, Error> {
let results = artist_entity::Entity::find()
.order_by_asc(artist_entity::Column::Name)
.all(&self.db)
.await?;
Ok(results)
pub async fn find_all(
&self,
filter: Option<String>,
) -> Result<Vec<artist_entity::Model>, Error> {
match filter {
Some(filter) => {
let results = artist_entity::Entity::find()
.filter(artist_entity::Column::Name.like(format!("%{}%", filter).as_str()))
.order_by_asc(artist_entity::Column::Name)
.all(&self.db)
.await?;
Ok(results)
}
None => {
let results = artist_entity::Entity::find()
.order_by_asc(artist_entity::Column::Name)
.all(&self.db)
.await?;
Ok(results)
}
}
}
}

View File

@@ -39,14 +39,30 @@ impl TrackRepository {
})
}
pub async fn find_all(&self, limit: u64) -> Result<Vec<track_entity::Model>, Error> {
let results: Vec<(track_entity::Model, Vec<artist_entity::Model>)> =
track_entity::Entity::find()
.limit(limit)
.order_by_asc(track_entity::Column::Title)
.find_with_related(artist_entity::Entity)
.all(&self.db)
.await?;
pub async fn find_all(
&self,
filter: Option<String>,
limit: u64,
) -> Result<Vec<track_entity::Model>, Error> {
let results = match filter {
Some(filter) => {
track_entity::Entity::find()
.filter(track_entity::Column::Title.like(format!("%{}%", filter).as_str()))
.limit(limit)
.order_by_asc(track_entity::Column::Title)
.find_with_related(artist_entity::Entity)
.all(&self.db)
.await?
}
None => {
track_entity::Entity::find()
.limit(limit)
.order_by_asc(track_entity::Column::Title)
.find_with_related(artist_entity::Entity)
.all(&self.db)
.await?
}
};
let albums: Vec<(track_entity::Model, Option<album_entity::Model>)> =
track_entity::Entity::find()