Compare commits

..

17 Commits

Author SHA1 Message Date
chmanie a1987869c2 Styling fixes
CI checks / stable / fmt (push) Has been cancelled Details
2023-10-08 23:48:49 +02:00
Hans Mündelein d6406d1e09
Add users artists album support
CI checks / stable / fmt (push) Successful in 4s Details
CI release / stable / cross-${{ matrix.target }} (aarch64-unknown-linux-gnu) (push) Successful in 14m15s Details
CI release / stable / cross-${{ matrix.target }} (x86_64-unknown-linux-gnu) (push) Successful in 20m49s Details
CI release / stable / cross-${{ matrix.target }} (armv7-unknown-linux-gnueabihf) (push) Successful in 22m2s Details
2023-06-17 13:16:19 +02:00
Hans Mündelein 24e968ac08
Start playing when adding to an empty queue
CI checks / stable / fmt (push) Successful in 3s Details
CI release / stable / cross-${{ matrix.target }} (x86_64-unknown-linux-gnu) (push) Successful in 5m52s Details
CI release / stable / cross-${{ matrix.target }} (aarch64-unknown-linux-gnu) (push) Successful in 7m41s Details
CI release / stable / cross-${{ matrix.target }} (armv7-unknown-linux-gnueabihf) (push) Successful in 7m40s Details
2023-06-14 11:49:27 +02:00
Hans Mündelein a4303b9b70
Change tidal track duration to milliseconds
CI checks / stable / fmt (push) Successful in 3s Details
2023-06-14 11:31:15 +02:00
Hans Mündelein efc36e88f8
Don't error on failing broadcast channel send
CI checks / stable / fmt (push) Successful in 3s Details
2023-06-14 11:21:33 +02:00
Hans Mündelein 6db11131c0
Fix incorrect offset when turning off shuffle
CI checks / stable / fmt (push) Successful in 4s Details
2023-06-14 11:15:42 +02:00
Hans Mündelein f8a77ee6ed
Stop playing if last track is removed
CI checks / stable / fmt (push) Successful in 3s Details
2023-06-14 10:59:05 +02:00
Hans Mündelein 97ebb44ca5
Fix panic on next on empty queue
CI checks / stable / fmt (push) Successful in 3s Details
2023-06-14 10:51:29 +02:00
Hans Mündelein dfd2e0af92
Fix incorrect position update on queue and insert
CI checks / stable / fmt (push) Successful in 4s Details
2023-06-14 10:44:26 +02:00
Hans Mündelein 5c50544523
Unnwrap server code
CI checks / stable / fmt (push) Successful in 4s Details
2023-06-14 09:57:26 +02:00
chmanie 73cc79d776 Clean up a bit
CI checks / stable / fmt (push) Successful in 4s Details
2023-06-13 10:12:26 +02:00
chmanie a7c2fe391b Try tag filtering again
CI release / stable / cross-${{ matrix.target }} (x86_64-unknown-linux-gnu) (push) Successful in 4m52s Details
CI release / stable / cross-${{ matrix.target }} (aarch64-unknown-linux-gnu) (push) Successful in 6m7s Details
CI release / stable / cross-${{ matrix.target }} (armv7-unknown-linux-gnueabihf) (push) Successful in 7m5s Details
2023-06-13 10:00:16 +02:00
chmanie 2f89886e5d Show album and release date
stable / fmt Details
stable / cross-${{ matrix.target }} (aarch64-unknown-linux-gnu) Details
stable / cross-${{ matrix.target }} (armv7-unknown-linux-gnueabihf) Details
stable / cross-${{ matrix.target }} (x86_64-unknown-linux-gnu) Details
2023-06-13 00:59:54 +02:00
chmanie 4b34fa7233 Add key command to select currently playing track
stable / fmt Details
stable / cross-${{ matrix.target }} (aarch64-unknown-linux-gnu) Details
stable / cross-${{ matrix.target }} (armv7-unknown-linux-gnueabihf) Details
stable / cross-${{ matrix.target }} (x86_64-unknown-linux-gnu) Details
2023-06-13 00:17:56 +02:00
Hans Mündelein 902c0b903f
Replace some unwraps with more error logging
stable / fmt Details
stable / cross-${{ matrix.target }} (aarch64-unknown-linux-gnu) Details
stable / cross-${{ matrix.target }} (armv7-unknown-linux-gnueabihf) Details
stable / cross-${{ matrix.target }} (x86_64-unknown-linux-gnu) Details
2023-06-12 22:34:39 +02:00
Hans Mündelein 5d1a62c630
Add exclude current clearing for the server
stable / cross-${{ matrix.target }} (aarch64-unknown-linux-gnu) Details
stable / cross-${{ matrix.target }} (armv7-unknown-linux-gnueabihf) Details
stable / cross-${{ matrix.target }} (x86_64-unknown-linux-gnu) Details
stable / fmt Details
2023-06-12 22:18:34 +02:00
chmanie 02f47d682b Implement clear queue in tui
stable / fmt Details
stable / cross-${{ matrix.target }} (aarch64-unknown-linux-gnu) Details
stable / cross-${{ matrix.target }} (armv7-unknown-linux-gnueabihf) Details
stable / cross-${{ matrix.target }} (x86_64-unknown-linux-gnu) Details
2023-06-12 22:07:41 +02:00
19 changed files with 856 additions and 306 deletions

View File

@ -1,6 +0,0 @@
#!/bin/bash
echo "$(echo $GITHUB_REF| cut -d'/' -f2)"
if [ "$(echo $GITHUB_REF| cut -d'/' -f2)" != "tags" ]; then
exit 1;
fi

View File

@ -3,7 +3,7 @@ run-name: CI release
on: on:
push: push:
tags: tags:
- '*' - 'v*'
jobs: jobs:
release: release:
@ -15,7 +15,6 @@ jobs:
target: ["armv7-unknown-linux-gnueabihf", "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"] target: ["armv7-unknown-linux-gnueabihf", "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- run: .gitea/scripts/check-tag
- run: cargo fmt --check - run: cargo fmt --check
- uses: https://github.com/Swatinem/rust-cache@v2 - uses: https://github.com/Swatinem/rust-cache@v2
with: with:

Binary file not shown.

View File

@ -222,6 +222,7 @@ impl PlayerEngine {
SEEK_TO.store(time.as_secs(), Ordering::SeqCst); SEEK_TO.store(time.as_secs(), Ordering::SeqCst);
// FIXME: ideally we would like to return once the seeking is successful // FIXME: ideally we would like to return once the seeking is successful
// then return the current elapsed time // then return the current elapsed time
// Cond-var might be needed to sleep this (seeking takes time)
Ok(time) Ok(time)
} }

View File

