mirror of
https://github.com/tsirysndr/music-player.git
synced 2026-01-10 05:37:57 -05:00
feat: improve remote connect
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -3987,6 +3987,7 @@ dependencies = [
|
||||
"music-player-settings",
|
||||
"music-player-storage",
|
||||
"music-player-tracklist",
|
||||
"music-player-types",
|
||||
"tokio",
|
||||
"tokio-tungstenite",
|
||||
"tonic",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use anyhow::Error;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use music_player_types::types::{Album, Artist, Device, Track};
|
||||
use music_player_types::types::{Album, Artist, Device, Playlist, Track};
|
||||
|
||||
use super::{Addon, Browseable, Player, StreamingAddon};
|
||||
|
||||
@@ -78,6 +78,10 @@ impl Browseable for Kodi {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn playlists(&mut self, offset: i32, limit: i32) -> Result<Vec<Playlist>, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn album(&mut self, id: &str) -> Result<Album, Error> {
|
||||
todo!()
|
||||
}
|
||||
@@ -89,6 +93,10 @@ impl Browseable for Kodi {
|
||||
async fn track(&mut self, id: &str) -> Result<Track, Error> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn playlist(&mut self, id: &str) -> Result<Playlist, Error> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -8,7 +8,7 @@ pub mod tononkira;
|
||||
|
||||
use anyhow::Error;
|
||||
use async_trait::async_trait;
|
||||
use music_player_types::types::{Album, Artist, Track};
|
||||
use music_player_types::types::{Album, Artist, Playlist, Track};
|
||||
|
||||
pub trait Addon {
|
||||
fn name(&self) -> &str;
|
||||
@@ -32,9 +32,11 @@ 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 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>;
|
||||
async fn track(&mut self, id: &str) -> Result<Track, Error>;
|
||||
async fn playlist(&mut self, id: &str) -> Result<Playlist, Error>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -5,7 +5,7 @@ use music_player_client::{
|
||||
library::LibraryClient, playback::PlaybackClient, playlist::PlaylistClient,
|
||||
tracklist::TracklistClient,
|
||||
};
|
||||
use music_player_types::types::{Album, Artist, Device, Track};
|
||||
use music_player_types::types::{Album, Artist, Device, Playlist, Track};
|
||||
|
||||
pub struct Client {
|
||||
pub library: LibraryClient,
|
||||
@@ -107,6 +107,11 @@ impl Browseable for Local {
|
||||
Ok(response.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
async fn playlists(&mut self, offset: i32, limit: i32) -> Result<Vec<Playlist>, Error> {
|
||||
let response = self.client.as_mut().unwrap().playlist.list_all().await?;
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn album(&mut self, id: &str) -> Result<Album, Error> {
|
||||
let response = self.client.as_mut().unwrap().library.album(id).await?;
|
||||
match response {
|
||||
@@ -130,6 +135,11 @@ impl Browseable for Local {
|
||||
None => Err(Error::msg("Track not found")),
|
||||
}
|
||||
}
|
||||
|
||||
async fn playlist(&mut self, id: &str) -> Result<Playlist, Error> {
|
||||
let response = self.client.as_mut().unwrap().playlist.find(id).await?;
|
||||
Ok(response)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@@ -19,6 +19,7 @@ rust_library(
|
||||
"//playback:music_player_playback",
|
||||
"//storage:music_player_storage",
|
||||
"//tracklist:music_player_tracklist",
|
||||
"//types:music_player_types",
|
||||
"@crate_index//:tonic",
|
||||
"@crate_index//:futures-util",
|
||||
"@crate_index//:url",
|
||||
|
||||
@@ -22,6 +22,10 @@ version = "0.1.9"
|
||||
path = "../settings"
|
||||
version = "0.1.1"
|
||||
|
||||
[dependencies.music-player-types]
|
||||
path = "../types"
|
||||
version = "0.1.1"
|
||||
|
||||
[dev-dependencies.music-player-playback]
|
||||
path = "../playback"
|
||||
version = "0.1.7"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use anyhow::Error;
|
||||
use music_player_server::api::music::v1alpha1::playlist_service_client::PlaylistServiceClient;
|
||||
use anyhow::{Error, Ok};
|
||||
use music_player_server::api::music::v1alpha1::{
|
||||
playlist_service_client::PlaylistServiceClient, FindAllRequest, GetPlaylistDetailsRequest,
|
||||
};
|
||||
use music_player_settings::{read_settings, Settings};
|
||||
use music_player_types::types::Playlist;
|
||||
use tonic::transport::Channel;
|
||||
pub struct PlaylistClient {
|
||||
client: PlaylistServiceClient<Channel>,
|
||||
@@ -13,21 +16,48 @@ impl PlaylistClient {
|
||||
Ok(Self { client })
|
||||
}
|
||||
|
||||
pub async fn add(&self, id: &str) {}
|
||||
pub async fn find(&mut self, id: &str) -> Result<Playlist, Error> {
|
||||
let request = tonic::Request::new(GetPlaylistDetailsRequest { id: id.to_string() });
|
||||
let response = self.client.get_playlist_details(request).await?;
|
||||
Ok(response.into_inner().into())
|
||||
}
|
||||
|
||||
pub async fn list_songs(&self) {}
|
||||
pub async fn add(&mut self, id: &str) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn clear(&self, id: &str) {}
|
||||
pub async fn list_songs(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn list_all(&self) {}
|
||||
pub async fn clear(&mut self, id: &str) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn play(&self, id: &str) {}
|
||||
pub async fn list_all(&mut self) -> Result<Vec<Playlist>, Error> {
|
||||
let request = tonic::Request::new(FindAllRequest {});
|
||||
let response = self.client.find_all(request).await?;
|
||||
let playlists = response.into_inner().playlists;
|
||||
Ok(playlists.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
pub async fn remove(&self, id: &str) {}
|
||||
pub async fn play(&mut self, id: &str) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn shuffle(&self) {}
|
||||
pub async fn remove(&mut self, id: &str) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn create(&self, name: &str) {}
|
||||
pub async fn shuffle(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn delete_playlist(&self, id: &str) {}
|
||||
pub async fn create(&mut self, name: &str) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn delete_playlist(&mut self, id: &str) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use music_player_types::types::{Album as AlbumType, Song};
|
||||
use music_player_types::types::{Album as AlbumType, RemoteCoverUrl, Song};
|
||||
use sea_orm::{entity::prelude::*, ActiveValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -65,7 +65,36 @@ impl From<AlbumType> for Model {
|
||||
id: album.id.clone(),
|
||||
title: album.title,
|
||||
cover: album.cover,
|
||||
..Default::default()
|
||||
artist: album.artist,
|
||||
artist_id: album.artist_id,
|
||||
year: album.year,
|
||||
tracks: album.tracks.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<AlbumType> for Model {
|
||||
fn into(self) -> AlbumType {
|
||||
AlbumType {
|
||||
id: self.id,
|
||||
title: self.title,
|
||||
cover: self.cover,
|
||||
artist: self.artist,
|
||||
artist_id: self.artist_id,
|
||||
year: self.year,
|
||||
tracks: self.tracks.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteCoverUrl for Model {
|
||||
fn with_remote_cover_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
cover: self
|
||||
.cover
|
||||
.clone()
|
||||
.map(|cover| format!("{}/covers/{}", base_url, cover)),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use music_player_types::types::{Artist as ArtistType, Song};
|
||||
use music_player_types::types::{Artist as ArtistType, RemoteTrackUrl, Song, RemoteCoverUrl};
|
||||
use sea_orm::{entity::prelude::*, ActiveValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -55,3 +55,39 @@ impl From<ArtistType> for Model {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<ArtistType> for Model {
|
||||
fn into(self) -> ArtistType {
|
||||
ArtistType {
|
||||
id: self.id,
|
||||
name: self.name,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteCoverUrl for Model {
|
||||
fn with_remote_cover_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
albums: self
|
||||
.albums
|
||||
.iter()
|
||||
.map(|album| album.with_remote_cover_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Model {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
tracks: self
|
||||
.tracks
|
||||
.iter()
|
||||
.map(|track| track.with_remote_track_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use music_player_types::types::Playlist as PlaylistType;
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -47,3 +48,14 @@ impl Related<super::track::Entity> for Entity {
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
impl Into<PlaylistType> for Model {
|
||||
fn into(self) -> PlaylistType {
|
||||
PlaylistType {
|
||||
id: self.id,
|
||||
name: self.name,
|
||||
description: self.description,
|
||||
tracks: self.tracks.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use music_player_types::types::{Song, Track as TrackType};
|
||||
use music_player_types::types::{Song, Track as TrackType, RemoteTrackUrl};
|
||||
use sea_orm::{entity::prelude::*, ActiveValue};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -192,3 +192,27 @@ impl From<TrackType> for Model {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<TrackType> for Model {
|
||||
fn into(self) -> TrackType {
|
||||
TrackType {
|
||||
id: self.id,
|
||||
title: self.title,
|
||||
artist: self.artist,
|
||||
uri: self.uri,
|
||||
duration: self.duration,
|
||||
album: Some(self.album.into()),
|
||||
artists: self.artists.into_iter().map(Into::into).collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Model {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
uri: format!("{}/tracks/{}", base_url, self.id),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
use crate::simple_broker::SimpleBroker;
|
||||
use async_graphql::Schema;
|
||||
use futures_util::StreamExt;
|
||||
use music_player_discovery::{discover, SERVICE_NAME, XBMC_SERVICE_NAME};
|
||||
use music_player_entity::track as track_entity;
|
||||
use music_player_playback::player::PlayerCommand;
|
||||
use music_player_types::types::Device;
|
||||
use rand::seq::SliceRandom;
|
||||
use schema::{Mutation, Query, Subscription};
|
||||
use std::{sync::Arc, thread};
|
||||
|
||||
use crate::simple_broker::SimpleBroker;
|
||||
use std::{
|
||||
sync::{Arc, Mutex},
|
||||
thread,
|
||||
};
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
|
||||
pub mod schema;
|
||||
pub mod simple_broker;
|
||||
@@ -57,3 +63,23 @@ pub async fn scan_devices() -> Result<Arc<std::sync::Mutex<Vec<Device>>>, Box<dy
|
||||
|
||||
Ok(devices)
|
||||
}
|
||||
|
||||
pub fn load_tracks(
|
||||
player_cmd: &Arc<Mutex<UnboundedSender<PlayerCommand>>>,
|
||||
mut tracks: Vec<track_entity::Model>,
|
||||
position: Option<u32>,
|
||||
shuffle: bool,
|
||||
) {
|
||||
if shuffle {
|
||||
tracks.shuffle(&mut rand::thread_rng());
|
||||
}
|
||||
let player_cmd_tx = player_cmd.lock().unwrap();
|
||||
player_cmd_tx.send(PlayerCommand::Stop).unwrap();
|
||||
player_cmd_tx.send(PlayerCommand::Clear).unwrap();
|
||||
player_cmd_tx
|
||||
.send(PlayerCommand::LoadTracklist { tracks })
|
||||
.unwrap();
|
||||
player_cmd_tx
|
||||
.send(PlayerCommand::PlayTrackAt(position.unwrap_or(0) as usize))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@@ -8,16 +8,11 @@ use music_player_storage::{
|
||||
repo::{album::AlbumRepository, artist::ArtistRepository, track::TrackRepository},
|
||||
Database,
|
||||
};
|
||||
use music_player_types::types;
|
||||
use music_player_types::types::{self, RemoteCoverUrl, RemoteTrackUrl};
|
||||
use sea_orm::{ActiveModelTrait, ActiveValue};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::objects::{
|
||||
album::{Album, RemoteCoverUrl},
|
||||
artist::Artist,
|
||||
search_result::SearchResult,
|
||||
track::{RemoteTrackUrl, Track},
|
||||
};
|
||||
use super::objects::{album::Album, artist::Artist, search_result::SearchResult, track::Track};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LibraryQuery;
|
||||
@@ -69,6 +64,9 @@ impl LibraryQuery {
|
||||
offset: Option<i32>,
|
||||
limit: Option<i32>,
|
||||
) -> Result<Vec<Artist>, Error> {
|
||||
let connected_device = ctx
|
||||
.data::<Arc<StdMutex<HashMap<String, types::Device>>>>()
|
||||
.unwrap();
|
||||
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
|
||||
let mut device = current_device.lock().await;
|
||||
|
||||
@@ -77,7 +75,20 @@ impl LibraryQuery {
|
||||
let artists = source
|
||||
.artists(offset.unwrap_or(0), limit.unwrap_or(100))
|
||||
.await?;
|
||||
return Ok(artists.into_iter().map(Into::into).collect());
|
||||
|
||||
let device = connected_device.lock().unwrap();
|
||||
let device = device.get("current_device").unwrap();
|
||||
let base_url = device.base_url.as_ref().unwrap();
|
||||
|
||||
return Ok(artists
|
||||
.into_iter()
|
||||
.map(|artist| {
|
||||
artist
|
||||
.with_remote_cover_url(base_url.as_str())
|
||||
.with_remote_track_url(base_url.as_str())
|
||||
})
|
||||
.map(Into::into)
|
||||
.collect());
|
||||
}
|
||||
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
@@ -115,7 +126,11 @@ impl LibraryQuery {
|
||||
|
||||
return Ok(result
|
||||
.into_iter()
|
||||
.map(|album| album.with_remote_cover_url(base_url.as_str()))
|
||||
.map(|album| {
|
||||
album
|
||||
.with_remote_cover_url(base_url.as_str())
|
||||
.with_remote_track_url(base_url.as_str())
|
||||
})
|
||||
.collect());
|
||||
}
|
||||
|
||||
@@ -157,6 +172,9 @@ impl LibraryQuery {
|
||||
}
|
||||
|
||||
async fn artist(&self, ctx: &Context<'_>, id: ID) -> Result<Artist, Error> {
|
||||
let connected_device = ctx
|
||||
.data::<Arc<StdMutex<HashMap<String, types::Device>>>>()
|
||||
.unwrap();
|
||||
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
|
||||
let mut device = current_device.lock().await;
|
||||
let id = id.to_string();
|
||||
@@ -164,7 +182,12 @@ impl LibraryQuery {
|
||||
if device.source.is_some() {
|
||||
let source = device.source.as_mut().unwrap();
|
||||
let artist = source.artist(&id).await?;
|
||||
return Ok(artist.into());
|
||||
|
||||
let device = connected_device.lock().unwrap();
|
||||
let device = device.get("current_device").unwrap();
|
||||
let base_url = device.base_url.as_ref().unwrap();
|
||||
|
||||
return Ok(artist.with_remote_track_url(base_url.as_str()).into());
|
||||
}
|
||||
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
@@ -193,7 +216,9 @@ impl LibraryQuery {
|
||||
let device = device.get("current_device").unwrap();
|
||||
let base_url = device.base_url.as_ref().unwrap();
|
||||
|
||||
return Ok(Album::from(album).with_remote_cover_url(base_url.as_str()));
|
||||
return Ok(Album::from(album)
|
||||
.with_remote_cover_url(base_url.as_str())
|
||||
.with_remote_track_url(base_url.as_str()));
|
||||
}
|
||||
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use async_graphql::*;
|
||||
use music_player_entity::album::Model;
|
||||
use music_player_types::types::Album as AlbumType;
|
||||
use music_player_types::types::{Album as AlbumType, RemoteCoverUrl, RemoteTrackUrl};
|
||||
use serde::Serialize;
|
||||
|
||||
use super::track::Track;
|
||||
@@ -52,10 +52,6 @@ impl Album {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RemoteCoverUrl {
|
||||
fn with_remote_cover_url(&self, base_url: &str) -> Self;
|
||||
}
|
||||
|
||||
impl RemoteCoverUrl for Album {
|
||||
fn with_remote_cover_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
@@ -68,6 +64,19 @@ impl RemoteCoverUrl for Album {
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Album {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
tracks: self
|
||||
.tracks
|
||||
.iter()
|
||||
.map(|track| track.with_remote_track_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Model> for Album {
|
||||
fn from(model: Model) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use super::{album::Album, track::Track};
|
||||
use async_graphql::*;
|
||||
use music_player_entity::artist::Model;
|
||||
use music_player_types::types::Artist as ArtistType;
|
||||
use music_player_types::types::{Artist as ArtistType, RemoteTrackUrl};
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Default, Clone, Serialize)]
|
||||
@@ -67,12 +67,29 @@ impl From<Model> for Artist {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ArtistType> for Artist {
|
||||
fn from(artist: ArtistType) -> Self {
|
||||
Self {
|
||||
id: ID(artist.id),
|
||||
name: artist.name,
|
||||
picture: artist.picture.unwrap_or_default(),
|
||||
albums: artist.albums.into_iter().map(Into::into).collect(),
|
||||
songs: artist.songs.into_iter().map(Into::into).collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Artist {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
songs: self
|
||||
.songs
|
||||
.iter()
|
||||
.map(|track| track.with_remote_track_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use async_graphql::*;
|
||||
use music_player_entity::{playlist::Model, select_result};
|
||||
use music_player_types::types::Playlist as PlaylistType;
|
||||
use music_player_types::types::{Playlist as PlaylistType, RemoteTrackUrl};
|
||||
|
||||
use super::track::Track;
|
||||
|
||||
@@ -66,3 +66,17 @@ impl From<PlaylistType> for Playlist {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Playlist {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
tracks: self
|
||||
.tracks
|
||||
.clone()
|
||||
.into_iter()
|
||||
.map(|track| track.with_remote_track_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use async_graphql::*;
|
||||
use music_player_entity::{select_result, track::Model};
|
||||
use music_player_types::types;
|
||||
use music_player_types::types::{self, RemoteTrackUrl};
|
||||
use music_player_types::types::SimplifiedSong as TrackType;
|
||||
use serde::Serialize;
|
||||
|
||||
@@ -88,10 +88,6 @@ impl Track {
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RemoteTrackUrl {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self;
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Track {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use async_graphql::*;
|
||||
use cuid::cuid;
|
||||
use futures_util::Stream;
|
||||
use music_player_addons::CurrentDevice;
|
||||
use music_player_entity::{
|
||||
album as album_entity, artist as artist_entity, folder as folder_entity,
|
||||
playlist as playlist_entity, playlist_tracks as playlist_tracks_entity, select_result,
|
||||
@@ -31,6 +32,15 @@ impl PlaylistQuery {
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
let db = db.lock().await;
|
||||
|
||||
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
|
||||
let mut device = current_device.lock().await;
|
||||
|
||||
if device.source.is_some() {
|
||||
let source = device.source.as_mut().unwrap();
|
||||
let result = source.playlist(&id).await?;
|
||||
return Ok(result.into());
|
||||
}
|
||||
|
||||
let result = PlaylistRepository::new(db.get_connection())
|
||||
.find(id.as_str())
|
||||
.await?;
|
||||
@@ -41,9 +51,18 @@ impl PlaylistQuery {
|
||||
async fn playlists(&self, ctx: &Context<'_>) -> Result<Vec<Playlist>, Error> {
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
let db = db.lock().await;
|
||||
playlist_entity::Entity::find()
|
||||
.order_by_asc(playlist_entity::Column::Name)
|
||||
.all(db.get_connection())
|
||||
|
||||
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
|
||||
let mut device = current_device.lock().await;
|
||||
|
||||
if device.source.is_some() {
|
||||
let source = device.source.as_mut().unwrap();
|
||||
let result = source.playlists(0, 10).await?;
|
||||
return Ok(result.into_iter().map(Into::into).collect());
|
||||
}
|
||||
|
||||
PlaylistRepository::new(db.get_connection())
|
||||
.find_all()
|
||||
.await
|
||||
.map(|playlists| playlists.into_iter().map(Into::into).collect())
|
||||
.map_err(|e| Error::new(e.to_string()))
|
||||
@@ -52,10 +71,8 @@ impl PlaylistQuery {
|
||||
async fn main_playlists(&self, ctx: &Context<'_>) -> Result<Vec<Playlist>, Error> {
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
let db = db.lock().await;
|
||||
playlist_entity::Entity::find()
|
||||
.order_by_asc(playlist_entity::Column::Name)
|
||||
.filter(playlist_entity::Column::FolderId.is_null())
|
||||
.all(db.get_connection())
|
||||
PlaylistRepository::new(db.get_connection())
|
||||
.main_playlists()
|
||||
.await
|
||||
.map(|playlists| playlists.into_iter().map(Into::into).collect())
|
||||
.map_err(|e| Error::new(e.to_string()))
|
||||
@@ -64,10 +81,8 @@ impl PlaylistQuery {
|
||||
async fn recent_playlists(&self, ctx: &Context<'_>) -> Result<Vec<Playlist>, Error> {
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
let db = db.lock().await;
|
||||
playlist_entity::Entity::find()
|
||||
.order_by_desc(playlist_entity::Column::CreatedAt)
|
||||
.limit(10)
|
||||
.all(db.get_connection())
|
||||
PlaylistRepository::new(db.get_connection())
|
||||
.recent_playlists()
|
||||
.await
|
||||
.map(|playlists| playlists.into_iter().map(Into::into).collect())
|
||||
.map_err(|e| Error::new(e.to_string()))
|
||||
|
||||
@@ -12,7 +12,7 @@ use music_player_storage::repo::playlist::PlaylistRepository;
|
||||
use music_player_storage::repo::track::TrackRepository;
|
||||
use music_player_storage::Database;
|
||||
use music_player_tracklist::Tracklist as TracklistState;
|
||||
use music_player_types::types;
|
||||
use music_player_types::types::{self, RemoteCoverUrl, RemoteTrackUrl};
|
||||
use rand::seq::SliceRandom;
|
||||
use sea_orm::{
|
||||
ColumnTrait, EntityTrait, JoinType, ModelTrait, QueryFilter, QueryOrder, QuerySelect,
|
||||
@@ -22,8 +22,10 @@ use std::sync::Mutex as StdMutex;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use tokio::sync::{mpsc::UnboundedSender, Mutex};
|
||||
|
||||
use crate::load_tracks;
|
||||
use crate::simple_broker::SimpleBroker;
|
||||
|
||||
use super::objects::album::Album;
|
||||
use super::{
|
||||
objects::{
|
||||
track::{Track, TrackInput},
|
||||
@@ -234,6 +236,9 @@ impl TracklistMutation {
|
||||
position: Option<u32>,
|
||||
shuffle: bool,
|
||||
) -> Result<bool, Error> {
|
||||
let player_cmd = ctx
|
||||
.data::<Arc<StdMutex<UnboundedSender<PlayerCommand>>>>()
|
||||
.unwrap();
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
let connected_device = ctx
|
||||
.data::<Arc<StdMutex<HashMap<String, types::Device>>>>()
|
||||
@@ -241,35 +246,29 @@ impl TracklistMutation {
|
||||
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
|
||||
let mut device = current_device.lock().await;
|
||||
|
||||
let id = id.to_string();
|
||||
|
||||
if device.source.is_some() {
|
||||
let source = device.source.as_mut().unwrap();
|
||||
// TODO: call grpc to play album
|
||||
}
|
||||
let album = source.album(&id).await?;
|
||||
|
||||
let id = id.to_string();
|
||||
let device = connected_device.lock().unwrap();
|
||||
let device = device.get("current_device").unwrap();
|
||||
let base_url = device.base_url.as_ref().unwrap();
|
||||
|
||||
let album: album_entity::Model = album
|
||||
.with_remote_cover_url(base_url.as_str())
|
||||
.with_remote_track_url(base_url.as_str())
|
||||
.into();
|
||||
let tracks = album.tracks;
|
||||
load_tracks(player_cmd, tracks, position, shuffle);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let result = AlbumRepository::new(db.lock().await.get_connection())
|
||||
.find(&id)
|
||||
.await?;
|
||||
let player_cmd = ctx
|
||||
.data::<Arc<StdMutex<UnboundedSender<PlayerCommand>>>>()
|
||||
.unwrap();
|
||||
let player_cmd_tx = player_cmd.lock().unwrap();
|
||||
player_cmd_tx.send(PlayerCommand::Stop).unwrap();
|
||||
player_cmd_tx.send(PlayerCommand::Clear).unwrap();
|
||||
|
||||
let mut tracks = result.tracks;
|
||||
|
||||
if shuffle {
|
||||
tracks.shuffle(&mut rand::thread_rng());
|
||||
}
|
||||
|
||||
player_cmd_tx
|
||||
.send(PlayerCommand::LoadTracklist { tracks })
|
||||
.unwrap();
|
||||
player_cmd_tx
|
||||
.send(PlayerCommand::PlayTrackAt(position.unwrap_or(0) as usize))
|
||||
.unwrap();
|
||||
load_tracks(player_cmd, result.tracks, position, shuffle);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -280,43 +279,36 @@ impl TracklistMutation {
|
||||
position: Option<u32>,
|
||||
shuffle: bool,
|
||||
) -> Result<bool, Error> {
|
||||
let player_cmd = ctx
|
||||
.data::<Arc<StdMutex<UnboundedSender<PlayerCommand>>>>()
|
||||
.unwrap();
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
let connected_device = ctx
|
||||
.data::<Arc<StdMutex<HashMap<String, types::Device>>>>()
|
||||
.unwrap();
|
||||
let current_device = ctx.data::<Arc<Mutex<CurrentDevice>>>().unwrap();
|
||||
let mut device = current_device.lock().await;
|
||||
let id = id.to_string();
|
||||
|
||||
if device.source.is_some() {
|
||||
let source = device.source.as_mut().unwrap();
|
||||
// TODO: call grpc to play artist tracks
|
||||
let artist = source.artist(&id).await?;
|
||||
|
||||
let device = connected_device.lock().unwrap();
|
||||
let device = device.get("current_device").unwrap();
|
||||
let base_url = device.base_url.as_ref().unwrap();
|
||||
|
||||
let artist: artist_entity::Model =
|
||||
artist.with_remote_track_url(base_url.as_str()).into();
|
||||
load_tracks(player_cmd, artist.tracks, position, shuffle);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let id = id.to_string();
|
||||
let mut artist = ArtistRepository::new(db.lock().await.get_connection())
|
||||
let artist = ArtistRepository::new(db.lock().await.get_connection())
|
||||
.find(&id)
|
||||
.await?;
|
||||
|
||||
let player_cmd = ctx
|
||||
.data::<Arc<StdMutex<UnboundedSender<PlayerCommand>>>>()
|
||||
.unwrap();
|
||||
let player_cmd_tx = player_cmd.lock().unwrap();
|
||||
player_cmd_tx.send(PlayerCommand::Stop).unwrap();
|
||||
player_cmd_tx.send(PlayerCommand::Clear).unwrap();
|
||||
|
||||
if shuffle {
|
||||
artist.tracks.shuffle(&mut rand::thread_rng());
|
||||
}
|
||||
|
||||
player_cmd_tx
|
||||
.send(PlayerCommand::LoadTracklist {
|
||||
tracks: artist.tracks,
|
||||
})
|
||||
.unwrap();
|
||||
player_cmd_tx
|
||||
.send(PlayerCommand::PlayTrackAt(position.unwrap_or(0) as usize))
|
||||
.unwrap();
|
||||
|
||||
load_tracks(player_cmd, artist.tracks, position, shuffle);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -327,6 +319,9 @@ impl TracklistMutation {
|
||||
position: Option<u32>,
|
||||
shuffle: bool,
|
||||
) -> Result<bool, Error> {
|
||||
let player_cmd = ctx
|
||||
.data::<Arc<std::sync::Mutex<UnboundedSender<PlayerCommand>>>>()
|
||||
.unwrap();
|
||||
let db = ctx.data::<Arc<Mutex<Database>>>().unwrap();
|
||||
let db = db.lock().await;
|
||||
let connected_device = ctx
|
||||
@@ -339,32 +334,27 @@ impl TracklistMutation {
|
||||
|
||||
if device.source.is_some() {
|
||||
let source = device.source.as_mut().unwrap();
|
||||
// TODO: call grpc to play playlist
|
||||
let result = source.playlist(&id).await?;
|
||||
|
||||
let device = connected_device.lock().unwrap();
|
||||
let device = device.get("current_device").unwrap();
|
||||
let base_url = device.base_url.as_ref().unwrap();
|
||||
|
||||
let tracks = result.with_remote_track_url(base_url.as_str()).tracks;
|
||||
let tracks: Vec<track_entity::Model> = tracks.into_iter().map(Into::into).collect();
|
||||
|
||||
load_tracks(player_cmd, tracks, position, shuffle);
|
||||
return Ok(true);
|
||||
}
|
||||
|
||||
let playlist = PlaylistRepository::new(db.get_connection())
|
||||
.find(id.as_str())
|
||||
.await?;
|
||||
|
||||
let mut tracks: Vec<track_entity::Model> =
|
||||
let tracks: Vec<track_entity::Model> =
|
||||
playlist.tracks.into_iter().map(Into::into).collect();
|
||||
|
||||
if shuffle {
|
||||
tracks.shuffle(&mut rand::thread_rng());
|
||||
}
|
||||
|
||||
let player_cmd = ctx
|
||||
.data::<Arc<std::sync::Mutex<UnboundedSender<PlayerCommand>>>>()
|
||||
.unwrap();
|
||||
let player_cmd_tx = player_cmd.lock().unwrap();
|
||||
player_cmd_tx.send(PlayerCommand::Stop).unwrap();
|
||||
player_cmd_tx.send(PlayerCommand::Clear).unwrap();
|
||||
player_cmd_tx
|
||||
.send(PlayerCommand::LoadTracklist { tracks })
|
||||
.unwrap();
|
||||
player_cmd_tx
|
||||
.send(PlayerCommand::PlayTrackAt(position.unwrap_or(0) as usize))
|
||||
.unwrap();
|
||||
load_tracks(player_cmd, tracks, position, shuffle);
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ message GetPlaylistDetailsRequest { string id = 1; }
|
||||
message GetPlaylistDetailsResponse {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
repeated metadata.v1alpha1.Track tracks = 3;
|
||||
string description = 3;
|
||||
repeated metadata.v1alpha1.Track tracks = 4;
|
||||
}
|
||||
|
||||
message CreateFolderRequest {
|
||||
|
||||
@@ -3005,7 +3005,9 @@ pub struct GetPlaylistDetailsResponse {
|
||||
pub id: ::prost::alloc::string::String,
|
||||
#[prost(string, tag = "2")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
#[prost(message, repeated, tag = "3")]
|
||||
#[prost(string, tag = "3")]
|
||||
pub description: ::prost::alloc::string::String,
|
||||
#[prost(message, repeated, tag = "4")]
|
||||
pub tracks: ::prost::alloc::vec::Vec<super::super::metadata::v1alpha1::Track>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
|
||||
@@ -15,8 +15,9 @@ pub mod api {
|
||||
#[path = ""]
|
||||
pub mod music {
|
||||
use music_player_entity::folder;
|
||||
use music_player_types::types::Playlist;
|
||||
|
||||
use self::v1alpha1::GetFolderDetailsResponse;
|
||||
use self::v1alpha1::{GetFolderDetailsResponse, GetPlaylistDetailsResponse};
|
||||
|
||||
#[path = "music.v1alpha1.rs"]
|
||||
pub mod v1alpha1;
|
||||
@@ -31,6 +32,28 @@ pub mod api {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Playlist> for GetPlaylistDetailsResponse {
|
||||
fn into(self) -> Playlist {
|
||||
Playlist {
|
||||
id: self.id,
|
||||
name: self.name,
|
||||
description: Some(self.description),
|
||||
tracks: self.tracks.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Playlist> for GetPlaylistDetailsResponse {
|
||||
fn from(playlist: Playlist) -> Self {
|
||||
Self {
|
||||
id: playlist.id,
|
||||
name: playlist.name,
|
||||
description: playlist.description.unwrap_or_default(),
|
||||
tracks: playlist.tracks.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[path = ""]
|
||||
@@ -130,6 +153,19 @@ pub mod api {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<types::Track> for Song {
|
||||
fn from(track: types::Track) -> Self {
|
||||
Self {
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
duration: track.duration.unwrap_or_default(),
|
||||
track_number: track.track_number.unwrap_or_default() as i32,
|
||||
artists: track.artists.into_iter().map(Into::into).collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<artist::Model> for SongArtist {
|
||||
fn from(model: artist::Model) -> Self {
|
||||
Self {
|
||||
@@ -140,6 +176,16 @@ pub mod api {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<types::Artist> for SongArtist {
|
||||
fn from(artist: types::Artist) -> Self {
|
||||
Self {
|
||||
id: artist.id,
|
||||
name: artist.name,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<track::Model> for ArtistSong {
|
||||
fn from(model: track::Model) -> Self {
|
||||
Self {
|
||||
@@ -154,6 +200,20 @@ pub mod api {
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<types::Track> for ArtistSong {
|
||||
fn into(self) -> types::Track {
|
||||
types::Track {
|
||||
id: self.id,
|
||||
title: self.title,
|
||||
duration: Some(self.duration),
|
||||
track_number: Some(u32::try_from(self.track_number).unwrap_or_default()),
|
||||
disc_number: u32::try_from(self.disc_number).unwrap_or_default(),
|
||||
artists: self.artists.into_iter().map(Into::into).collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<types::Track> for Track {
|
||||
fn into(self) -> types::Track {
|
||||
types::Track {
|
||||
@@ -173,12 +233,45 @@ pub mod api {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<types::Track> for Track {
|
||||
fn from(track: types::Track) -> Self {
|
||||
Self {
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
uri: track.uri,
|
||||
duration: track.duration.unwrap_or_default(),
|
||||
track_number: i32::try_from(track.track_number.unwrap_or_default()).unwrap(),
|
||||
disc_number: i32::try_from(track.disc_number).unwrap(),
|
||||
artists: track.artists.into_iter().map(Into::into).collect(),
|
||||
artist: track.artist,
|
||||
album: match track.album {
|
||||
Some(album) => Some(album.into()),
|
||||
None => None,
|
||||
},
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<types::Artist> for Artist {
|
||||
fn into(self) -> types::Artist {
|
||||
types::Artist {
|
||||
id: self.id,
|
||||
name: self.name,
|
||||
picture: Some(self.picture),
|
||||
albums: self.albums.into_iter().map(Into::into).collect(),
|
||||
songs: self.songs.into_iter().map(Into::into).collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<types::Artist> for Artist {
|
||||
fn from(artist: types::Artist) -> Self {
|
||||
Self {
|
||||
id: artist.id,
|
||||
name: artist.name,
|
||||
picture: artist.picture.unwrap_or_default(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -220,5 +313,19 @@ pub mod api {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<types::Album> for Album {
|
||||
fn from(album: types::Album) -> Self {
|
||||
Self {
|
||||
id: album.id,
|
||||
title: album.title,
|
||||
cover: album.cover.unwrap_or_default(),
|
||||
artist: album.artist,
|
||||
year: i32::try_from(album.year.unwrap_or_default()).unwrap_or_default(),
|
||||
tracks: album.tracks.into_iter().map(Into::into).collect(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use music_player_entity::{playlist, playlist_tracks, track};
|
||||
use music_player_storage::Database;
|
||||
use music_player_storage::{repo::playlist::PlaylistRepository, Database};
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, ModelTrait, QueryFilter, Set,
|
||||
};
|
||||
@@ -203,71 +203,24 @@ impl PlaylistService for Playlist {
|
||||
&self,
|
||||
_request: tonic::Request<FindAllRequest>,
|
||||
) -> Result<tonic::Response<FindAllResponse>, tonic::Status> {
|
||||
playlist::Entity::find()
|
||||
.all(self.db.lock().await.get_connection())
|
||||
let result = PlaylistRepository::new(self.db.lock().await.get_connection())
|
||||
.find_all()
|
||||
.await
|
||||
.map(|playlists| {
|
||||
tonic::Response::new(FindAllResponse {
|
||||
playlists: playlists
|
||||
.into_iter()
|
||||
.map(|playlist| GetPlaylistDetailsResponse {
|
||||
id: playlist.id,
|
||||
name: playlist.name,
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.map_err(|e| tonic::Status::internal(e.to_string()))?;
|
||||
|
||||
let response = FindAllResponse {
|
||||
..Default::default()
|
||||
};
|
||||
Ok(tonic::Response::new(response))
|
||||
.map_err(|_| tonic::Status::internal("Failed to get playlist"))?;
|
||||
Ok(tonic::Response::new(FindAllResponse {
|
||||
playlists: result.into_iter().map(Into::into).collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
async fn get_playlist_details(
|
||||
&self,
|
||||
request: tonic::Request<GetPlaylistDetailsRequest>,
|
||||
) -> Result<tonic::Response<GetPlaylistDetailsResponse>, tonic::Status> {
|
||||
let result = playlist::Entity::find_by_id(request.get_ref().id.clone())
|
||||
.one(self.db.lock().await.get_connection())
|
||||
.await;
|
||||
match result {
|
||||
Ok(playlist) => {
|
||||
if playlist.is_none() {
|
||||
return Err(tonic::Status::not_found("Playlist not found"));
|
||||
}
|
||||
playlist
|
||||
.clone()
|
||||
.unwrap()
|
||||
.find_related(track::Entity)
|
||||
.all(self.db.lock().await.get_connection())
|
||||
.await
|
||||
.map(|tracks| {
|
||||
tonic::Response::new(GetPlaylistDetailsResponse {
|
||||
id: playlist.clone().unwrap().id,
|
||||
name: playlist.clone().unwrap().name,
|
||||
tracks: tracks
|
||||
.into_iter()
|
||||
.map(|track| Track {
|
||||
id: track.id,
|
||||
title: track.title,
|
||||
uri: track.uri,
|
||||
duration: track.duration.unwrap_or_default(),
|
||||
disc_number: i32::try_from(track.track.unwrap_or_default())
|
||||
.unwrap(),
|
||||
..Default::default()
|
||||
})
|
||||
.collect(),
|
||||
..Default::default()
|
||||
})
|
||||
})
|
||||
.map_err(|_| tonic::Status::internal("Failed to get playlist items"))
|
||||
}
|
||||
Err(_) => return Err(tonic::Status::internal("Failed to get playlist")),
|
||||
}
|
||||
let result = PlaylistRepository::new(self.db.lock().await.get_connection())
|
||||
.find(&request.get_ref().id)
|
||||
.await
|
||||
.map_err(|_| tonic::Status::internal("Failed to get playlist"))?;
|
||||
Ok(tonic::Response::new(result.into()))
|
||||
}
|
||||
|
||||
async fn create_folder(
|
||||
|
||||
@@ -5,7 +5,8 @@ use music_player_entity::{
|
||||
};
|
||||
use music_player_types::types::Playlist;
|
||||
use sea_orm::{
|
||||
ColumnTrait, DatabaseConnection, EntityTrait, JoinType, QueryFilter, QuerySelect, RelationTrait,
|
||||
ColumnTrait, DatabaseConnection, EntityTrait, JoinType, QueryFilter, QueryOrder, QuerySelect,
|
||||
RelationTrait,
|
||||
};
|
||||
|
||||
pub struct PlaylistRepository {
|
||||
@@ -90,6 +91,31 @@ impl PlaylistRepository {
|
||||
}
|
||||
|
||||
pub async fn find_all(&self) -> Result<Vec<Playlist>, Error> {
|
||||
todo!()
|
||||
playlist_entity::Entity::find()
|
||||
.order_by_asc(playlist_entity::Column::Name)
|
||||
.all(&self.db)
|
||||
.await
|
||||
.map(|playlists| playlists.into_iter().map(Into::into).collect())
|
||||
.map_err(|e| Error::msg(e.to_string()))
|
||||
}
|
||||
|
||||
pub async fn main_playlists(&self) -> Result<Vec<Playlist>, Error> {
|
||||
playlist_entity::Entity::find()
|
||||
.order_by_asc(playlist_entity::Column::Name)
|
||||
.filter(playlist_entity::Column::FolderId.is_null())
|
||||
.all(&self.db)
|
||||
.await
|
||||
.map(|playlists| playlists.into_iter().map(Into::into).collect())
|
||||
.map_err(|e| Error::msg(e.to_string()))
|
||||
}
|
||||
|
||||
pub async fn recent_playlists(&self) -> Result<Vec<Playlist>, Error> {
|
||||
playlist_entity::Entity::find()
|
||||
.order_by_desc(playlist_entity::Column::CreatedAt)
|
||||
.limit(10)
|
||||
.all(&self.db)
|
||||
.await
|
||||
.map(|playlists| playlists.into_iter().map(Into::into).collect())
|
||||
.map_err(|e| Error::msg(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ pub struct Artist {
|
||||
pub id: String,
|
||||
pub name: String,
|
||||
pub picture: Option<String>,
|
||||
pub albums: Vec<Album>,
|
||||
pub songs: Vec<Track>,
|
||||
}
|
||||
|
||||
impl From<Document> for Album {
|
||||
@@ -412,3 +414,84 @@ pub struct Folder {
|
||||
pub name: String,
|
||||
pub playlists: Vec<Playlist>,
|
||||
}
|
||||
|
||||
pub trait RemoteTrackUrl {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self;
|
||||
}
|
||||
|
||||
pub trait RemoteCoverUrl {
|
||||
fn with_remote_cover_url(&self, base_url: &str) -> Self;
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Track {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
uri: format!("{}/tracks/{}", base_url, self.id),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteCoverUrl for Album {
|
||||
fn with_remote_cover_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
cover: match self.cover {
|
||||
Some(ref cover) => Some(format!("{}/covers/{}", base_url, cover)),
|
||||
None => None,
|
||||
},
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Album {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
tracks: self
|
||||
.tracks
|
||||
.iter()
|
||||
.map(|track| track.with_remote_track_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteCoverUrl for Artist {
|
||||
fn with_remote_cover_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
albums: self
|
||||
.albums
|
||||
.iter()
|
||||
.map(|album| album.with_remote_cover_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Artist {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
songs: self
|
||||
.songs
|
||||
.iter()
|
||||
.map(|track| track.with_remote_track_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RemoteTrackUrl for Playlist {
|
||||
fn with_remote_track_url(&self, base_url: &str) -> Self {
|
||||
Self {
|
||||
tracks: self
|
||||
.tracks
|
||||
.iter()
|
||||
.map(|track| track.with_remote_track_url(base_url))
|
||||
.collect(),
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user