feat: implement Create/Update/Delete Playlists

bump version code
This commit is contained in:
Tsiry Sandratraina
2022-09-19 22:24:16 +00:00
parent 7cfdc79a75
commit 4ff83eecda
19 changed files with 298 additions and 95 deletions

7
Cargo.lock generated
View File

@@ -1578,14 +1578,14 @@ version = "0.1.0"
[[package]]
name = "music-player-entity"
version = "0.1.2"
version = "0.1.3"
dependencies = [
"sea-orm",
]
[[package]]
name = "music-player-migration"
version = "0.1.1"
version = "0.1.2"
dependencies = [
"dotenv",
"music-player-settings",
@@ -1635,7 +1635,7 @@ dependencies = [
[[package]]
name = "music-player-server"
version = "0.1.3"
version = "0.1.4"
dependencies = [
"cuid",
"futures",
@@ -1652,6 +1652,7 @@ dependencies = [
"tonic",
"tonic-build",
"tonic-web",
"uuid",
]
[[package]]

View File

@@ -1,6 +1,6 @@
[package]
name = "music-player"
version = "0.1.3"
version = "0.1.4"
edition = "2021"
repository = "https://github.com/tsirysndr/music-player"
license = "MIT"
@@ -20,15 +20,15 @@ path = "src/main.rs"
[dependencies.music-player-server]
path = "server"
version = "0.1.3"
version = "0.1.4"
[dependencies.music-player-playback]
path = "playback"
version = "0.1.2"
version = "0.1.3"
[dependencies.music-player-scanner]
path = "scanner"
version = "0.1.2"
version = "0.1.3"
[dependencies.music-player-entity]
path = "entity"
@@ -36,7 +36,7 @@ version = "0.1.2"
[dependencies.music-player-migration]
path = "migration"
version = "0.1.1"
version = "0.1.2"
[dependencies.music-player-settings]
path = "settings"
@@ -52,7 +52,7 @@ version = "0.1.0"
[dependencies.music-player-tracklist]
path = "tracklist"
version = "0.1.2"
version = "0.1.3"
[dependencies.sea-orm-migration]
version = "^0.9.0"

View File

@@ -81,7 +81,8 @@ SUBCOMMANDS:
- [x] Scan music library
- [x] Play/Pause/Stop music
- [x] Next/Previous track
- [ ] Create/Delete playlists
- [x] Create/Delete playlists
- [ ] Music Player client
- [ ] Terminal UI (using [tui-rs](https://github.com/fdehau/tui-rs))
- [ ] Web UI
- [ ] Desktop version (using [gtk-rs](https://gtk-rs.org/))

View File

@@ -1,6 +1,6 @@
[package]
name = "music-player-entity"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
repository = "https://github.com/tsirysndr/music-player"
license = "MIT"

View File

@@ -3,7 +3,7 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "addon")]
pub struct Model {
#[sea_orm(primary_key)]
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub name: String,
pub description: String,

View File

@@ -3,7 +3,7 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "album")]
pub struct Model {
#[sea_orm(primary_key)]
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub title: String,
pub artist: String,

View File

@@ -3,7 +3,7 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "artist")]
pub struct Model {
#[sea_orm(primary_key)]
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub name: String,
}

View File

@@ -3,7 +3,7 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "playlist")]
pub struct Model {
#[sea_orm(primary_key)]
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub name: String,
}

View File

@@ -3,8 +3,8 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "playlist_track")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub playlist_id: String,
pub track_id: String,
}

View File