@ -1,5 +1,6 @@
pub use ratatui::widgets::ListState; pub use ratatui::widgets::ListState;
// FIXME: Move marking stuff here, to be able to use it in queue as well
pub trait StatefulList { pub trait StatefulList {
fn get_size(&self) -> usize; fn get_size(&self) -> usize;
fn select(&mut self, idx: Option<usize>); fn select(&mut self, idx: Option<usize>);

View File

@ -65,6 +65,7 @@ pub enum MessageFromUi {
InsertTracks(Vec<String>, usize), InsertTracks(Vec<String>, usize),
RemoveTracks(Vec<usize>), RemoveTracks(Vec<usize>),
ReplaceQueue(Vec<String>), ReplaceQueue(Vec<String>),
ClearQueue(bool),
NextTrack, NextTrack,
PrevTrack, PrevTrack,
RestartTrack, RestartTrack,

View File

@ -45,10 +45,21 @@ impl NowPlaying {
} }
pub fn update_track(&mut self, active: Option<Track>) { pub fn update_track(&mut self, active: Option<Track>) {
if let Some(track) = &active { if let Some(track) = &active {
let body = if let Some(ref album) = track.album {
format!(
"{} by {}\n\n{} ({})",
track.title,
track.artist,
album.title,
// FIXME: get out year and format differently if it's missing
album.release_date()
)
} else {
format!("{} by {}", track.title, track.artist,)
};
Notification::new() Notification::new()
.summary("Crabidy playing") .summary("Now playing")
// FIXME: album .body(&body)
.body(&format!("{} by {}", track.title, track.artist))
.show() .show()
.unwrap(); .unwrap();
} }

View File

@ -41,6 +41,9 @@ impl Queue {
self.tx.send(MessageFromUi::SetCurrentTrack(pos)); self.tx.send(MessageFromUi::SetCurrentTrack(pos));
} }
} }
pub fn select_current(&mut self) {
self.select(Some(self.current_position));
}
pub fn remove_track(&mut self) { pub fn remove_track(&mut self) {
if let Some(pos) = self.selected() { if let Some(pos) = self.selected() {
// FIXME: mark multiple tracks on queue and remove them // FIXME: mark multiple tracks on queue and remove them

View File

@ -1,9 +1,4 @@
use crabidy_core::{ use crabidy_core::{clap, clap_serde_derive, serde::Serialize, ClapSerde};
clap::{self},
clap_serde_derive,
serde::Serialize,
ClapSerde,
};
#[derive(ClapSerde, Serialize, Debug)] #[derive(ClapSerde, Serialize, Debug)]
#[clap(author, version, about)] #[clap(author, version, about)]

View File

@ -123,6 +123,9 @@ async fn poll(
MessageFromUi::ToggleRepeat => { MessageFromUi::ToggleRepeat => {
rpc_client.toggle_repeat().await? rpc_client.toggle_repeat().await?
} }
MessageFromUi::ClearQueue(exclude_current) => {
rpc_client.clear_queue(exclude_current).await?
}
} }
} }
Some(resp) = rpc_client.update_stream.next() => { Some(resp) = rpc_client.update_stream.next() => {
@ -300,12 +303,21 @@ fn run_ui(tx: Sender<MessageFromUi>, rx: Receiver<MessageToUi>) {
(UiFocus::Queue, KeyModifiers::CONTROL, KeyCode::Char('u')) => { (UiFocus::Queue, KeyModifiers::CONTROL, KeyCode::Char('u')) => {
app.queue.up(); app.queue.up();
} }
(UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('o')) => {
app.queue.select_current();
}
(UiFocus::Queue, KeyModifiers::NONE, KeyCode::Enter) => { (UiFocus::Queue, KeyModifiers::NONE, KeyCode::Enter) => {
app.queue.play_selected(); app.queue.play_selected();
} }
(UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('d')) => { (UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('d')) => {
app.queue.remove_track(); app.queue.remove_track();
} }
(UiFocus::Queue, KeyModifiers::NONE, KeyCode::Char('c')) => {
tx.send(MessageFromUi::ClearQueue(true));
}
(UiFocus::Queue, KeyModifiers::SHIFT, KeyCode::Char('C')) => {
tx.send(MessageFromUi::ClearQueue(false));
}
_ => {} _ => {}
} }
} }

View File

@ -1,7 +1,7 @@
use crabidy_core::proto::crabidy::{ use crabidy_core::proto::crabidy::{
crabidy_service_client::CrabidyServiceClient, AppendRequest, ChangeVolumeRequest, crabidy_service_client::CrabidyServiceClient, AppendRequest, ChangeVolumeRequest,
GetLibraryNodeRequest, GetUpdateStreamRequest, GetUpdateStreamResponse, InitRequest, ClearQueueRequest, GetLibraryNodeRequest, GetUpdateStreamRequest, GetUpdateStreamResponse,
InitResponse, InsertRequest, LibraryNode, NextRequest, PrevRequest, QueueRequest, InitRequest, InitResponse, InsertRequest, LibraryNode, NextRequest, PrevRequest, QueueRequest,
RemoveRequest, ReplaceRequest, RestartTrackRequest, SetCurrentRequest, ToggleMuteRequest, RemoveRequest, ReplaceRequest, RestartTrackRequest, SetCurrentRequest, ToggleMuteRequest,
TogglePlayRequest, ToggleRepeatRequest, ToggleShuffleRequest, TogglePlayRequest, ToggleRepeatRequest, ToggleShuffleRequest,
}; };
@ -128,6 +128,12 @@ impl RpcClient {
Ok(()) Ok(())
} }
pub async fn clear_queue(&mut self, exclude_current: bool) -> Result<(), Box<dyn Error>> {
let clear_queue_request = Request::new(ClearQueueRequest { exclude_current });
self.client.clear_queue(clear_queue_request).await?;
Ok(())
}
pub async fn replace_queue(&mut self, uuids: Vec<String>) -> Result<(), Box<dyn Error>> { pub async fn replace_queue(&mut self, uuids: Vec<String>) -> Result<(), Box<dyn Error>> {
let replace_request = Request::new(ReplaceRequest { uuids }); let replace_request = Request::new(ReplaceRequest { uuids });
self.client.replace(replace_request).await?; self.client.replace(replace_request).await?;

View File

@ -14,11 +14,11 @@ service CrabidyService {
rpc Append(AppendRequest) returns (AppendResponse); rpc Append(AppendRequest) returns (AppendResponse);
rpc Remove(RemoveRequest) returns (RemoveResponse); rpc Remove(RemoveRequest) returns (RemoveResponse);
rpc Insert(InsertRequest) returns (InsertResponse); rpc Insert(InsertRequest) returns (InsertResponse);
rpc ClearQueue(ClearQueueRequest) returns (ClearQueueResponse);
rpc SetCurrent(SetCurrentRequest) returns (SetCurrentResponse); rpc SetCurrent(SetCurrentRequest) returns (SetCurrentResponse);
rpc ToggleShuffle(ToggleShuffleRequest) returns (ToggleShuffleResponse); rpc ToggleShuffle(ToggleShuffleRequest) returns (ToggleShuffleResponse);
rpc ToggleRepeat(ToggleRepeatRequest) returns (ToggleRepeatResponse); rpc ToggleRepeat(ToggleRepeatRequest) returns (ToggleRepeatResponse);
rpc GetUpdateStream(GetUpdateStreamRequest) returns (stream GetUpdateStreamResponse); rpc GetUpdateStream(GetUpdateStreamRequest) returns (stream GetUpdateStreamResponse);
rpc ClearQueue(ClearQueueRequest) returns (ClearQueueResponse);
rpc SaveQueue(SaveQueueRequest) returns (SaveQueueResponse); rpc SaveQueue(SaveQueueRequest) returns (SaveQueueResponse);
// Playback // Playback

View File

@ -1,9 +1,9 @@
use crabidy_core::proto::crabidy::{Queue, Track}; use crabidy_core::proto::crabidy::{Queue, Track};
use rand::{seq::SliceRandom, thread_rng}; use rand::{seq::SliceRandom, thread_rng};
use std::time::SystemTime; use std::time::SystemTime;
use tracing::debug; use tracing::{debug, error};
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct QueueManager { pub struct QueueManager {
created_at: SystemTime, created_at: SystemTime,
current_offset: usize, current_offset: usize,
@ -16,7 +16,11 @@ pub struct QueueManager {
impl From<QueueManager> for Queue { impl From<QueueManager> for Queue {
fn from(queue_manager: QueueManager) -> Self { fn from(queue_manager: QueueManager) -> Self {
Self { Self {
timestamp: queue_manager.created_at.elapsed().unwrap().as_secs(), timestamp: queue_manager
.created_at
.elapsed()
.expect("failed to get elapsed time")
.as_secs(),
current_position: queue_manager.current_position() as u32, current_position: queue_manager.current_position() as u32,
tracks: queue_manager.tracks, tracks: queue_manager.tracks,
} }
@ -42,6 +46,10 @@ impl QueueManager {
} }
} }
pub fn is_last_track(&self) -> bool {
self.current_position() == self.tracks.len() - 1
}
pub fn shuffle_on(&mut self) { pub fn shuffle_on(&mut self) {
self.shuffle = true; self.shuffle = true;
self.shuffle_before(self.current_offset); self.shuffle_before(self.current_offset);
@ -50,6 +58,8 @@ impl QueueManager {
pub fn shuffle_off(&mut self) { pub fn shuffle_off(&mut self) {
self.shuffle = false; self.shuffle = false;
let pos = self.current_position();
self.current_offset = pos;
self.play_order = (0..self.tracks.len()).collect(); self.play_order = (0..self.tracks.len()).collect();
} }
@ -75,6 +85,9 @@ impl QueueManager {
pub fn next_track(&mut self) -> Option<Track> { pub fn next_track(&mut self) -> Option<Track> {
let len = self.tracks.len(); let len = self.tracks.len();
if len == 0 {
return None;
};
if self.current_offset < len - 1 { if self.current_offset < len - 1 {
self.current_offset += 1; self.current_offset += 1;
let current_pos = self.current_position(); let current_pos = self.current_position();
@ -111,11 +124,15 @@ impl QueueManager {
if self.shuffle { if self.shuffle {
self.shuffle_all(); self.shuffle_all();
} }
let current_offset = self let Some(current_offset) = self
.play_order .play_order
.iter() .iter()
.position(|&i| i == current_position as usize) .position(|&i| i == current_position as usize)
.unwrap(); else {
error!("invalid current position");
error!("queue: {:#?}", self);
return false
};
if self.shuffle { if self.shuffle {
self.play_order.swap(0, current_offset); self.play_order.swap(0, current_offset);
self.current_offset = 0; self.current_offset = 0;
@ -142,36 +159,19 @@ impl QueueManager {
} }
} }
pub fn append_tracks(&mut self, tracks: &[Track]) { pub fn append_tracks(&mut self, tracks: &[Track]) -> Option<Track> {
let len = self.tracks.len(); let len = self.tracks.len();
let is_empty = len == 0;
let order_additions: Vec<usize> = (len..len + tracks.len()).collect(); let order_additions: Vec<usize> = (len..len + tracks.len()).collect();
self.play_order.extend(order_additions); self.play_order.extend(order_additions);
self.tracks.extend(tracks.iter().cloned()); self.tracks.extend(tracks.iter().cloned());
if self.shuffle { if self.shuffle {
self.shuffle_behind(self.current_offset); self.shuffle_behind(self.current_offset);
} }
} if is_empty {
self.current_track()
pub fn queue_tracks(&mut self, tracks: &[Track]) { } else {
let len = self.tracks.len(); None
if len == 0 {
self.replace_with_tracks(tracks);
return;
}
let pos = self.current_position();
let order_additions: Vec<usize> = (len..len + tracks.len()).collect();
self.play_order.extend(order_additions);
let tail: Vec<Track> = self
.tracks
.splice((self.current_position() + 1).., tracks.to_vec())
.collect();
self.tracks.extend(tail);
self.play_order
.iter_mut()
.filter(|i| (pos as usize) < **i)
.for_each(|i| *i += len);
if self.shuffle {
self.shuffle_behind(self.current_offset);
} }
} }
@ -184,11 +184,15 @@ impl QueueManager {
if *pos == self.current_position() as u32 { if *pos == self.current_position() as u32 {
play_next = true; play_next = true;
} }
let offset = self let Some(offset) = self
.play_order .play_order
.iter() .iter()
.position(|&i| i == *pos as usize) .position(|&i| i == *pos as usize)
.unwrap(); else {
error!("invalid current position");
error!("queue: {:#?}", self);
return None
};
if offset < self.current_offset { if offset < self.current_offset {
self.current_offset -= 1; self.current_offset -= 1;
} }
@ -206,8 +210,11 @@ impl QueueManager {
} }
} }
pub fn insert_tracks(&mut self, position: u32, tracks: &[Track]) { pub fn insert_tracks(&mut self, position: u32, tracks: &[Track]) -> Option<Track> {
let len = self.tracks.len(); let len = self.tracks.len();
if len == 0 {
return self.replace_with_tracks(tracks);
}
let order_additions: Vec<usize> = (len..len + tracks.len()).collect(); let order_additions: Vec<usize> = (len..len + tracks.len()).collect();
self.play_order.extend(order_additions); self.play_order.extend(order_additions);
let tail: Vec<Track> = self let tail: Vec<Track> = self
@ -215,9 +222,57 @@ impl QueueManager {
.splice((position as usize + 1).., tracks.to_vec()) .splice((position as usize + 1).., tracks.to_vec())
.collect(); .collect();
self.tracks.extend(tail); self.tracks.extend(tail);
let mut changed: Vec<usize> = Vec::new();
// in shuffle mode, it might be that we played already postions which are behind
// the insertion point and which postions are shifted by the lenght of the inserted
// track
for i in self
.play_order
.iter_mut()
.take(self.current_offset)
.filter(|i| (position as usize) < **i)
{
*i += len;
changed.push(*i);
}
if !self.shuffle {
// if we don't shuffle, there should be no positions alredy played behind the
// current track
assert!(changed.is_empty());
}
// the newly inserted indices need to replaced with the ones that we already handled
self.play_order
.iter_mut()
.skip(self.current_offset)
.for_each(|i| {
if changed.contains(i) {
*i -= len;
}
});
if self.shuffle { if self.shuffle {
self.shuffle_behind(self.current_offset); self.shuffle_behind(self.current_offset);
} }
None
}
pub fn queue_tracks(&mut self, tracks: &[Track]) -> Option<Track> {
let pos = self.current_position();
self.insert_tracks(pos as u32, tracks)
}
pub fn clear(&mut self, exclude_current: bool) -> bool {
let current_track = self.current_track();
self.current_offset = 0;
self.tracks.clear();
if exclude_current {
if let Some(track) = current_track {
self.tracks.push(track);
}
}
!exclude_current
} }
} }

View File

@ -3,7 +3,7 @@ use crabidy_core::proto::crabidy::{
crabidy_service_server::CrabidyServiceServer, InitResponse, LibraryNode, PlayState, Track, crabidy_service_server::CrabidyServiceServer, InitResponse, LibraryNode, PlayState, Track,
}; };
use crabidy_core::{ProviderClient, ProviderError}; use crabidy_core::{ProviderClient, ProviderError};
use tracing::{debug_span, info, instrument, warn, Span}; use tracing::{debug_span, error, info, instrument, level_filters, warn, Span};
use tracing_subscriber::{filter::Targets, prelude::*}; use tracing_subscriber::{filter::Targets, prelude::*};
mod playback; mod playback;
@ -22,8 +22,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
} }
let (non_blocking, _guard) = tracing_appender::non_blocking(std::io::stderr()); let (non_blocking, _guard) = tracing_appender::non_blocking(std::io::stderr());
let targets_filter = let targets_filter = Targets::new()
Targets::new().with_target("crabidy_server", tracing::level_filters::LevelFilter::DEBUG); .with_target("crabidy_server", tracing::level_filters::LevelFilter::DEBUG)
.with_target("tidaldy", level_filters::LevelFilter::DEBUG);
let subscriber = tracing_subscriber::fmt::layer() let subscriber = tracing_subscriber::fmt::layer()
.with_writer(non_blocking) .with_writer(non_blocking)
.with_file(true) .with_file(true)
@ -39,7 +40,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
info!("audio player started initialized"); info!("audio player started initialized");
let (update_tx, _) = tokio::sync::broadcast::channel(2048); let (update_tx, _) = tokio::sync::broadcast::channel(2048);
let orchestrator = ProviderOrchestrator::init("").await.unwrap(); let orchestrator = ProviderOrchestrator::init("")
.await
.expect("failed to init orchestrator");
let playback = Playback::new(update_tx.clone(), orchestrator.provider_tx.clone()); let playback = Playback::new(update_tx.clone(), orchestrator.provider_tx.clone());
@ -76,44 +79,51 @@ fn poll_play_bus(rx: flume::Receiver<PlayerMessage>, tx: flume::Sender<PlaybackM
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
match msg { match msg {
PlayerMessage::EndOfStream => { PlayerMessage::EndOfStream => {
tx.send(PlaybackMessage::Next { span }).unwrap(); if let Err(err) = tx.send(PlaybackMessage::Next { span }) {
error!("failed to send next message: {}", err);
}
} }
PlayerMessage::Stopped => { PlayerMessage::Stopped => {
tx.send(PlaybackMessage::StateChanged { if let Err(err) = tx.send(PlaybackMessage::StateChanged {
state: PlayState::Stopped, state: PlayState::Stopped,
span, span,
}) }) {
.unwrap(); error!("failed to send stopped message: {}", err);
}
} }
PlayerMessage::Paused => { PlayerMessage::Paused => {
tx.send(PlaybackMessage::StateChanged { if let Err(err) = tx.send(PlaybackMessage::StateChanged {
state: PlayState::Paused, state: PlayState::Paused,
span, span,
}) }) {
.unwrap(); error!("failed to send paused message: {}", err);
}
} }
PlayerMessage::Playing => { PlayerMessage::Playing => {
tx.send(PlaybackMessage::StateChanged { if let Err(err) = tx.send(PlaybackMessage::StateChanged {
state: PlayState::Playing, state: PlayState::Playing,
span, span,
}) }) {
.unwrap(); error!("failed to send playing message: {}", err);
}
} }
PlayerMessage::Elapsed { duration, elapsed } => { PlayerMessage::Elapsed { duration, elapsed } => {
tx.send(PlaybackMessage::PostitionChanged { if let Err(err) = tx.send(PlaybackMessage::PostitionChanged {
duration: duration.as_millis() as u32, duration: duration.as_millis() as u32,
position: elapsed.as_millis() as u32, position: elapsed.as_millis() as u32,
span, span,
}) }) {
.unwrap(); error!("failed to send elapsed message: {}", err);
}
} }
PlayerMessage::Duration { duration } => { PlayerMessage::Duration { duration } => {
tx.send(PlaybackMessage::PostitionChanged { if let Err(err) = tx.send(PlaybackMessage::PostitionChanged {
duration: duration.as_millis() as u32, duration: duration.as_millis() as u32,
position: 0, position: 0,
span, span,
}) }) {
.unwrap(); error!("failed to send duration message: {}", err);
}
} }
} }
} }
@ -170,6 +180,10 @@ pub enum PlaybackMessage {
uuids: Vec<String>, uuids: Vec<String>,
span: Span, span: Span,
}, },
Clear {
exclude_current: bool,
span: Span,
},
SetCurrent { SetCurrent {
position: u32, position: u32,
span: Span, span: Span,

View File

@ -51,7 +51,10 @@ impl Playback {
let repeat; let repeat;
let shuffle; let shuffle;
let response = { let response = {
let queue = self.queue.lock().unwrap(); let Ok(queue) = self.queue.lock() else {
error!("failed to get queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
repeat = queue.repeat; repeat = queue.repeat;
shuffle = queue.shuffle; shuffle = queue.shuffle;
@ -69,13 +72,11 @@ impl Playback {
trace!("position {:?}", position); trace!("position {:?}", position);
let play_state = { let play_state = {
debug!("getting play state lock"); debug!("getting play state lock");
match *self.state.lock().unwrap() { let Ok(play_state) = self.state.lock() else {
PlayState::Playing => PlayState::Playing, error!("failed to get play state lock");
PlayState::Paused => PlayState::Paused, continue;
PlayState::Stopped => PlayState::Stopped, };
PlayState::Loading => PlayState::Loading, *play_state
_ => PlayState::Unspecified,
}
}; };
trace!("play_state {:?}", play_state); trace!("play_state {:?}", play_state);
debug!("released play state lock"); debug!("released play state lock");
@ -90,7 +91,9 @@ impl Playback {
} }
}; };
trace!("response {:?}", response); trace!("response {:?}", response);
result_tx.send(response).unwrap(); if let Err(err) = result_tx.send(response) {
error!("failed to send response: {:#?}", err);
}
} }
PlaybackMessage::Replace { uuids, span } => { PlaybackMessage::Replace { uuids, span } => {
let _e = span.enter(); let _e = span.enter();
@ -108,12 +111,17 @@ impl Playback {
} }
trace!("got tracks {:?}", all_tracks); trace!("got tracks {:?}", all_tracks);
let current = { let current = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
queue.replace_with_tracks(&all_tracks); queue.replace_with_tracks(&all_tracks);
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let update = StreamUpdate::Queue(queue.clone().into()); let update = StreamUpdate::Queue(queue.clone().into());
queue_update_tx.send(update).unwrap(); if let Err(err) = queue_update_tx.send(update) {
trace!("{:?}", err)
};
queue.current_track() queue.current_track()
}; };
debug!("got current {:?}", current); debug!("got current {:?}", current);
@ -135,17 +143,22 @@ impl Playback {
} }
} }
trace!("got tracks {:?}", all_tracks); trace!("got tracks {:?}", all_tracks);
{ let track = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
queue.queue_tracks(&all_tracks); let track = queue.queue_tracks(&all_tracks);
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let update = StreamUpdate::Queue(queue.clone().into()); let update = StreamUpdate::Queue(queue.clone().into());
if let Err(err) = queue_update_tx.send(update) { if let Err(err) = queue_update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} track
};
debug!("que lock released"); debug!("que lock released");
self.play(track).in_current_span().await;
} }
PlaybackMessage::Append { uuids, span } => { PlaybackMessage::Append { uuids, span } => {
@ -163,35 +176,59 @@ impl Playback {
} }
} }
trace!("got tracks {:?}", all_tracks); trace!("got tracks {:?}", all_tracks);
{ let track = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
queue.append_tracks(&all_tracks); let track = queue.append_tracks(&all_tracks);
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let update = StreamUpdate::Queue(queue.clone().into()); let update = StreamUpdate::Queue(queue.clone().into());
if let Err(err) = queue_update_tx.send(update) { if let Err(err) = queue_update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} track
};
debug!("queue lock released"); debug!("queue lock released");
self.play(track).in_current_span().await;
} }
PlaybackMessage::Remove { positions, span } => { PlaybackMessage::Remove { positions, span } => {
let _e = span.enter(); let _e = span.enter();
let is_last;
debug!("removing"); debug!("removing");
let track = { let track = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
is_last = queue.is_last_track();
let track = queue.remove_tracks(&positions); let track = queue.remove_tracks(&positions);
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let update = StreamUpdate::Queue(queue.clone().into()); let update = StreamUpdate::Queue(queue.clone().into());
queue_update_tx.send(update).unwrap(); if let Err(err) = queue_update_tx.send(update) {
trace!("{:?}", err)
};
track track
}; };
debug!("queue lock released"); debug!("queue lock released");
let state = *self.state.lock().unwrap(); let state = {
let Ok(state) = self.state.lock() else {
error!("failed to get play state lock");
continue;
};
*state
};
if state == PlayState::Playing { if state == PlayState::Playing {
self.play(track).in_current_span().await; if is_last {
if let Err(err) = self.player.stop().in_current_span().await {
error!("{:?}", err)
}
} else {
self.play(track).in_current_span().await;
}
} }
} }
@ -214,15 +251,50 @@ impl Playback {
} }
} }
trace!("got tracks {:?}", all_tracks); trace!("got tracks {:?}", all_tracks);
{ let track = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
queue.insert_tracks(position, &all_tracks); let track = queue.insert_tracks(position, &all_tracks);
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let update = StreamUpdate::Queue(queue.clone().into()); let update = StreamUpdate::Queue(queue.clone().into());
queue_update_tx.send(update).unwrap(); if let Err(err) = queue_update_tx.send(update) {
} trace!("{:?}", err)
};
track
};
debug!("queue lock released"); debug!("queue lock released");
self.play(track).in_current_span().await;
}
PlaybackMessage::Clear {
exclude_current,
span,
} => {
let _e = span.enter();
debug!("clearing");
let should_stop = {
let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock");
let should_stop = queue.clear(exclude_current);
let queue_update_tx = self.update_tx.clone();
let update = StreamUpdate::Queue(queue.clone().into());
if let Err(err) = queue_update_tx.send(update) {
trace!("{:?}", err)
};
should_stop
};
debug!("queue lock released");
if should_stop {
if let Err(err) = self.player.stop().in_current_span().await {
error!("{:?}", err)
}
}
} }
PlaybackMessage::SetCurrent { PlaybackMessage::SetCurrent {
@ -232,7 +304,10 @@ impl Playback {
let _e = span.enter(); let _e = span.enter();
debug!("setting current"); debug!("setting current");
let track = { let track = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
queue.set_current_position(queue_position); queue.set_current_position(queue_position);
queue.current_track() queue.current_track()
@ -247,7 +322,10 @@ impl Playback {
let shuffle; let shuffle;
let repeat; let repeat;
{ {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
repeat = queue.repeat; repeat = queue.repeat;
if queue.shuffle { if queue.shuffle {
@ -261,7 +339,7 @@ impl Playback {
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let update = StreamUpdate::Mods(QueueModifiers { shuffle, repeat }); let update = StreamUpdate::Mods(QueueModifiers { shuffle, repeat });
if let Err(err) = queue_update_tx.send(update) { if let Err(err) = queue_update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} }
@ -271,7 +349,10 @@ impl Playback {
let shuffle; let shuffle;
let repeat; let repeat;
{ {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
shuffle = queue.shuffle; shuffle = queue.shuffle;
if queue.repeat { if queue.repeat {
@ -285,7 +366,7 @@ impl Playback {
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let update = StreamUpdate::Mods(QueueModifiers { shuffle, repeat }); let update = StreamUpdate::Mods(QueueModifiers { shuffle, repeat });
if let Err(err) = queue_update_tx.send(update) { if let Err(err) = queue_update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} }
@ -293,7 +374,13 @@ impl Playback {
let _e = span.enter(); let _e = span.enter();
debug!("toggling play"); debug!("toggling play");
{ {
let state = *self.state.lock().unwrap(); let state = {
let Ok(state) = self.state.lock() else {
debug!("got state lock");
continue;
};
*state
};
debug!("got state lock"); debug!("got state lock");
if state == PlayState::Playing { if state == PlayState::Playing {
if let Err(err) = self.player.pause().await { if let Err(err) = self.player.pause().await {
@ -337,7 +424,10 @@ impl Playback {
let _e = span.enter(); let _e = span.enter();
debug!("nexting"); debug!("nexting");
let track = { let track = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
queue.next_track() queue.next_track()
}; };
@ -350,7 +440,10 @@ impl Playback {
let _e = span.enter(); let _e = span.enter();
debug!("preving"); debug!("preving");
let track = { let track = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
debug!("got queue lock"); debug!("got queue lock");
queue.prev_track() queue.prev_track()
}; };
@ -363,14 +456,18 @@ impl Playback {
debug!("state changed"); debug!("state changed");
let play_state = { let play_state = {
*self.state.lock().unwrap() = state; let Ok(mut state_lock) = self.state.lock() else {
debug!("got state lock");
continue;
};
*state_lock = state;
state state
}; };
debug!("released state lock and got play state {:?}", play_state); debug!("released state lock and got play state {:?}", play_state);
let active_track_tx = self.update_tx.clone(); let active_track_tx = self.update_tx.clone();
let update = StreamUpdate::PlayState(play_state as i32); let update = StreamUpdate::PlayState(play_state as i32);
if let Err(err) = active_track_tx.send(update) { if let Err(err) = active_track_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
}; };
} }
@ -388,7 +485,7 @@ impl Playback {
let update_tx = self.update_tx.clone(); let update_tx = self.update_tx.clone();
let update = StreamUpdate::Volume(volume); let update = StreamUpdate::Volume(volume);
if let Err(err) = update_tx.send(update) { if let Err(err) = update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} }
@ -398,7 +495,7 @@ impl Playback {
let update_tx = self.update_tx.clone(); let update_tx = self.update_tx.clone();
let update = StreamUpdate::Mute(muted); let update = StreamUpdate::Mute(muted);
if let Err(err) = update_tx.send(update) { if let Err(err) = update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} }
@ -412,7 +509,7 @@ impl Playback {
let update_tx = self.update_tx.clone(); let update_tx = self.update_tx.clone();
let update = StreamUpdate::Position(TrackPosition { duration, position }); let update = StreamUpdate::Position(TrackPosition { duration, position });
if let Err(err) = update_tx.send(update) { if let Err(err) = update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} }
} }
@ -422,6 +519,7 @@ impl Playback {
#[instrument(skip(self))] #[instrument(skip(self))]
async fn flatten_node(&self, uuid: &str) -> Vec<Track> { async fn flatten_node(&self, uuid: &str) -> Vec<Track> {
debug!("flattening node");
let tx = self.provider_tx.clone(); let tx = self.provider_tx.clone();
let (result_tx, result_rx) = flume::bounded(1); let (result_tx, result_rx) = flume::bounded(1);
let span = debug_span!("prov-chan"); let span = debug_span!("prov-chan");
@ -464,6 +562,7 @@ impl Playback {
#[instrument(skip(self))] #[instrument(skip(self))]
async fn get_urls_for_track(&self, uuid: &str) -> Result<Vec<String>, ProviderError> { async fn get_urls_for_track(&self, uuid: &str) -> Result<Vec<String>, ProviderError> {
debug!("getting urls for track");
let tx = self.provider_tx.clone(); let tx = self.provider_tx.clone();
let (result_tx, result_rx) = flume::bounded(1); let (result_tx, result_rx) = flume::bounded(1);
let span = tracing::trace_span!("prov-chan"); let span = tracing::trace_span!("prov-chan");
@ -484,6 +583,7 @@ impl Playback {
#[instrument(skip(self))] #[instrument(skip(self))]
async fn play_or_stop(&self, track: Option<Track>) { async fn play_or_stop(&self, track: Option<Track>) {
debug!("play or stop");
if let Some(track) = track { if let Some(track) = track {
let mut uuid = track.uuid.clone(); let mut uuid = track.uuid.clone();
let urls = loop { let urls = loop {
@ -492,7 +592,10 @@ impl Playback {
Err(err) => { Err(err) => {
warn!("no urls found for track {:?}: {}", track.uuid, err); warn!("no urls found for track {:?}: {}", track.uuid, err);
uuid = { uuid = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("got queue lock");
continue;
};
if let Some(track) = queue.next_track() { if let Some(track) = queue.next_track() {
track.uuid.clone() track.uuid.clone()
} else { } else {
@ -503,7 +606,10 @@ impl Playback {
} }
}; };
{ {
let queue = self.queue.lock().unwrap(); let Ok(queue) = self.queue.lock() else {
error!("poisend queue lock");
return
};
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let track = queue.current_track(); let track = queue.current_track();
let update = StreamUpdate::QueueTrack(QueueTrack { let update = StreamUpdate::QueueTrack(QueueTrack {
@ -511,7 +617,7 @@ impl Playback {
track, track,
}); });
if let Err(err) = queue_update_tx.send(update) { if let Err(err) = queue_update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} }
if let Err(err) = self.player.play(&urls[0]).await { if let Err(err) = self.player.play(&urls[0]).await {
@ -524,6 +630,7 @@ impl Playback {
#[instrument(skip(self))] #[instrument(skip(self))]
async fn play(&self, track: Option<Track>) { async fn play(&self, track: Option<Track>) {
debug!("play");
if let Some(track) = track { if let Some(track) = track {
let mut uuid = track.uuid.clone(); let mut uuid = track.uuid.clone();
let urls = loop { let urls = loop {
@ -532,7 +639,10 @@ impl Playback {
Err(err) => { Err(err) => {
warn!("no urls found for track {:?}: {}", track.uuid, err); warn!("no urls found for track {:?}: {}", track.uuid, err);
uuid = { uuid = {
let mut queue = self.queue.lock().unwrap(); let Ok(mut queue) = self.queue.lock() else {
debug!("poisend queue lock");
return;
};
if let Some(track) = queue.next_track() { if let Some(track) = queue.next_track() {
track.uuid.clone() track.uuid.clone()
} else { } else {
@ -543,7 +653,10 @@ impl Playback {
} }
}; };
{ {
let queue = self.queue.lock().unwrap(); let Ok(queue) = self.queue.lock() else {
error!("poisend queue lock");
return
};
let queue_update_tx = self.update_tx.clone(); let queue_update_tx = self.update_tx.clone();
let track = queue.current_track(); let track = queue.current_track();
let update = StreamUpdate::QueueTrack(QueueTrack { let update = StreamUpdate::QueueTrack(QueueTrack {
@ -551,7 +664,7 @@ impl Playback {
track, track,
}); });
if let Err(err) = queue_update_tx.send(update) { if let Err(err) = queue_update_tx.send(update) {
error!("{:?}", err) trace!("{:?}", err)
} }
} }
if let Err(err) = self.player.play(&urls[0]).await { if let Err(err) = self.player.play(&urls[0]).await {

View File

@ -28,11 +28,9 @@ impl ProviderOrchestrator {
} => { } => {
let _e = span.enter(); let _e = span.enter();
let result = self.get_lib_node(&uuid).in_current_span().await; let result = self.get_lib_node(&uuid).in_current_span().await;
result_tx if let Err(err) = result_tx.send_async(result).in_current_span().await {
.send_async(result) error!("failed to send result: {}", err);
.in_current_span() }
.await
.unwrap();
} }
ProviderMessage::GetTrack { ProviderMessage::GetTrack {
uuid, uuid,
@ -41,11 +39,9 @@ impl ProviderOrchestrator {
} => { } => {
let _e = span.enter(); let _e = span.enter();
let result = self.get_metadata_for_track(&uuid).in_current_span().await; let result = self.get_metadata_for_track(&uuid).in_current_span().await;
result_tx if let Err(err) = result_tx.send_async(result).in_current_span().await {
.send_async(result) error!("failed to send result: {}", err);
.in_current_span() }
.await
.unwrap();
} }
ProviderMessage::GetTrackUrls { ProviderMessage::GetTrackUrls {
uuid, uuid,
@ -54,11 +50,9 @@ impl ProviderOrchestrator {
} => { } => {
let _e = span.enter(); let _e = span.enter();
let result = self.get_urls_for_track(&uuid).in_current_span().await; let result = self.get_urls_for_track(&uuid).in_current_span().await;
result_tx if let Err(err) = result_tx.send_async(result).in_current_span().await {
.send_async(result) error!("failed to send result: {}", err);
.in_current_span() }
.await
.unwrap();
} }
ProviderMessage::FlattenNode { ProviderMessage::FlattenNode {
uuid, uuid,
@ -67,11 +61,9 @@ impl ProviderOrchestrator {
} => { } => {
let _e = span.enter(); let _e = span.enter();
let result = self.flatten_node(&uuid).in_current_span().await; let result = self.flatten_node(&uuid).in_current_span().await;
result_tx if let Err(err) = result_tx.send_async(result).in_current_span().await {
.send_async(result) error!("failed to send result: {}", err);
.in_current_span() }
.await
.unwrap();
} }
} }
} }
@ -118,7 +110,7 @@ impl ProviderClient for ProviderOrchestrator {
tidaldy::Client::init(&raw_toml_settings) tidaldy::Client::init(&raw_toml_settings)
.in_current_span() .in_current_span()
.await .await
.unwrap(), .expect("Failed to init Tidal clienta"),
); );
let new_toml_config = tidal_client.settings(); let new_toml_config = tidal_client.settings();
if let Err(err) = tokio::fs::write(&config_file, new_toml_config) if let Err(err) = tokio::fs::write(&config_file, new_toml_config)
@ -140,6 +132,7 @@ impl ProviderClient for ProviderOrchestrator {
} }
#[instrument(skip(self))] #[instrument(skip(self))]
async fn get_urls_for_track(&self, track_uuid: &str) -> Result<Vec<String>, ProviderError> { async fn get_urls_for_track(&self, track_uuid: &str) -> Result<Vec<String>, ProviderError> {
debug!("get_urls_for_track");
self.tidal_client self.tidal_client
.get_urls_for_track(track_uuid) .get_urls_for_track(track_uuid)
.in_current_span() .in_current_span()
@ -155,6 +148,7 @@ impl ProviderClient for ProviderOrchestrator {
} }
#[instrument(skip(self))] #[instrument(skip(self))]
fn get_lib_root(&self) -> LibraryNode { fn get_lib_root(&self) -> LibraryNode {
debug!("get_lib_root in provider manager");
let mut root_node = LibraryNode::new(); let mut root_node = LibraryNode::new();
let child = LibraryNodeChild::new("node:tidal".to_owned(), "tidal".to_owned(), false); let child = LibraryNodeChild::new("node:tidal".to_owned(), "tidal".to_owned(), false);
root_node.children.push(child); root_node.children.push(child);
@ -162,13 +156,16 @@ impl ProviderClient for ProviderOrchestrator {
} }
#[instrument(skip(self))] #[instrument(skip(self))]
async fn get_lib_node(&self, uuid: &str) -> Result<LibraryNode, ProviderError> { async fn get_lib_node(&self, uuid: &str) -> Result<LibraryNode, ProviderError> {
debug!("get_lib_node"); debug!("get_lib_node in provider manager");
if uuid == "node:/" { if uuid == "node:/" {
debug!("get global root");
return Ok(self.get_lib_root()); return Ok(self.get_lib_root());
} }
if uuid == "node:tidal" { if uuid == "node:tidal" {
debug!("get tidal root");
return Ok(self.tidal_client.get_lib_root()); return Ok(self.tidal_client.get_lib_root());
} }
debug!("tidal node");
self.tidal_client.get_lib_node(uuid).in_current_span().await self.tidal_client.get_lib_node(uuid).in_current_span().await
} }
} }

View File

@ -91,10 +91,16 @@ impl CrabidyService for RpcService {
.recv_async() .recv_async()
.in_current_span() .in_current_span()
.await .await
.map_err(|_| Status::internal("Failed to receive response from provider channel"))?; .map_err(|e| {
error!("{:?}", e);
Status::internal("Failed to receive response from provider channel")
})?;
match result { match result {
Ok(node) => Ok(Response::new(GetLibraryNodeResponse { node: Some(node) })), Ok(node) => Ok(Response::new(GetLibraryNodeResponse { node: Some(node) })),
Err(err) => Err(Status::internal(err.to_string())), Err(err) => {
error!("{:?}", err);
Err(Status::internal(err.to_string()))
}
} }
} }
@ -201,6 +207,28 @@ impl CrabidyService for RpcService {
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
#[instrument(skip(self, request), fields(exclude_current))]
async fn clear_queue(
&self,
request: tonic::Request<ClearQueueRequest>,
) -> std::result::Result<tonic::Response<ClearQueueResponse>, tonic::Status> {
let exclude_current = request.into_inner().exclude_current;
Span::current().record("exclude_current", exclude_current);
debug!("Received clear_queue request");
let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan");
playback_tx
.send_async(PlaybackMessage::Clear {
exclude_current,
span,
})
.in_current_span()
.await
.map_err(|_| Status::internal("Failed to send request via channel"))?;
let reply = ClearQueueResponse {};
Ok(Response::new(reply))
}
#[instrument(skip(self, request), fields(position))] #[instrument(skip(self, request), fields(position))]
async fn set_current( async fn set_current(
&self, &self,
@ -228,11 +256,13 @@ impl CrabidyService for RpcService {
debug!("Received toggle_shuffle request"); debug!("Received toggle_shuffle request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::ToggleShuffle { span }) .send_async(PlaybackMessage::ToggleShuffle { span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = ToggleShuffleResponse {}; let reply = ToggleShuffleResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
@ -245,11 +275,13 @@ impl CrabidyService for RpcService {
debug!("Received toggle_repeat request"); debug!("Received toggle_repeat request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::ToggleRepeat { span }) .send_async(PlaybackMessage::ToggleRepeat { span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = ToggleRepeatResponse {}; let reply = ToggleRepeatResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
@ -279,24 +311,6 @@ impl CrabidyService for RpcService {
Ok(Response::new(Box::pin(output_stream))) Ok(Response::new(Box::pin(output_stream)))
} }
#[instrument(skip(self, _request))]
async fn clear_queue(
&self,
_request: tonic::Request<ClearQueueRequest>,
) -> std::result::Result<tonic::Response<ClearQueueResponse>, tonic::Status> {
debug!("Received clear_queue request");
let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan");
let uuids = Vec::new();
playback_tx
.send_async(PlaybackMessage::Replace { uuids, span })
.in_current_span()
.await
.map_err(|_| Status::internal("Failed to send request via channel"))?;
let reply = ClearQueueResponse {};
Ok(Response::new(reply))
}
#[instrument(skip(self, _request))] #[instrument(skip(self, _request))]
async fn save_queue( async fn save_queue(
&self, &self,
@ -316,11 +330,13 @@ impl CrabidyService for RpcService {
debug!("Received toggle_play request"); debug!("Received toggle_play request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::TogglePlay { span }) .send_async(PlaybackMessage::TogglePlay { span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = TogglePlayResponse {}; let reply = TogglePlayResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
@ -333,11 +349,13 @@ impl CrabidyService for RpcService {
debug!("Received stop request"); debug!("Received stop request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::Stop { span }) .send_async(PlaybackMessage::Stop { span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = StopResponse {}; let reply = StopResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
@ -352,11 +370,13 @@ impl CrabidyService for RpcService {
debug!("Received change_volume request"); debug!("Received change_volume request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::ChangeVolume { delta, span }) .send_async(PlaybackMessage::ChangeVolume { delta, span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = ChangeVolumeResponse {}; let reply = ChangeVolumeResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
@ -369,11 +389,13 @@ impl CrabidyService for RpcService {
debug!("Received toggle_mute request"); debug!("Received toggle_mute request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::ToggleMute { span }) .send_async(PlaybackMessage::ToggleMute { span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = ToggleMuteResponse {}; let reply = ToggleMuteResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
@ -386,11 +408,13 @@ impl CrabidyService for RpcService {
debug!("Received next request"); debug!("Received next request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::Next { span }) .send_async(PlaybackMessage::Next { span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = NextResponse {}; let reply = NextResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
@ -403,11 +427,13 @@ impl CrabidyService for RpcService {
debug!("Received prev request"); debug!("Received prev request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::Prev { span }) .send_async(PlaybackMessage::Prev { span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = PrevResponse {}; let reply = PrevResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }
@ -420,11 +446,13 @@ impl CrabidyService for RpcService {
debug!("Received restart_track request"); debug!("Received restart_track request");
let playback_tx = self.playback_tx.clone(); let playback_tx = self.playback_tx.clone();
let span = debug_span!("play-chan"); let span = debug_span!("play-chan");
playback_tx if let Err(err) = playback_tx
.send_async(PlaybackMessage::RestartTrack { span }) .send_async(PlaybackMessage::RestartTrack { span })
.in_current_span() .in_current_span()
.await .await
.unwrap(); {
error!("Failed to send request via channel: {}", err);
}
let reply = RestartTrackResponse {}; let reply = RestartTrackResponse {};
Ok(Response::new(reply)) Ok(Response::new(reply))
} }

View File

@ -1,9 +1,11 @@
use std::fmt::format;
/// Lots of stuff and especially the auth handling is shamelessly copied from /// Lots of stuff and especially the auth handling is shamelessly copied from
/// https://github.com/MinisculeGirraffe/tdl /// https://github.com/MinisculeGirraffe/tdl
use reqwest::Client as HttpClient; use reqwest::Client as HttpClient;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use tokio::time::{sleep, Duration, Instant}; use tokio::time::{sleep, Duration, Instant};
use tracing::{debug, instrument}; use tracing::{debug, error, info, instrument};
pub mod config; pub mod config;
pub mod models; pub mod models;
use async_trait::async_trait; use async_trait::async_trait;
@ -75,13 +77,20 @@ impl crabidy_core::ProviderClient for Client {
#[instrument(skip(self))] #[instrument(skip(self))]
fn get_lib_root(&self) -> crabidy_core::proto::crabidy::LibraryNode { fn get_lib_root(&self) -> crabidy_core::proto::crabidy::LibraryNode {
debug!("get_lib_root"); debug!("get_lib_root in tidaldy");
let global_root = crabidy_core::proto::crabidy::LibraryNode::new(); let global_root = crabidy_core::proto::crabidy::LibraryNode::new();
let children = vec![crabidy_core::proto::crabidy::LibraryNodeChild::new( let children = vec![
"node:userplaylists".to_string(), crabidy_core::proto::crabidy::LibraryNodeChild::new(
"playlists".to_string(), "node:userplaylists".to_string(),
false, "playlists".to_string(),
)]; false,
),
crabidy_core::proto::crabidy::LibraryNodeChild::new(
"node:userartists".to_string(),
"artists".to_string(),
false,
),
];
crabidy_core::proto::crabidy::LibraryNode { crabidy_core::proto::crabidy::LibraryNode {
uuid: "node:tidal".to_string(), uuid: "node:tidal".to_string(),
title: "tidal".to_string(), title: "tidal".to_string(),
@ -100,8 +109,9 @@ impl crabidy_core::ProviderClient for Client {
let Some(user_id) = self.settings.login.user_id.clone() else { let Some(user_id) = self.settings.login.user_id.clone() else {
return Err(crabidy_core::ProviderError::UnknownUser) return Err(crabidy_core::ProviderError::UnknownUser)
}; };
debug!("get_lib_node {}", uuid); debug!("get_lib_node in tidaldy{}", uuid);
let (_kind, module, uuid) = split_uuid(uuid); let (_kind, module, uuid) = split_uuid(uuid);
error!("module:{},uuid: {}", module, uuid);
let node = match module.as_str() { let node = match module.as_str() {
"userplaylists" => { "userplaylists" => {
let mut node = crabidy_core::proto::crabidy::LibraryNode { let mut node = crabidy_core::proto::crabidy::LibraryNode {
@ -138,6 +148,53 @@ impl crabidy_core::ProviderClient for Client {
node.parent = Some("node:userplaylists".to_string()); node.parent = Some("node:userplaylists".to_string());
node node
} }
"userartists" => {
let mut node = crabidy_core::proto::crabidy::LibraryNode {
uuid: "node:userartists".to_string(),
title: "artists".to_string(),
parent: Some("node:tidal".to_string()),
tracks: Vec::new(),
children: Vec::new(),
is_queable: false,
};
for artist in self.get_users_artists(&user_id).await? {
let child = crabidy_core::proto::crabidy::LibraryNodeChild::new(
format!("node:artist:{}", artist.item.id),
artist.item.name,
true,
);
node.children.push(child);
}
node
}
"artist" => {
info!("artist");
let mut node: crabidy_core::proto::crabidy::LibraryNode =
self.get_artist(&uuid).await?.into();
let children: Vec<crabidy_core::proto::crabidy::LibraryNodeChild> = self
.get_artist_albums(&uuid)
.await?
.iter()
.map(|t| t.into())
.collect();
node.children = children;
node.parent = Some("node:userartists".to_string());
node
}
"album" => {
let album = self.get_album(&uuid).await?;
let artis_id = album.artist.clone().unwrap().id;
let mut node: crabidy_core::proto::crabidy::LibraryNode = album.into();
let tracks: Vec<crabidy_core::proto::crabidy::Track> = self
.get_album_tracks(&uuid)
.await?
.iter()
.map(|t| t.into())
.collect();
node.tracks = tracks;
node.parent = Some(format!("node:artist:{}", artis_id));
node
}
_ => return Err(crabidy_core::ProviderError::MalformedUuid), _ => return Err(crabidy_core::ProviderError::MalformedUuid),
}; };
Ok(node) Ok(node)
@ -166,17 +223,18 @@ impl Client {
}) })
} }
#[instrument] #[instrument(skip(self))]
pub fn get_user_id(&self) -> Option<String> { pub fn get_user_id(&self) -> Option<String> {
self.settings.login.user_id.clone() self.settings.login.user_id.clone()
} }
#[instrument] #[instrument(skip(self))]
pub async fn make_request<T: DeserializeOwned>( pub async fn make_request<T: DeserializeOwned>(
&self, &self,
uri: &str, uri: &str,
query: Option<&[(&str, String)]>, query: Option<&[(&str, String)]>,
) -> Result<T, ClientError> { ) -> Result<T, ClientError> {
debug!("make_request {}", uri);
let Some(ref access_token) = self.settings.login.access_token.clone() else { let Some(ref access_token) = self.settings.login.access_token.clone() else {
return Err(ClientError::AuthError( return Err(ClientError::AuthError(
"No access token found".to_string(), "No access token found".to_string(),
@ -199,18 +257,27 @@ impl Client {
.bearer_auth(access_token) .bearer_auth(access_token)
.query(&params) .query(&params)
.send() .send()
.await? .await
.map_err(|e| {
error!("{:?}", e);
e
})?
.json() .json()
.await?; .await
.map_err(|e| {
error!("{:?}", e);
e
})?;
Ok(response) Ok(response)
} }
#[instrument] #[instrument(skip(self))]
pub async fn make_paginated_request<T: DeserializeOwned>( pub async fn make_paginated_request<T: DeserializeOwned>(
&self, &self,
uri: &str, uri: &str,
query: Option<&[(&str, String)]>, query: Option<&[(&str, String)]>,
) -> Result<Vec<T>, ClientError> { ) -> Result<Vec<T>, ClientError> {
debug!("make_paginated_request {}", uri);
let Some(ref access_token) = self.settings.login.access_token.clone() else { let Some(ref access_token) = self.settings.login.access_token.clone() else {
return Err(ClientError::AuthError( return Err(ClientError::AuthError(
"No access token found".to_string(), "No access token found".to_string(),
@ -236,9 +303,17 @@ impl Client {
.bearer_auth(access_token) .bearer_auth(access_token)
.query(&params) .query(&params)
.send() .send()
.await? .await
.map_err(|e| {
error!("{:?}", e);
e
})?
.json() .json()
.await?; .await
.map_err(|e| {
error!("{:?}", e);
e
})?;
let mut items = Vec::with_capacity(response.total_number_of_items); let mut items = Vec::with_capacity(response.total_number_of_items);
items.extend(response.items); items.extend(response.items);
while response.offset + limit < response.total_number_of_items { while response.offset + limit < response.total_number_of_items {
@ -255,15 +330,23 @@ impl Client {
.bearer_auth(access_token) .bearer_auth(access_token)
.query(&params) .query(&params)
.send() .send()
.await? .await
.map_err(|e| {
error!("{:?}", e);
e
})?
.json() .json()
.await?; .await
.map_err(|e| {
error!("{:?}", e);
e
})?;
items.extend(response.items); items.extend(response.items);
} }
Ok(items) Ok(items)
} }
#[instrument] #[instrument(skip(self))]
pub async fn make_explorer_request( pub async fn make_explorer_request(
&self, &self,
uri: &str, uri: &str,
@ -291,14 +374,22 @@ impl Client {
.bearer_auth(access_token) .bearer_auth(access_token)
.query(&params) .query(&params)
.send() .send()
.await? .await
.map_err(|e| {
error!("{:?}", e);
e
})?
.text() .text()
.await?; .await
.map_err(|e| {
error!("{:?}", e);
e
})?;
println!("{:?}", response); println!("{:?}", response);
Ok(()) Ok(())
} }
#[instrument] #[instrument(skip(self))]
pub async fn search(&self, query: &str) -> Result<(), ClientError> { pub async fn search(&self, query: &str) -> Result<(), ClientError> {
let query = vec![("query", query.to_string())]; let query = vec![("query", query.to_string())];
self.make_explorer_request(&format!("search/artists"), Some(&query)) self.make_explorer_request(&format!("search/artists"), Some(&query))
@ -306,7 +397,7 @@ impl Client {
Ok(()) Ok(())
} }
#[instrument] #[instrument(skip(self))]
pub async fn get_playlist_tracks( pub async fn get_playlist_tracks(
&self, &self,
playlist_uuid: &str, playlist_uuid: &str,
@ -316,21 +407,35 @@ impl Client {
.await?) .await?)
} }
#[instrument] #[instrument(skip(self))]
pub async fn get_playlist(&self, playlist_uuid: &str) -> Result<Playlist, ClientError> { pub async fn get_playlist(&self, playlist_uuid: &str) -> Result<Playlist, ClientError> {
Ok(self Ok(self
.make_request(&format!("playlists/{}", playlist_uuid), None) .make_request(&format!("playlists/{}", playlist_uuid), None)
.await?) .await?)
} }
#[instrument] #[instrument(skip(self))]
pub async fn get_artist(&self, artist_uuid: &str) -> Result<Artist, ClientError> {
Ok(self
.make_request(&format!("artists/{}", artist_uuid), None)
.await?)
}
#[instrument(skip(self))]
pub async fn get_artist_albums(&self, artist_uuid: &str) -> Result<Vec<Album>, ClientError> {
Ok(self
.make_paginated_request(&format!("artists/{}/albums", artist_uuid), None)
.await?)
}
#[instrument(skip(self))]
pub async fn get_users_playlists(&self, user_id: u64) -> Result<Vec<Playlist>, ClientError> { pub async fn get_users_playlists(&self, user_id: u64) -> Result<Vec<Playlist>, ClientError> {
Ok(self Ok(self
.make_paginated_request(&format!("users/{}/playlists", user_id), None) .make_paginated_request(&format!("users/{}/playlists", user_id), None)
.await?) .await?)
} }
#[instrument] #[instrument(skip(self))]
pub async fn get_users_playlists_and_favorite_playlists( pub async fn get_users_playlists_and_favorite_playlists(
&self, &self,
user_id: &str, user_id: &str,
@ -343,25 +448,7 @@ impl Client {
.await?) .await?)
} }
#[instrument] #[instrument(skip(self))]
pub async fn explore_get_users_playlists_and_favorite_playlists(
&self,
user_id: u64,
) -> Result<(), ClientError> {
let limit = 50;
let offset = 0;
let limit_param = ("limit", limit.to_string());
let offset_param = ("offset", offset.to_string());
let params: Vec<(&str, String)> = vec![limit_param, offset_param];
self.make_explorer_request(
&format!("users/{}/playlistsAndFavoritePlaylists", user_id),
Some(&params[..]),
)
.await?;
Ok(())
}
#[instrument]
pub async fn get_users_favorites(&self, user_id: u64) -> Result<(), ClientError> { pub async fn get_users_favorites(&self, user_id: u64) -> Result<(), ClientError> {
self.make_explorer_request( self.make_explorer_request(
&format!("users/{}/favorites", user_id), &format!("users/{}/favorites", user_id),
@ -372,7 +459,18 @@ impl Client {
Ok(()) Ok(())
} }
#[instrument] #[instrument(skip(self))]
pub async fn get_users_artists(&self, user_id: &str) -> Result<Vec<ArtistItem>, ClientError> {
Ok(self
.make_paginated_request(
&format!("users/{}/favorites/artists", user_id),
None,
// Some(&query),
)
.await?)
}
#[instrument(skip(self))]
pub async fn get_user(&self, user_id: u64) -> Result<(), ClientError> { pub async fn get_user(&self, user_id: u64) -> Result<(), ClientError> {
self.make_explorer_request( self.make_explorer_request(
&format!("users/{}", user_id), &format!("users/{}", user_id),
@ -383,7 +481,19 @@ impl Client {
Ok(()) Ok(())
} }
#[instrument] #[instrument(skip(self))]
pub async fn get_album(&self, album_id: &str) -> Result<Album, ClientError> {
self.make_request(&format!("albums/{}/", album_id), None)
.await
}
#[instrument(skip(self))]
pub async fn get_album_tracks(&self, album_id: &str) -> Result<Vec<Track>, ClientError> {
self.make_paginated_request(&format!("albums/{}/tracks", album_id), None)
.await
}
#[instrument(skip(self))]
pub async fn get_track_playback(&self, track_id: &str) -> Result<TrackPlayback, ClientError> { pub async fn get_track_playback(&self, track_id: &str) -> Result<TrackPlayback, ClientError> {
let query = vec![ let query = vec![
("audioquality", "LOSSLESS".to_string()), ("audioquality", "LOSSLESS".to_string()),
@ -397,14 +507,14 @@ impl Client {
.await .await
} }
#[instrument] #[instrument(skip(self))]
pub async fn get_track(&self, track_id: &str) -> Result<Track, ClientError> { pub async fn get_track(&self, track_id: &str) -> Result<Track, ClientError> {
let (_, track_id, _) = split_uuid(track_id); let (_, track_id, _) = split_uuid(track_id);
self.make_request(&format!("tracks/{}", track_id), None) self.make_request(&format!("tracks/{}", track_id), None)
.await .await
} }
#[instrument] #[instrument(skip(self))]
pub async fn login_web(&mut self) -> Result<(), ClientError> { pub async fn login_web(&mut self) -> Result<(), ClientError> {
let code_response = self.get_device_code().await?; let code_response = self.get_device_code().await?;
let now = Instant::now(); let now = Instant::now();
@ -443,7 +553,11 @@ impl Client {
.get(format!("{}/sessions", self.settings.base_url)) .get(format!("{}/sessions", self.settings.base_url))
.bearer_auth(access_token) .bearer_auth(access_token)
.send() .send()
.await? .await
.map_err(|e| {
error!("{:?}", e);
e
})?
.status() .status()
.is_success() .is_success()
{ {
@ -459,7 +573,7 @@ impl Client {
Ok(()) Ok(())
} }
#[instrument] #[instrument(skip(self))]
pub async fn refresh_access_token(&self) -> Result<RefreshResponse, ClientError> { pub async fn refresh_access_token(&self) -> Result<RefreshResponse, ClientError> {
let Some(refresh_token) = self.settings.login.refresh_token.clone() else { let Some(refresh_token) = self.settings.login.refresh_token.clone() else {
return Err(ClientError::AuthError( return Err(ClientError::AuthError(
@ -485,7 +599,11 @@ impl Client {
) )
.header("Content-Type", "application/x-www-form-urlencoded") .header("Content-Type", "application/x-www-form-urlencoded")
.send() .send()
.await?; .await
.map_err(|e| {
error!("{:?}", e);
e
})?;
if req.status().is_success() { if req.status().is_success() {
let res = req.json::<RefreshResponse>().await?; let res = req.json::<RefreshResponse>().await?;
Ok(res) Ok(res)
@ -495,7 +613,7 @@ impl Client {
)) ))
} }
} }
#[instrument] #[instrument(skip(self))]
async fn get_device_code(&self) -> Result<DeviceAuthResponse, ClientError> { async fn get_device_code(&self) -> Result<DeviceAuthResponse, ClientError> {
let req = DeviceAuthRequest { let req = DeviceAuthRequest {
client_id: self.settings.oauth.client_id.clone(), client_id: self.settings.oauth.client_id.clone(),
@ -512,7 +630,11 @@ impl Client {
.header("Content-Type", "application/x-www-form-urlencoded") .header("Content-Type", "application/x-www-form-urlencoded")
.body(payload) .body(payload)
.send() .send()
.await?; .await
.map_err(|e| {
error!("{:?}", e);
e
})?;
if !res.status().is_success() { if !res.status().is_success() {
return Err(ClientError::AuthError(res.status().to_string())); return Err(ClientError::AuthError(res.status().to_string()));
@ -521,7 +643,7 @@ impl Client {
Ok(code) Ok(code)
} }
#[instrument] #[instrument(skip(self))]
pub async fn check_auth_status( pub async fn check_auth_status(
&self, &self,
device_code: &str, device_code: &str,
@ -544,7 +666,11 @@ impl Client {
.body(payload) .body(payload)
.header("Content-Type", "application/x-www-form-urlencoded") .header("Content-Type", "application/x-www-form-urlencoded")
.send() .send()
.await?; .await
.map_err(|e| {
error!("{:?}", e);
e
})?;
if !res.status().is_success() { if !res.status().is_success() {
if res.status().is_client_error() { if res.status().is_client_error() {
return Err(ClientError::AuthError(format!( return Err(ClientError::AuthError(format!(
@ -564,25 +690,26 @@ impl Client {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crabidy_core::ProviderClient;
use super::*; use super::*;
fn setup() -> Client { async fn setup() -> Client {
let settings = crate::config::Settings::default(); let raw_toml_settings =
Client::new(settings).expect("could not create tidaldy client") std::fs::read_to_string("/home/hans/.config/crabidy/tidaldy.toml").unwrap();
Client::init(&raw_toml_settings).await.unwrap()
} }
#[tokio::test] #[tokio::test]
async fn test_get_device_code() { async fn test() {
let client = setup(); let client = setup().await;
println!("{:#?}", client); let user = client.settings.login.user_id.clone().unwrap();
let response = client.get_device_code().await.unwrap(); let result = client.get_users_artists(&user).await.unwrap();
assert!(!response.device_code.is_empty()); println!("{:?}", result);
assert_eq!(response.device_code.len(), 36); let result = client.get_artist("5293333").await.unwrap();
assert!(!response.user_code.is_empty()); println!("{:?}", result);
assert_eq!(response.user_code.len(), 5); let result = client.get_album("244167550").await.unwrap();
assert!(!response.verification_uri.is_empty()); println!("{:?}", result);
assert!(!response.verification_uri_complete.is_empty()); assert!(false);
assert!(response.expires_in == 300);
assert!(response.interval != 0);
} }
} }

View File

@ -1,5 +1,6 @@
use std::{str::FromStr, string::FromUtf8Error}; use std::{str::FromStr, string::FromUtf8Error};
use crabidy_core::proto::crabidy::{LibraryNode, LibraryNodeChild};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_json::Value; use serde_json::Value;
use thiserror::Error; use thiserror::Error;
@ -15,15 +16,58 @@ pub struct Page<T> {
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Item { pub struct ArtistItem {
pub created: String,
pub item: Artist,
}
impl From<ArtistItem> for LibraryNode {
fn from(item: ArtistItem) -> Self {
Self {
uuid: format!("artist:{}", item.item.id),
title: item.item.name,
children: Vec::new(),
parent: None,
tracks: Vec::new(),
is_queable: true,
}
}
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Artist {
pub id: i64, pub id: i64,
pub name: String, pub name: String,
pub artist_types: Vec<String>, pub artist_types: Option<Vec<String>>,
pub url: String, pub url: Option<String>,
pub picture: Value, pub picture: Option<Value>,
pub popularity: i64, pub popularity: Option<i64>,
pub artist_roles: Vec<ArtistRole>, pub artist_roles: Option<Vec<ArtistRole>>,
pub mixes: Mixes, pub mixes: Option<ArtistMixes>,
}
impl From<Artist> for LibraryNode {
fn from(artist: Artist) -> Self {
Self {
uuid: format!("node:artist:{}", artist.id),
title: artist.name,
children: Vec::new(),
parent: None,
tracks: Vec::new(),
is_queable: true,
}
}
}
impl From<Artist> for LibraryNodeChild {
fn from(artist: Artist) -> Self {
Self {
uuid: format!("node:artist:{}", artist.id),
title: artist.name,
is_queable: true,
}
}
} }
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
@ -126,43 +170,76 @@ impl TrackPlayback {
} }
} }
// #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
// #[serde(rename_all = "camelCase")]
// pub struct Track {
// pub id: u64,
// pub title: String,
// pub duration: u64,
// pub replay_gain: f64,
// pub peak: f64,
// pub allow_streaming: bool,
// pub stream_ready: bool,
// pub stream_start_date: Option<String>,
// pub premium_streaming_only: bool,
// pub track_number: u64,
// pub volume_number: u64,
// pub version: Value,
// pub popularity: u64,
// pub copyright: Option<String>,
// pub url: Option<String>,
// pub isrc: Option<String>,
// pub editable: bool,
// pub explicit: bool,
// pub audio_quality: String,
// pub audio_modes: Vec<String>,
// pub artist: Artist,
// pub artists: Vec<Artist>,
// pub album: Album,
// pub mixes: TrackMixes,
// }
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Track { pub struct Track {
pub id: u64, pub id: i64,
pub title: String, pub title: String,
pub duration: u64, pub duration: Option<i64>,
pub replay_gain: f64, pub replay_gain: Option<f64>,
pub peak: f64, pub peak: Option<f64>,
pub allow_streaming: bool, pub allow_streaming: Option<bool>,
pub stream_ready: bool, pub stream_ready: Option<bool>,
pub ad_supported_stream_ready: Option<bool>,
pub stream_start_date: Option<String>, pub stream_start_date: Option<String>,
pub premium_streaming_only: bool, pub premium_streaming_only: Option<bool>,
pub track_number: u64, pub track_number: Option<i64>,
pub volume_number: u64, pub volume_number: Option<i64>,
pub version: Value, pub version: Option<Value>,
pub popularity: u64, pub popularity: Option<i64>,
pub copyright: Option<String>, pub copyright: Option<String>,
pub url: Option<String>, pub url: Option<String>,
pub isrc: Option<String>, pub isrc: Option<String>,
pub editable: bool, pub editable: Option<bool>,
pub explicit: bool, pub explicit: Option<bool>,
pub audio_quality: String, pub audio_quality: Option<String>,
pub audio_modes: Vec<String>, pub audio_modes: Option<Vec<String>>,
pub artist: Artist, pub media_metadata: Option<MediaMetadata>,
pub artists: Vec<Artist>, pub artist: Option<Artist>,
pub album: Album, pub artists: Option<Vec<Artist>>,
pub mixes: Mixes, pub album: Option<Album>,
pub mixes: Option<TrackMixes>,
} }
impl From<Track> for crabidy_core::proto::crabidy::Track { impl From<Track> for crabidy_core::proto::crabidy::Track {
fn from(track: Track) -> Self { fn from(track: Track) -> Self {
Self { Self {
uuid: format!("track:{}", track.id), uuid: format!("track:{}", track.id),
title: track.title, title: track.title,
artist: track.artist.name, artist: match track.artist {
album: Some(track.album.into()), Some(a) => a.name.clone(),
duration: Some(track.duration as u32), None => "".to_string(),
},
album: track.album.map(|a| a.into()),
duration: track.duration.map(|d| d as u32 * 1000),
} }
} }
} }
@ -172,42 +249,143 @@ impl From<&Track> for crabidy_core::proto::crabidy::Track {
Self { Self {
uuid: format!("track:{}", track.id), uuid: format!("track:{}", track.id),
title: track.title.clone(), title: track.title.clone(),
artist: track.artist.name.clone(), artist: match track.artist.as_ref() {
album: Some(track.album.clone().into()), Some(a) => a.name.clone(),
duration: Some(track.duration as u32), None => "".to_string(),
},
album: track.album.clone().map(|a| a.into()),
duration: track.duration.map(|d| d as u32 * 1000),
} }
} }
} }
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] // #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] // #[serde(rename_all = "camelCase")]
pub struct Artist { // pub struct Artist {
pub id: i64, // pub id: i64,
pub name: String, // pub name: String,
#[serde(rename = "type")] // #[serde(rename = "type")]
pub type_field: String, // pub type_field: String,
pub picture: Value, // pub picture: Value,
} // }
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] // #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] // #[serde(rename_all = "camelCase")]
pub struct Artist2 { // pub struct Artist2 {
pub id: i64, // pub id: i64,
pub name: String, // pub name: String,
#[serde(rename = "type")] // #[serde(rename = "type")]
pub type_field: String, // pub type_field: String,
pub picture: Value, // pub picture: Value,
} // }
// #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
// #[serde(rename_all = "camelCase")]
// pub struct Album {
// pub id: i64,
// pub title: String,
// pub cover: String,
// pub vibrant_color: String,
// pub video_cover: Value,
// pub release_date: Option<String>,
// }
//
// #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
// #[serde(rename_all = "camelCase")]
// pub struct Root {
// pub id: i64,
// pub title: String,
// pub duration: i64,
// pub stream_ready: bool,
// pub ad_supported_stream_ready: bool,
// pub stream_start_date: String,
// pub allow_streaming: bool,
// pub premium_streaming_only: bool,
// pub number_of_tracks: i64,
// pub number_of_videos: i64,
// pub number_of_volumes: i64,
// pub release_date: String,
// pub copyright: String,
// #[serde(rename = "type")]
// pub type_field: String,
// pub version: Value,
// pub url: String,
// pub cover: String,
// pub vibrant_color: String,
// pub video_cover: Value,
// pub explicit: bool,
// pub upc: String,
// pub popularity: i64,
// pub audio_quality: String,
// pub audio_modes: Vec<String>,
// pub media_metadata: MediaMetadata,
// pub artist: Artist,
// pub artists: Vec<Artist2>,
// }
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Album { pub struct Album {
pub id: i64, pub id: i64,
pub title: String, pub title: String,
pub cover: String, pub cover: Option<String>,
pub vibrant_color: String, pub vibrant_color: Option<String>,
pub video_cover: Value,
pub release_date: Option<String>, pub release_date: Option<String>,
pub duration: Option<i64>,
pub stream_ready: Option<bool>,
pub ad_supported_stream_ready: Option<bool>,
pub stream_start_date: Option<String>,
pub allow_streaming: Option<bool>,
pub premium_streaming_only: Option<bool>,
pub number_of_tracks: Option<i64>,
pub number_of_videos: Option<i64>,
pub number_of_volumes: Option<i64>,
pub copyright: Option<String>,
#[serde(rename = "type")]
pub type_field: Option<String>,
pub version: Option<Value>,
pub url: Option<String>,
pub video_cover: Option<Value>,
pub explicit: Option<bool>,
pub upc: Option<String>,
pub popularity: Option<i64>,
pub audio_quality: Option<String>,
pub audio_modes: Option<Vec<String>>,
pub media_metadata: Option<MediaMetadata>,
pub artist: Option<Artist>,
pub artists: Option<Vec<Artist>>,
}
impl From<Album> for crabidy_core::proto::crabidy::LibraryNode {
fn from(album: Album) -> Self {
Self {
uuid: format!("node:album:{}", album.id),
title: album.title,
children: Vec::new(),
parent: None,
tracks: Vec::new(),
is_queable: true,
}
}
}
impl From<Album> for crabidy_core::proto::crabidy::LibraryNodeChild {
fn from(album: Album) -> Self {
Self {
uuid: format!("node:album:{}", album.id),
title: album.title,
is_queable: true,
}
}
}
impl From<&Album> for crabidy_core::proto::crabidy::LibraryNodeChild {
fn from(album: &Album) -> Self {
Self {
uuid: format!("node:album:{}", album.id),
title: album.title.clone(),
is_queable: true,
}
}
} }
impl From<Album> for crabidy_core::proto::crabidy::Album { impl From<Album> for crabidy_core::proto::crabidy::Album {
@ -221,11 +399,26 @@ impl From<Album> for crabidy_core::proto::crabidy::Album {
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct Mixes { pub struct MediaMetadata {
pub tags: Vec<String>,
}
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TrackMixes {
#[serde(rename = "TRACK_MIX")] #[serde(rename = "TRACK_MIX")]
pub track_mix: Option<String>, pub track_mix: Option<String>,
} }
#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ArtistMixes {
#[serde(rename = "MASTER_ARTIST_MIX")]
pub master_artist_mix: Option<String>,
#[serde(rename = "ARTIST_MIX")]
pub artist_mix: Option<String>,
}
#[derive(Deserialize, Serialize, Debug)] #[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all(deserialize = "camelCase"))] #[serde(rename_all(deserialize = "camelCase"))]
pub struct PlaybackManifest { pub struct PlaybackManifest {
@ -359,7 +552,7 @@ pub struct PlaylistTrack {
pub artist: Artist, pub artist: Artist,
pub artists: Vec<Artist>, pub artists: Vec<Artist>,
pub album: Album, pub album: Album,
pub mixes: Mixes, pub mixes: TrackMixes,
pub date_added: String, pub date_added: String,
pub index: i64, pub index: i64,
pub item_uuid: String, pub item_uuid: String,