@@ -3,7 +3,7 @@ use sea_orm::entity::prelude::*;
#[derive(Clone, Debug, Default, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "track")]
pub struct Model {
#[sea_orm(primary_key)]
#[sea_orm(primary_key, auto_increment = false)]
pub id: String,
pub title: String,
pub artist: String,

View File

@@ -1,6 +1,6 @@
[package]
name = "music-player-migration"
version = "0.1.1"
version = "0.1.2"
edition = "2021"
repository = "https://github.com/tsirysndr/music-player"
license = "MIT"

View File

@@ -105,7 +105,7 @@ impl MigrationTrait for Migration {
.if_not_exists()
.col(
ColumnDef::new(PlaylistTrack::Id)
.integer()
.string()
.not_null()
.primary_key(),
)
@@ -199,7 +199,6 @@ enum Track {
Channels,
Duration,
Uri,
TracklistId,
AlbumId,
}

View File

@@ -1,6 +1,6 @@
[package]
name = "music-player-playback"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
repository = "https://github.com/tsirysndr/music-player"
license = "MIT"
@@ -11,11 +11,11 @@ description = "The playback logic for music player"
[dependencies.music-player-tracklist]
path = "../tracklist"
version = "0.1.0"
version = "0.1.3"
[dependencies.music-player-entity]
path = "../entity"
version = "0.1.2"
version = "0.1.3"
[dependencies]
cpal = "0.13.0"

View File

@@ -1,6 +1,6 @@
[package]
name = "music-player-scanner"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
repository = "https://github.com/tsirysndr/music-player"
license = "MIT"
@@ -15,7 +15,7 @@ version = "0.1.0"
[dependencies.music-player-entity]
path = "../entity"
version = "0.1.2"
version = "0.1.3"
[dependencies.music-player-storage]
path = "../storage"

View File

@@ -1,6 +1,6 @@
[package]
name = "music-player-server"
version = "0.1.3"
version = "0.1.4"
edition = "2021"
repository = "https://github.com/tsirysndr/music-player"
license = "MIT"
@@ -17,7 +17,7 @@ version = "0.1.1"
[dependencies.music-player-scanner]
path = "../scanner"
version = "0.1.1"
version = "0.1.3"
[dependencies.music-player-settings]
path = "../settings"
@@ -29,7 +29,7 @@ version = "0.1.0"
[dependencies.music-player-entity]
path = "../entity"
version = "0.1.0"
version = "0.1.3"
[dependencies]
owo-colors = "3.5.0"
@@ -41,6 +41,7 @@ tonic-web = "0.4.0"
futures = "0.3.24"
cuid = "1.2.0"
md5 = "0.7.0"
uuid = "1.1.2"
[build-dependencies]
tonic-build = "0.8"

View File

@@ -2,47 +2,83 @@ syntax = "proto3";
package music.v1alpha1;
message CreateRequest {}
import "metadata/v1alpha1/track.proto";
import "objects/v1alpha1/playlist.proto";
message CreateResponse {}
message CreateRequest {
string name = 1;
repeated metadata.v1alpha1.Track tracks = 2;
}
message DeleteRequest {}
message CreateResponse {
string id = 1;
string name = 2;
repeated metadata.v1alpha1.Track tracks = 3;
}
message DeleteResponse {}
message DeleteRequest { string id = 1; }
message GetItemsRequest {}
message DeleteResponse {
string id = 1;
string name = 2;
}
message GetItemsResponse {}
message GetItemsRequest { string id = 1; }
message SaveRequest {}
message GetItemsResponse {
string id = 1;
string name = 2;
repeated metadata.v1alpha1.Track tracks = 3;
}
message SaveResponse {}
message RenameRequest {
string id = 1;
string name = 2;
}
message RenameRequest {}
message RenameResponse {
string id = 1;
string name = 2;
}
message RenameResponse {}
message RemoveItemRequest {
string id = 1;
string track_id = 2;
}
message RemoveItemRequest {}
message RemoveItemResponse {
string id = 1;
string name = 2;
repeated metadata.v1alpha1.Track tracks = 3;
}
message RemoveItemResponse {}
message AddItemRequest {
string id = 1;
string track_id = 2;
}
message AddItemRequest {}
message AddItemResponse {}
message AddItemResponse {
string id = 1;
string name = 2;
repeated metadata.v1alpha1.Track tracks = 3;
}
message FindAllRequest {}
message FindAllResponse {}
message FindAllResponse { repeated GetPlaylistDetailsResponse playlists = 1; }
message GetPlaylistDetailsRequest {}
message GetPlaylistDetailsRequest { string id = 1; }
message GetPlaylistDetailsResponse {}
message GetPlaylistDetailsResponse {
string id = 1;
string name = 2;
repeated metadata.v1alpha1.Track tracks = 3;
}
service PlaylistService {
rpc Create(CreateRequest) returns (CreateResponse) {}
rpc Delete(DeleteRequest) returns (DeleteResponse) {}
rpc GetItems(GetItemsRequest) returns (GetItemsResponse) {}
rpc Save(SaveRequest) returns (SaveResponse) {}
rpc Rename(RenameRequest) returns (RenameResponse) {}
rpc RemoveItem(RemoveItemRequest) returns (RemoveItemResponse) {}
rpc AddItem(AddItemRequest) returns (AddItemResponse) {}

View File

@@ -158,7 +158,8 @@ impl LibraryService for Library {
id: track.id,
title: track.title,
uri: track.uri,
disc_number: format!("{}", track.track.unwrap_or(0)).parse().unwrap(),
duration: track.duration.unwrap_or_default(),
disc_number: i32::try_from(track.track.unwrap_or_default()).unwrap(),
artists: vec![Artist {
name: track.artist,
..Default::default()

View File

@@ -1,15 +1,19 @@
use std::sync::Arc;
use music_player_entity::playlist;
use music_player_entity::{playlist, playlist_tracks, track};
use music_player_storage::Database;
use sea_orm::EntityTrait;
use sea_orm::{
ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, ModelTrait, QueryFilter, Set,
};
use std::sync::Arc;
use uuid::Uuid;
use crate::api::v1alpha1::{
playlist_service_server::PlaylistService, AddItemRequest, AddItemResponse, CreateRequest,
CreateResponse, DeleteRequest, DeleteResponse, FindAllRequest, FindAllResponse,
GetItemsRequest, GetItemsResponse, GetPlaylistDetailsRequest, GetPlaylistDetailsResponse,
RemoveItemRequest, RemoveItemResponse, RenameRequest, RenameResponse, SaveRequest,
SaveResponse,
use crate::{
api::v1alpha1::{
playlist_service_server::PlaylistService, AddItemRequest, AddItemResponse, CreateRequest,
CreateResponse, DeleteRequest, DeleteResponse, FindAllRequest, FindAllResponse,
GetItemsRequest, GetItemsResponse, GetPlaylistDetailsRequest, GetPlaylistDetailsResponse,
RemoveItemRequest, RemoveItemResponse, RenameRequest, RenameResponse,
},
metadata::v1alpha1::Track,
};
pub struct Playlist {
@@ -26,53 +30,167 @@ impl Playlist {
impl PlaylistService for Playlist {
async fn create(
&self,
_request: tonic::Request<CreateRequest>,
request: tonic::Request<CreateRequest>,
) -> Result<tonic::Response<CreateResponse>, tonic::Status> {
let response = CreateResponse {};
Ok(tonic::Response::new(response))
let item = playlist::ActiveModel {
id: ActiveValue::set(Uuid::new_v4().to_string()),
name: ActiveValue::set(request.get_ref().name.clone()),
};
match item.insert(self.db.get_connection()).await {
Ok(saved) => {
for track in request.get_ref().tracks.iter() {
let item = playlist_tracks::ActiveModel {
id: ActiveValue::set(Uuid::new_v4().to_string()),
playlist_id: ActiveValue::set(saved.id.clone()),
track_id: ActiveValue::set(track.id.clone()),
};
match item.insert(self.db.get_connection()).await {
Ok(_) => (),
Err(_) => (),
}
}
Ok(tonic::Response::new(CreateResponse {
id: saved.id,
name: saved.name,
tracks: request.get_ref().tracks.clone(),
..Default::default()
}))
}
Err(e) => Err(tonic::Status::internal(e.to_string())),
}
}
async fn delete(
&self,
_request: tonic::Request<DeleteRequest>,
request: tonic::Request<DeleteRequest>,
) -> Result<tonic::Response<DeleteResponse>, tonic::Status> {
let response = DeleteResponse {};
Ok(tonic::Response::new(response))
playlist::Entity::delete_by_id(request.get_ref().id.clone())
.exec(self.db.get_connection())
.await
.map(|_| {
tonic::Response::new(DeleteResponse {
id: request.get_ref().id.clone(),
..Default::default()
})
})
.map_err(|_| tonic::Status::internal("Failed to delete playlist"))
}
async fn get_items(
&self,
_request: tonic::Request<GetItemsRequest>,
request: tonic::Request<GetItemsRequest>,
) -> Result<tonic::Response<GetItemsResponse>, tonic::Status> {
let response = GetItemsResponse {};
Ok(tonic::Response::new(response))
}
async fn save(
&self,
_request: tonic::Request<SaveRequest>,
) -> Result<tonic::Response<SaveResponse>, tonic::Status> {
let response = SaveResponse {};
Ok(tonic::Response::new(response))
let result = playlist::Entity::find_by_id(request.get_ref().id.clone())
.one(self.db.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.get_connection())
.await
.map(|tracks| {
tonic::Response::new(GetItemsResponse {
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")),
}
}
async fn rename(
&self,
_request: tonic::Request<RenameRequest>,
request: tonic::Request<RenameRequest>,
) -> Result<tonic::Response<RenameResponse>, tonic::Status> {
let response = RenameResponse {};
Ok(tonic::Response::new(response))
let updates = playlist::ActiveModel {
name: Set(request.get_ref().name.clone()),
..Default::default()
};
playlist::Entity::update(updates)
.filter(playlist::Column::Id.eq("test"))
.exec(self.db.get_connection())
.await
.map(|updated| {
tonic::Response::new(RenameResponse {
id: updated.id,
name: updated.name,
..Default::default()
})
})
.map_err(|_| tonic::Status::internal("Failed to rename playlist"))
}
async fn remove_item(
&self,
_request: tonic::Request<RemoveItemRequest>,
request: tonic::Request<RemoveItemRequest>,
) -> Result<tonic::Response<RemoveItemResponse>, tonic::Status> {
let response = RemoveItemResponse {};
Ok(tonic::Response::new(response))
let item = playlist_tracks::Entity::find()
.filter(
playlist_tracks::Column::PlaylistId
.eq(request.get_ref().id.clone())
.and(playlist_tracks::Column::TrackId.eq(request.get_ref().track_id.clone())),
)
.one(self.db.get_connection())
.await;
if item.is_err() {
return Err(tonic::Status::internal(
"Failed to remove item from playlist",
));
}
playlist_tracks::Entity::delete(playlist_tracks::ActiveModel {
id: Set(item.unwrap().unwrap().id),
..Default::default()
})
.exec(self.db.get_connection())
.await
.map(|_| {
tonic::Response::new(RemoveItemResponse {
id: request.get_ref().id.clone(),
..Default::default()
})
})
.map_err(|_| tonic::Status::internal("Failed to remove item from playlist"))
}
async fn add_item(
&self,
_request: tonic::Request<AddItemRequest>,
request: tonic::Request<AddItemRequest>,
) -> Result<tonic::Response<AddItemResponse>, tonic::Status> {
let response = AddItemResponse {};
Ok(tonic::Response::new(response))
let item = playlist_tracks::ActiveModel {
id: ActiveValue::set(Uuid::new_v4().to_string()),
playlist_id: ActiveValue::set(request.get_ref().id.clone()),
track_id: ActiveValue::set(request.get_ref().track_id.clone()),
};
match item.insert(self.db.get_connection()).await {
Ok(saved) => Ok(tonic::Response::new(AddItemResponse {
id: saved.id,
..Default::default()
})),
Err(e) => Err(tonic::Status::internal(e.to_string())),
}
}
async fn find_all(
&self,
_request: tonic::Request<FindAllRequest>,
@@ -80,21 +198,67 @@ impl PlaylistService for Playlist {
playlist::Entity::find()
.all(self.db.get_connection())
.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 {};
let response = FindAllResponse {
..Default::default()
};
Ok(tonic::Response::new(response))
}
async fn get_playlist_details(
&self,
_request: tonic::Request<GetPlaylistDetailsRequest>,
request: tonic::Request<GetPlaylistDetailsRequest>,
) -> Result<tonic::Response<GetPlaylistDetailsResponse>, tonic::Status> {
playlist::Entity::find()
.all(self.db.get_connection())
.await
.map_err(|e| tonic::Status::internal(e.to_string()))?;
let response = GetPlaylistDetailsResponse {};
Ok(tonic::Response::new(response))
let result = playlist::Entity::find_by_id(request.get_ref().id.clone())
.one(self.db.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.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")),
}
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "music-player-tracklist"
version = "0.1.2"
version = "0.1.3"
edition = "2021"
repository = "https://github.com/tsirysndr/music-player"
license = "MIT"
@@ -12,7 +12,7 @@ description = "The tracklist manager for the music player"
[dependencies.music-player-entity]
path = "../entity"
version = "0.1.2"
version = "0.1.3"
[dependencies]
atlist-rs = "0.2.1"