From 085927caa16c39968463b8bfacf6eea36f629e8e Mon Sep 17 00:00:00 2001 From: G2-Games Date: Wed, 1 Nov 2023 14:14:28 -0500 Subject: [PATCH] `cargo fmt` --- src/lib.rs | 6 +- src/music_controller/config.rs | 30 +-- src/music_controller/init.rs | 11 +- src/music_controller/music_controller.rs | 45 ++-- src/music_player/music_output.rs | 153 ++++++++----- src/music_player/music_player.rs | 264 ++++++++++++++--------- src/music_player/music_resampler.rs | 13 +- src/music_processor/music_processor.rs | 20 +- src/music_storage/music_db.rs | 50 +++-- src/music_storage/playlist.rs | 8 +- src/music_tracker/music_tracker.rs | 202 ++++++++++------- 11 files changed, 474 insertions(+), 328 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3a25b3d..75b98ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,13 +12,13 @@ pub mod music_processor { } pub mod music_player { - pub mod music_player; pub mod music_output; + pub mod music_player; pub mod music_resampler; } pub mod music_controller { - pub mod music_controller; pub mod config; pub mod init; -} \ No newline at end of file + pub mod music_controller; +} diff --git a/src/music_controller/config.rs b/src/music_controller/config.rs index f6909f5..82cbd22 100644 --- a/src/music_controller/config.rs +++ b/src/music_controller/config.rs @@ -1,10 +1,10 @@ -use std::path::PathBuf; -use std::fs::read_to_string; use std::fs; +use std::fs::read_to_string; +use std::path::PathBuf; use serde::{Deserialize, Serialize}; -use crate::music_tracker::music_tracker::{LastFMConfig, DiscordRPCConfig, ListenBrainzConfig}; +use crate::music_tracker::music_tracker::{DiscordRPCConfig, LastFMConfig, ListenBrainzConfig}; #[derive(Serialize, Deserialize, PartialEq, Eq)] pub struct Config { @@ -22,51 +22,53 @@ impl Default for Config { db_path: Box::new(path), lastfm: None, - + discord: Some(DiscordRPCConfig { enabled: true, dango_client_id: 1144475145864499240, dango_icon: String::from("flat"), }), - + listenbrainz: Some(ListenBrainzConfig { enabled: false, api_url: String::from("https://api.listenbrainz.org"), auth_token: String::from(""), - }) + }), }; } } impl Config { /// Creates and saves a new config with default values - pub fn new(config_file: &PathBuf) -> std::io::Result { + pub fn new(config_file: &PathBuf) -> std::io::Result { let config = Config::default(); config.save(config_file)?; - + Ok(config) } /// Loads config from given file path pub fn from(config_file: &PathBuf) -> std::result::Result { - return toml::from_str(&read_to_string(config_file) - .expect("Failed to initalize music config: File not found!")); + return toml::from_str( + &read_to_string(config_file) + .expect("Failed to initalize music config: File not found!"), + ); } - + /// Saves config to given path /// Saves -> temp file, if successful, removes old config, and renames temp to given path pub fn save(&self, config_file: &PathBuf) -> std::io::Result<()> { let toml = toml::to_string_pretty(self).unwrap(); - + let mut temp_file = config_file.clone(); temp_file.set_extension("tomltemp"); - + fs::write(&temp_file, toml)?; // If configuration file already exists, delete it match fs::metadata(config_file) { Ok(_) => fs::remove_file(config_file)?, - Err(_) => {}, + Err(_) => {} } fs::rename(temp_file, config_file)?; diff --git a/src/music_controller/init.rs b/src/music_controller/init.rs index 80b91bf..38b101f 100644 --- a/src/music_controller/init.rs +++ b/src/music_controller/init.rs @@ -1,9 +1,7 @@ -use std::path::Path; use std::fs::File; +use std::path::Path; -pub fn init() { - -} +pub fn init() {} fn init_config() { let config_path = "./config.toml"; @@ -13,7 +11,4 @@ fn init_config() { } } -fn init_db() { - -} - +fn init_db() {} diff --git a/src/music_controller/music_controller.rs b/src/music_controller/music_controller.rs index 7d00b8a..24b8379 100644 --- a/src/music_controller/music_controller.rs +++ b/src/music_controller/music_controller.rs @@ -1,8 +1,8 @@ use std::path::PathBuf; -use std::sync::{RwLock, Arc}; +use std::sync::{Arc, RwLock}; use crate::music_controller::config::Config; -use crate::music_player::music_player::{MusicPlayer, PlayerStatus, DecoderMessage, DSPMessage}; +use crate::music_player::music_player::{DSPMessage, DecoderMessage, MusicPlayer, PlayerStatus}; use crate::music_storage::music_db::{MusicLibrary, Song, Tag}; pub struct MusicController { @@ -13,75 +13,78 @@ pub struct MusicController { impl MusicController { /// Creates new MusicController with config at given path - pub fn new(config_path: &PathBuf) -> Result>{ + pub fn new(config_path: &PathBuf) -> Result> { let config = Arc::new(RwLock::new(Config::new(config_path)?)); let music_player = MusicPlayer::new(config.clone()); let library = match MusicLibrary::init(config.clone()) { Ok(library) => library, - Err(error) => return Err(error) + Err(error) => return Err(error), }; - + let controller = MusicController { config, library, music_player, }; - - return Ok(controller) + + return Ok(controller); } - + /// Creates new music controller from a config at given path pub fn from(config_path: &PathBuf) -> Result> { let config = Arc::new(RwLock::new(Config::from(config_path)?)); let music_player = MusicPlayer::new(config.clone()); let library = match MusicLibrary::init(config.clone()) { Ok(library) => library, - Err(error) => return Err(error) + Err(error) => return Err(error), }; - + let controller = MusicController { config, library, music_player, }; - - return Ok(controller) + + return Ok(controller); } - + /// Sends given message to control music player pub fn song_control(&mut self, message: DecoderMessage) { self.music_player.send_message(message); } - + /// Gets status of the music player pub fn player_status(&mut self) -> PlayerStatus { return self.music_player.get_status(); } - + /// Gets current song being controlled, if any pub fn get_current_song(&self) -> Option { return self.music_player.get_current_song(); } - + /// Gets audio playback volume pub fn get_vol(&self) -> f32 { return self.music_player.music_processor.audio_volume; } - + /// Sets audio playback volume on a scale of 0.0 to 1.0 pub fn set_vol(&mut self, volume: f32) { self.music_player.music_processor.audio_volume = volume; - self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new(self.music_player.music_processor.clone())))); + self.song_control(DecoderMessage::DSP(DSPMessage::UpdateProcessor(Box::new( + self.music_player.music_processor.clone(), + )))); } - + /// Queries the [MusicLibrary], returning a `Vec` pub fn query_library( &self, query_string: &String, target_tags: Vec, search_location: bool, - sort_by: Vec + sort_by: Vec, ) -> Option> { - self.library.query(query_string, &target_tags, search_location, &sort_by) + self.library + .query(query_string, &target_tags, search_location, &sort_by) } } diff --git a/src/music_player/music_output.rs b/src/music_player/music_output.rs index 795e9e9..76e6d3b 100644 --- a/src/music_player/music_output.rs +++ b/src/music_player/music_output.rs @@ -1,7 +1,7 @@ use std::{result, thread}; -use symphonia::core::audio::{AudioBufferRef, SignalSpec, RawSample, SampleBuffer}; -use symphonia::core::conv::{ConvertibleSample, IntoSample, FromSample}; +use symphonia::core::audio::{AudioBufferRef, RawSample, SampleBuffer, SignalSpec}; +use symphonia::core::conv::{ConvertibleSample, FromSample, IntoSample}; use symphonia::core::units::Duration; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; @@ -25,10 +25,21 @@ pub enum AudioOutputError { pub type Result = result::Result; -pub trait OutputSample: SizedSample + FromSample + IntoSample +cpal::Sample + ConvertibleSample + RawSample + std::marker::Send + 'static {} +pub trait OutputSample: + SizedSample + + FromSample + + IntoSample + + cpal::Sample + + ConvertibleSample + + RawSample + + std::marker::Send + + 'static +{ +} pub struct AudioOutput -where T: OutputSample, +where + T: OutputSample, { ring_buf_producer: rb::Producer, sample_buf: SampleBuffer, @@ -48,80 +59,112 @@ impl OutputSample for f64 {} //create a new trait with functions, then impl that somehow pub fn open_stream(spec: SignalSpec, duration: Duration) -> Result> { - let host = cpal::default_host(); - - // Uses default audio device - let device = match host.default_output_device() { - Some(device) => device, - _ => return Err(AudioOutputError::OpenStreamError), - }; - - let config = match device.default_output_config() { - Ok(config) => config, - Err(err) => return Err(AudioOutputError::OpenStreamError), - }; - - return match config.sample_format(){ - cpal::SampleFormat::I8 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::I16 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::I32 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - //cpal::SampleFormat::I64 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::U8 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::U16 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::U32 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - //cpal::SampleFormat::U64 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::F32 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - cpal::SampleFormat::F64 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), - _ => todo!(), - }; + let host = cpal::default_host(); + + // Uses default audio device + let device = match host.default_output_device() { + Some(device) => device, + _ => return Err(AudioOutputError::OpenStreamError), + }; + + let config = match device.default_output_config() { + Ok(config) => config, + Err(err) => return Err(AudioOutputError::OpenStreamError), + }; + + return match config.sample_format() { + cpal::SampleFormat::I8 => { + AudioOutput::::create_stream(spec, &device, &config.into(), duration) + } + cpal::SampleFormat::I16 => { + AudioOutput::::create_stream(spec, &device, &config.into(), duration) + } + cpal::SampleFormat::I32 => { + AudioOutput::::create_stream(spec, &device, &config.into(), duration) + } + //cpal::SampleFormat::I64 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), + cpal::SampleFormat::U8 => { + AudioOutput::::create_stream(spec, &device, &config.into(), duration) + } + cpal::SampleFormat::U16 => { + AudioOutput::::create_stream(spec, &device, &config.into(), duration) + } + cpal::SampleFormat::U32 => { + AudioOutput::::create_stream(spec, &device, &config.into(), duration) + } + //cpal::SampleFormat::U64 => AudioOutput::::create_stream(spec, &device, &config.into(), duration), + cpal::SampleFormat::F32 => { + AudioOutput::::create_stream(spec, &device, &config.into(), duration) + } + cpal::SampleFormat::F64 => { + AudioOutput::::create_stream(spec, &device, &config.into(), duration) + } + _ => todo!(), + }; } impl AudioOutput { // Creates the stream (TODO: Merge w/open_stream?) - fn create_stream(spec: SignalSpec, device: &cpal::Device, config: &cpal::StreamConfig, duration: Duration) -> Result> { + fn create_stream( + spec: SignalSpec, + device: &cpal::Device, + config: &cpal::StreamConfig, + duration: Duration, + ) -> Result> { let num_channels = config.channels as usize; - + // Ring buffer is created with 200ms audio capacity let ring_len = ((50 * config.sample_rate.0 as usize) / 1000) * num_channels; - let ring_buf= rb::SpscRb::new(ring_len); - + let ring_buf = rb::SpscRb::new(ring_len); + let ring_buf_producer = ring_buf.producer(); let ring_buf_consumer = ring_buf.consumer(); - + let stream_result = device.build_output_stream( - config, + config, move |data: &mut [T], _: &cpal::OutputCallbackInfo| { // Writes samples in the ring buffer to the audio output let written = ring_buf_consumer.read(data).unwrap_or(0); - + // Mutes non-written samples - data[written..].iter_mut().for_each(|sample| *sample = T::MID); + data[written..] + .iter_mut() + .for_each(|sample| *sample = T::MID); }, //TODO: Handle error here properly move |err| println!("Yeah we erroring out here"), - None + None, ); - + if let Err(err) = stream_result { return Err(AudioOutputError::OpenStreamError); } - + let stream = stream_result.unwrap(); - + //Start output stream if let Err(err) = stream.play() { return Err(AudioOutputError::PlayStreamError); } - + let sample_buf = SampleBuffer::::new(duration, spec); - + let mut resampler = None; if spec.rate != config.sample_rate.0 { println!("Resampling enabled"); - resampler = Some(Resampler::new(spec, config.sample_rate.0 as usize, duration)) + resampler = Some(Resampler::new( + spec, + config.sample_rate.0 as usize, + duration, + )) } - - Ok(Box::new(AudioOutput { ring_buf_producer, sample_buf, stream, resampler})) + + Ok(Box::new(AudioOutput { + ring_buf_producer, + sample_buf, + stream, + resampler, + })) } } @@ -131,7 +174,7 @@ impl AudioStream for AudioOutput { if decoded.frames() == 0 { return Ok(()); } - + let mut samples: &[T] = if let Some(resampler) = &mut self.resampler { // Resamples if required match resampler.resample(decoded) { @@ -142,25 +185,25 @@ impl AudioStream for AudioOutput { self.sample_buf.copy_interleaved_ref(decoded); self.sample_buf.samples() }; - + // Write samples into ring buffer while let Some(written) = self.ring_buf_producer.write_blocking(samples) { samples = &samples[written..]; } - + Ok(()) } - + // Flushes resampler if needed fn flush(&mut self) { if let Some(resampler) = &mut self.resampler { - let mut stale_samples = resampler.flush().unwrap_or_default(); - + let mut stale_samples = resampler.flush().unwrap_or_default(); + while let Some(written) = self.ring_buf_producer.write_blocking(stale_samples) { stale_samples = &stale_samples[written..]; } } - + let _ = self.stream.pause(); } -} \ No newline at end of file +} diff --git a/src/music_player/music_player.rs b/src/music_player/music_player.rs index f12f68c..9ed99e5 100644 --- a/src/music_player/music_player.rs +++ b/src/music_player/music_player.rs @@ -1,18 +1,18 @@ -use std::sync::mpsc::{self, Sender, Receiver}; +use std::io::SeekFrom; +use std::sync::mpsc::{self, Receiver, Sender}; use std::sync::{Arc, RwLock}; use std::thread; -use std::io::SeekFrom; use async_std::io::ReadExt; use async_std::task; use futures::future::join_all; -use symphonia::core::codecs::{CODEC_TYPE_NULL, DecoderOptions, Decoder}; +use symphonia::core::codecs::{Decoder, DecoderOptions, CODEC_TYPE_NULL}; +use symphonia::core::errors::Error; use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo}; -use symphonia::core::io::{MediaSourceStream, MediaSource}; +use symphonia::core::io::{MediaSource, MediaSourceStream}; use symphonia::core::meta::MetadataOptions; use symphonia::core::probe::Hint; -use symphonia::core::errors::Error; use symphonia::core::units::{Time, TimeBase}; use futures::AsyncBufRead; @@ -20,8 +20,10 @@ use futures::AsyncBufRead; use crate::music_controller::config::Config; use crate::music_player::music_output::AudioStream; use crate::music_processor::music_processor::MusicProcessor; -use crate::music_storage::music_db::{URI, Song}; -use crate::music_tracker::music_tracker::{MusicTracker, TrackerError, LastFM, DiscordRPC, ListenBrainz}; +use crate::music_storage::music_db::{Song, URI}; +use crate::music_tracker::music_tracker::{ + DiscordRPC, LastFM, ListenBrainz, MusicTracker, TrackerError, +}; // Struct that controls playback of music pub struct MusicPlayer { @@ -49,18 +51,18 @@ pub enum DecoderMessage { Pause, Stop, SeekTo(u64), - DSP(DSPMessage) + DSP(DSPMessage), } #[derive(Clone)] pub enum TrackerMessage { Track(Song), - TrackNow(Song) + TrackNow(Song), } #[derive(Debug, Clone)] pub enum DSPMessage { - UpdateProcessor(Box) + UpdateProcessor(Box), } // Holds a song decoder reader, etc @@ -75,49 +77,59 @@ struct SongHandler { impl SongHandler { pub fn new(uri: &URI) -> Result { // Opens remote/local source and creates MediaSource for symphonia - let config = RemoteOptions {media_buffer_len: 10000, forward_buffer_len: 10000}; + let config = RemoteOptions { + media_buffer_len: 10000, + forward_buffer_len: 10000, + }; let src: Box = match uri { - URI::Local(path) => { - match std::fs::File::open(path) { - Ok(file) => Box::new(file), - Err(_) => return Err(()), - } + URI::Local(path) => match std::fs::File::open(path) { + Ok(file) => Box::new(file), + Err(_) => return Err(()), }, URI::Remote(_, location) => { match RemoteSource::new(location.to_str().unwrap(), &config) { Ok(remote_source) => Box::new(remote_source), Err(_) => return Err(()), } - }, - _ => todo!() + } + _ => todo!(), }; - + let mss = MediaSourceStream::new(src, Default::default()); - + // Use default metadata and format options let meta_opts: MetadataOptions = Default::default(); let fmt_opts: FormatOptions = Default::default(); let hint = Hint::new(); - - let probed = symphonia::default::get_probe().format(&hint, mss, &fmt_opts, &meta_opts).expect("Unsupported format"); - - let reader = probed.format; - - let track = reader.tracks() - .iter() - .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) - .expect("no supported audio tracks"); - + + let probed = symphonia::default::get_probe() + .format(&hint, mss, &fmt_opts, &meta_opts) + .expect("Unsupported format"); + + let reader = probed.format; + + let track = reader + .tracks() + .iter() + .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) + .expect("no supported audio tracks"); + let time_base = track.codec_params.time_base; let duration = track.codec_params.n_frames; - + let dec_opts: DecoderOptions = Default::default(); - - let decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts) - .expect("unsupported codec"); - - return Ok(SongHandler {reader, decoder, time_base, duration}); + + let decoder = symphonia::default::get_codecs() + .make(&track.codec_params, &dec_opts) + .expect("unsupported codec"); + + return Ok(SongHandler { + reader, + decoder, + time_base, + duration, + }); } } @@ -127,9 +139,14 @@ impl MusicPlayer { let (message_sender, message_receiver) = mpsc::channel(); let (status_sender, status_receiver) = mpsc::channel(); let current_song = Arc::new(RwLock::new(None)); - - MusicPlayer::start_player(message_receiver, status_sender, config.clone(), current_song.clone()); - + + MusicPlayer::start_player( + message_receiver, + status_sender, + config.clone(), + current_song.clone(), + ); + MusicPlayer { music_processor: MusicProcessor::new(), music_trackers: Vec::new(), @@ -140,15 +157,19 @@ impl MusicPlayer { config, } } - - fn start_tracker(status_sender: Sender>, tracker_receiver: Receiver, config: Arc>) { + + fn start_tracker( + status_sender: Sender>, + tracker_receiver: Receiver, + config: Arc>, + ) { thread::spawn(move || { let global_config = &*config.read().unwrap(); // Sets local config for trackers to detect changes let local_config = global_config.clone(); let mut trackers: Vec> = Vec::new(); // Updates local trackers to the music controller config // TODO: refactor - let update_trackers = |trackers: &mut Vec>|{ + let update_trackers = |trackers: &mut Vec>| { if let Some(lastfm_config) = global_config.lastfm.clone() { if lastfm_config.enabled { trackers.push(Box::new(LastFM::new(&lastfm_config))); @@ -177,9 +198,13 @@ impl MusicPlayer { let mut futures = Vec::new(); for tracker in trackers.iter_mut() { match message.clone() { - Ok(TrackerMessage::Track(song)) => futures.push(tracker.track_song(song)), - Ok(TrackerMessage::TrackNow(song)) => futures.push(tracker.track_now(song)), - Err(_) => {}, + Ok(TrackerMessage::Track(song)) => { + futures.push(tracker.track_song(song)) + } + Ok(TrackerMessage::TrackNow(song)) => { + futures.push(tracker.track_now(song)) + } + Err(_) => {} } } results = join_all(futures).await; @@ -192,30 +217,41 @@ impl MusicPlayer { } }); } - + // Opens and plays song with given path in separate thread - fn start_player(message_receiver: Receiver, status_sender: Sender, config: Arc>, current_song: Arc>>) { + fn start_player( + message_receiver: Receiver, + status_sender: Sender, + config: Arc>, + current_song: Arc>>, + ) { // Creates thread that audio is decoded in thread::spawn(move || { let current_song = current_song; - + let mut song_handler = None; - + let mut seek_time: Option = None; - + let mut audio_output: Option> = None; - + let mut music_processor = MusicProcessor::new(); - - let (tracker_sender, tracker_receiver): (Sender, Receiver) = mpsc::channel(); - let (tracker_status_sender, tracker_status_receiver): (Sender>, Receiver>) = mpsc::channel(); - + + let (tracker_sender, tracker_receiver): ( + Sender, + Receiver, + ) = mpsc::channel(); + let (tracker_status_sender, tracker_status_receiver): ( + Sender>, + Receiver>, + ) = mpsc::channel(); + MusicPlayer::start_tracker(tracker_status_sender, tracker_receiver, config); - + let mut song_tracked = false; let mut song_time = 0.0; let mut paused = true; - 'main_decode: loop { + 'main_decode: loop { 'handle_message: loop { let message = if paused { // Pauses playback by blocking on waiting for new player messages @@ -254,26 +290,34 @@ impl MusicPlayer { status_sender.send(PlayerStatus::Paused).unwrap(); } Some(DecoderMessage::SeekTo(time)) => seek_time = Some(time), - Some(DecoderMessage::DSP(dsp_message)) => { - match dsp_message { - DSPMessage::UpdateProcessor(new_processor) => music_processor = *new_processor, + Some(DecoderMessage::DSP(dsp_message)) => match dsp_message { + DSPMessage::UpdateProcessor(new_processor) => { + music_processor = *new_processor } - } + }, // Exits main decode loop and subsequently ends thread Some(DecoderMessage::Stop) => { status_sender.send(PlayerStatus::Stopped).unwrap(); - break 'main_decode + break 'main_decode; } - None => {}, + None => {} } status_sender.send(PlayerStatus::Error).unwrap(); - } + } // In theory this check should not need to occur? - if let (Some(song_handler), current_song) = (&mut song_handler, &*current_song.read().unwrap()) { + if let (Some(song_handler), current_song) = + (&mut song_handler, &*current_song.read().unwrap()) + { match seek_time { Some(time) => { - let seek_to = SeekTo::Time { time: Time::from(time), track_id: Some(0) }; - song_handler.reader.seek(SeekMode::Accurate, seek_to).unwrap(); + let seek_to = SeekTo::Time { + time: Time::from(time), + track_id: Some(0), + }; + song_handler + .reader + .seek(SeekMode::Accurate, seek_to) + .unwrap(); seek_time = None; } None => {} //Nothing to do! @@ -286,60 +330,74 @@ impl MusicPlayer { panic!("{}", err); } }; - + if let (Some(time_base), Some(song)) = (song_handler.time_base, current_song) { let time_units = time_base.calc_time(packet.ts); song_time = time_units.seconds as f64 + time_units.frac; // Tracks song now if song has just started if song_time == 0.0 { - tracker_sender.send(TrackerMessage::TrackNow(song.clone())).unwrap(); + tracker_sender + .send(TrackerMessage::TrackNow(song.clone())) + .unwrap(); } - + if let Some(duration) = song_handler.duration { let song_duration = time_base.calc_time(duration); - let song_duration_secs = song_duration.seconds as f64 + song_duration.frac; + let song_duration_secs = + song_duration.seconds as f64 + song_duration.frac; // Tracks song if current time is past half of total song duration or past 4 minutes - if (song_duration_secs / 2.0 < song_time || song_time > 240.0) && !song_tracked { + if (song_duration_secs / 2.0 < song_time || song_time > 240.0) + && !song_tracked + { song_tracked = true; - tracker_sender.send(TrackerMessage::Track(song.clone())).unwrap(); + tracker_sender + .send(TrackerMessage::Track(song.clone())) + .unwrap(); } } } - - status_sender.send(PlayerStatus::Playing(song_time)).unwrap(); - + + status_sender + .send(PlayerStatus::Playing(song_time)) + .unwrap(); + match song_handler.decoder.decode(&packet) { Ok(decoded) => { // Opens audio stream if there is not one if audio_output.is_none() { let spec = *decoded.spec(); let duration = decoded.capacity() as u64; - - audio_output.replace(crate::music_player::music_output::open_stream(spec, duration).unwrap()); + + audio_output.replace( + crate::music_player::music_output::open_stream(spec, duration) + .unwrap(), + ); } // Handles audio normally provided there is an audio stream if let Some(ref mut audio_output) = audio_output { // Changes buffer of the MusicProcessor if the packet has a differing capacity or spec - if music_processor.audio_buffer.capacity() != decoded.capacity() ||music_processor.audio_buffer.spec() != decoded.spec() { + if music_processor.audio_buffer.capacity() != decoded.capacity() + || music_processor.audio_buffer.spec() != decoded.spec() + { let spec = *decoded.spec(); let duration = decoded.capacity() as u64; - + music_processor.set_buffer(duration, spec); } let transformed_audio = music_processor.process(&decoded); - + // Writes transformed packet to audio out audio_output.write(transformed_audio).unwrap() } - }, + } Err(Error::IoError(_)) => { // rest in peace packet continue; - }, + } Err(Error::DecodeError(_)) => { // may you one day be decoded continue; - }, + } Err(err) => { // Unrecoverable, though shouldn't panic here panic!("{}", err); @@ -349,28 +407,28 @@ impl MusicPlayer { } }); } - + // Updates status by checking on messages from spawned thread fn update_player(&mut self) { for message in self.status_receiver.try_recv() { self.player_status = message; } } - - pub fn get_current_song(&self) -> Option{ + + pub fn get_current_song(&self) -> Option { match self.current_song.try_read() { Ok(song) => return (*song).clone(), Err(_) => return None, } } - + // Sends message to spawned thread pub fn send_message(&mut self, message: DecoderMessage) { self.update_player(); // Checks that message sender exists before sending a message off self.message_sender.send(message).unwrap(); } - + pub fn get_status(&mut self) -> PlayerStatus { self.update_player(); return self.player_status; @@ -394,7 +452,7 @@ impl Default for RemoteOptions { media_buffer_len: 100000, forward_buffer_len: 1024, } - } + } } /// A remote source of media @@ -408,12 +466,12 @@ struct RemoteSource { impl RemoteSource { /// Creates a new RemoteSource with given uri and configuration pub fn new(uri: &str, config: &RemoteOptions) -> Result { - let mut response = task::block_on(async { + let mut response = task::block_on(async { return surf::get(uri).await; })?; - + let reader = response.take_body().into_reader(); - + Ok(RemoteSource { reader, media_buffer: Vec::new(), @@ -433,16 +491,16 @@ impl std::io::Read for RemoteSource { Ok(_) => { self.media_buffer.extend_from_slice(&buffer); return Ok(()); - }, + } Err(err) => return Err(err), } }); match read_bytes { Err(err) => return Err(err), - _ => {}, + _ => {} } } - // Reads bytes from the media buffer into the buffer given by + // Reads bytes from the media buffer into the buffer given by let mut bytes_read = 0; for location in 0..1024 { if (location + self.offset as usize) < self.media_buffer.len() { @@ -450,7 +508,7 @@ impl std::io::Read for RemoteSource { bytes_read += 1; } } - + self.offset += bytes_read; return Ok(bytes_read as usize); } @@ -463,13 +521,13 @@ impl std::io::Seek for RemoteSource { match pos { // Offset is set to given position SeekFrom::Start(pos) => { - if pos > self.media_buffer.len() as u64{ + if pos > self.media_buffer.len() as u64 { self.offset = self.media_buffer.len() as u64; } else { self.offset = pos; } return Ok(self.offset); - }, + } // Offset is set to length of buffer + given position SeekFrom::End(pos) => { if self.media_buffer.len() as u64 + pos as u64 > self.media_buffer.len() as u64 { @@ -478,16 +536,16 @@ impl std::io::Seek for RemoteSource { self.offset = self.media_buffer.len() as u64 + pos as u64; } return Ok(self.offset); - }, + } // Offset is set to current offset + given position SeekFrom::Current(pos) => { - if self.offset + pos as u64 > self.media_buffer.len() as u64{ + if self.offset + pos as u64 > self.media_buffer.len() as u64 { self.offset = self.media_buffer.len() as u64; } else { self.offset += pos as u64 } return Ok(self.offset); - }, + } } } } @@ -496,7 +554,7 @@ impl MediaSource for RemoteSource { fn is_seekable(&self) -> bool { return true; } - + fn byte_len(&self) -> Option { return None; } diff --git a/src/music_player/music_resampler.rs b/src/music_player/music_resampler.rs index f654a17..4040835 100644 --- a/src/music_player/music_resampler.rs +++ b/src/music_player/music_resampler.rs @@ -48,7 +48,8 @@ where // Interleave the planar samples from Rubato. let num_channels = self.output.len(); - self.interleaved.resize(num_channels * self.output[0].len(), T::MID); + self.interleaved + .resize(num_channels * self.output[0].len(), T::MID); for (i, frame) in self.interleaved.chunks_exact_mut(num_channels).enumerate() { for (ch, s) in frame.iter_mut().enumerate() { @@ -81,7 +82,13 @@ where let input = vec![Vec::with_capacity(duration); num_channels]; - Self { resampler, input, output, duration, interleaved: Default::default() } + Self { + resampler, + input, + output, + duration, + interleaved: Default::default(), + } } /// Resamples a planar/non-interleaved input. @@ -144,4 +151,4 @@ where let src = input.chan(c); dst.extend(src.iter().map(|&s| s.into_sample())); } -} \ No newline at end of file +} diff --git a/src/music_processor/music_processor.rs b/src/music_processor/music_processor.rs index e6d6608..64f4eaa 100644 --- a/src/music_processor/music_processor.rs +++ b/src/music_processor/music_processor.rs @@ -1,6 +1,6 @@ use std::fmt::Debug; -use symphonia::core::audio::{AudioBuffer, AudioBufferRef, Signal, AsAudioBufferRef, SignalSpec}; +use symphonia::core::audio::{AsAudioBufferRef, AudioBuffer, AudioBufferRef, Signal, SignalSpec}; #[derive(Clone)] pub struct MusicProcessor { @@ -10,7 +10,9 @@ pub struct MusicProcessor { impl Debug for MusicProcessor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("MusicProcessor").field("audio_volume", &self.audio_volume).finish() + f.debug_struct("MusicProcessor") + .field("audio_volume", &self.audio_volume) + .finish() } } @@ -22,22 +24,22 @@ impl MusicProcessor { audio_volume: 1.0, } } - + /// Processes audio samples - /// + /// /// Currently only supports transformations of volume pub fn process(&mut self, audio_buffer_ref: &AudioBufferRef) -> AudioBufferRef { audio_buffer_ref.convert(&mut self.audio_buffer); - + let process = |sample| sample * self.audio_volume; - + self.audio_buffer.transform(process); - + return self.audio_buffer.as_audio_buffer_ref(); } - + /// Sets buffer of the MusicProcessor pub fn set_buffer(&mut self, duration: u64, spec: SignalSpec) { self.audio_buffer = AudioBuffer::new(duration, spec); } -} \ No newline at end of file +} diff --git a/src/music_storage/music_db.rs b/src/music_storage/music_db.rs index e59f51c..b6457d3 100644 --- a/src/music_storage/music_db.rs +++ b/src/music_storage/music_db.rs @@ -362,7 +362,7 @@ impl MusicLibrary { Ok(_) => { //println!("{:?}", target_file.path()); total += 1 - }, + } Err(_error) => { //println!("{}, {:?}: {}", format, target_file.file_name(), _error) } // TODO: Handle more of these errors @@ -481,8 +481,14 @@ impl MusicLibrary { let cue_data = CD::parse_file(cuesheet.to_owned()).unwrap(); // Get album level information - let album_title = &cue_data.get_cdtext().read(cue::cd_text::PTI::Title).unwrap_or(String::new()); - let album_artist = &cue_data.get_cdtext().read(cue::cd_text::PTI::Performer).unwrap_or(String::new()); + let album_title = &cue_data + .get_cdtext() + .read(cue::cd_text::PTI::Title) + .unwrap_or(String::new()); + let album_artist = &cue_data + .get_cdtext() + .read(cue::cd_text::PTI::Performer) + .unwrap_or(String::new()); let parent_dir = cuesheet.parent().expect("The file has no parent path??"); for (i, track) in cue_data.tracks().iter().enumerate() { @@ -498,17 +504,17 @@ impl MusicLibrary { // Get the track timing information let pregap = match track.get_zero_pre() { Some(pregap) => Duration::from_micros((pregap as f32 * 13333.333333) as u64), - None => Duration::from_secs(0) + None => Duration::from_secs(0), }; let postgap = match track.get_zero_post() { Some(postgap) => Duration::from_micros((postgap as f32 * 13333.333333) as u64), - None => Duration::from_secs(0) + None => Duration::from_secs(0), }; let mut start = Duration::from_micros((track.get_start() as f32 * 13333.333333) as u64); start -= pregap; let duration = match track.get_length() { - Some(len) => Duration::from_micros((len as f32 * 13333.333333) as u64), + Some(len) => Duration::from_micros((len as f32 * 13333.333333) as u64), None => { let tagged_file = match lofty::read_from_path(&audio_location) { Ok(tagged_file) => tagged_file, @@ -537,33 +543,31 @@ impl MusicLibrary { tags.push((Tag::Key("AlbumArtist".to_string()), album_artist.clone())); match track.get_cdtext().read(cue::cd_text::PTI::Title) { Some(title) => tags.push((Tag::Title, title)), - None => { - match track.get_cdtext().read(cue::cd_text::PTI::UPC_ISRC) { - Some(title) => tags.push((Tag::Title, title)), - None => { - let namestr = format!("{} - {}", i, track.get_filename()); - tags.push((Tag::Title, namestr)) - } + None => match track.get_cdtext().read(cue::cd_text::PTI::UPC_ISRC) { + Some(title) => tags.push((Tag::Title, title)), + None => { + let namestr = format!("{} - {}", i, track.get_filename()); + tags.push((Tag::Title, namestr)) } - } + }, }; match track.get_cdtext().read(cue::cd_text::PTI::Performer) { Some(artist) => tags.push((Tag::Artist, artist)), - None => () + None => (), }; match track.get_cdtext().read(cue::cd_text::PTI::Genre) { Some(genre) => tags.push((Tag::Genre, genre)), - None => () + None => (), }; match track.get_cdtext().read(cue::cd_text::PTI::Message) { Some(comment) => tags.push((Tag::Comment, comment)), - None => () + None => (), }; let album_art = Vec::new(); let new_song = Song { - location: URI::Cue{ + location: URI::Cue { location: audio_location, start, end, @@ -586,8 +590,8 @@ impl MusicLibrary { Ok(_) => tracks_added += 1, Err(_error) => { //println!("{}", _error); - continue - }, + continue; + } }; } @@ -604,8 +608,8 @@ impl MusicLibrary { match new_song.location { URI::Local(_) if self.query_path(&new_song.location.path()).is_some() => { return Err(format!("Location exists for {:?}", new_song.location).into()) - }, - _ => () + } + _ => (), } self.library.push(new_song); @@ -652,7 +656,7 @@ impl MusicLibrary { for tag in target_tags { let track_result = match track.get_tag(&tag) { Some(value) => value, - None => continue + None => continue, }; if normalize(track_result).contains(&normalize(&query_string)) { diff --git a/src/music_storage/playlist.rs b/src/music_storage/playlist.rs index e61b405..797d0d2 100644 --- a/src/music_storage/playlist.rs +++ b/src/music_storage/playlist.rs @@ -1,10 +1,6 @@ -use std::path::Path; use crate::music_controller::config::Config; +use std::path::Path; -pub fn playlist_add( - config: &Config, - playlist_name: &str, - song_paths: &Vec<&Path> -) { +pub fn playlist_add(config: &Config, playlist_name: &str, song_paths: &Vec<&Path>) { unimplemented!() } diff --git a/src/music_tracker/music_tracker.rs b/src/music_tracker/music_tracker.rs index 12e57e8..9ffd73d 100644 --- a/src/music_tracker/music_tracker.rs +++ b/src/music_tracker/music_tracker.rs @@ -1,11 +1,11 @@ -use std::time::{SystemTime, UNIX_EPOCH}; -use std::collections::BTreeMap; use serde_json::json; +use std::collections::BTreeMap; +use std::time::{SystemTime, UNIX_EPOCH}; use async_trait::async_trait; -use serde::{Serialize, Deserialize}; -use md5::{Md5, Digest}; -use discord_presence::{Event}; +use discord_presence::Event; +use md5::{Digest, Md5}; +use serde::{Deserialize, Serialize}; use surf::StatusCode; use crate::music_storage::music_db::{Song, Tag}; @@ -14,13 +14,13 @@ use crate::music_storage::music_db::{Song, Tag}; pub trait MusicTracker { /// Adds one listen to a song halfway through playback async fn track_song(&mut self, song: Song) -> Result<(), TrackerError>; - + /// Adds a 'listening' status to the music tracker service of choice async fn track_now(&mut self, song: Song) -> Result<(), TrackerError>; - + /// Reads config files, and attempts authentication with service async fn test_tracker(&mut self) -> Result<(), TrackerError>; - + /// Returns plays for a given song according to tracker service async fn get_times_tracked(&mut self, song: &Song) -> Result; } @@ -50,7 +50,7 @@ impl TrackerError { StatusCode::ServiceUnavailable => TrackerError::ServiceUnavailable, StatusCode::NotFound => TrackerError::ServiceUnavailable, _ => TrackerError::Unknown, - } + }; } } @@ -63,62 +63,66 @@ pub struct LastFMConfig { } pub struct LastFM { - config: LastFMConfig + config: LastFMConfig, } #[async_trait] impl MusicTracker for LastFM { async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> { let mut params: BTreeMap<&str, &str> = BTreeMap::new(); - + // Sets timestamp of song beginning play time - let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30; + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Your time is off.") + .as_secs() + - 30; let string_timestamp = timestamp.to_string(); - + let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) { (Some(artist), Some(track)) => (artist, track), - _ => return Err(TrackerError::InvalidSong) + _ => return Err(TrackerError::InvalidSong), }; - + params.insert("method", "track.scrobble"); params.insert("artist", &artist); params.insert("track", &track); params.insert("timestamp", &string_timestamp); - + return match self.api_request(params).await { Ok(_) => Ok(()), Err(err) => Err(TrackerError::from_surf_error(err)), - } + }; } - + async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> { let mut params: BTreeMap<&str, &str> = BTreeMap::new(); - + let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) { (Some(artist), Some(track)) => (artist, track), - _ => return Err(TrackerError::InvalidSong) + _ => return Err(TrackerError::InvalidSong), }; - + params.insert("method", "track.updateNowPlaying"); params.insert("artist", &artist); params.insert("track", &track); - + return match self.api_request(params).await { Ok(_) => Ok(()), Err(err) => Err(TrackerError::from_surf_error(err)), - } + }; } - + async fn test_tracker(&mut self) -> Result<(), TrackerError> { let mut params: BTreeMap<&str, &str> = BTreeMap::new(); params.insert("method", "chart.getTopArtists"); - + return match self.api_request(params).await { Ok(_) => Ok(()), Err(err) => Err(TrackerError::from_surf_error(err)), - } + }; } - + async fn get_times_tracked(&mut self, song: &Song) -> Result { todo!(); } @@ -126,7 +130,7 @@ impl MusicTracker for LastFM { #[derive(Deserialize, Serialize)] struct AuthToken { - token: String + token: String, } #[derive(Deserialize, Serialize, Debug)] @@ -138,72 +142,85 @@ struct SessionResponse { #[derive(Deserialize, Serialize, Debug)] struct Session { - session: SessionResponse + session: SessionResponse, } impl LastFM { /// Returns a url to be approved by the user along with the auth token pub async fn get_auth(api_key: &String) -> Result { let method = String::from("auth.gettoken"); - let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&format=json"); - + let api_request_url = format!( + "http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&format=json" + ); + let auth_token: AuthToken = surf::get(api_request_url).await?.body_json().await?; - - let auth_url = format!("http://www.last.fm/api/auth/?api_key={api_key}&token={}", auth_token.token); - + + let auth_url = format!( + "http://www.last.fm/api/auth/?api_key={api_key}&token={}", + auth_token.token + ); + return Ok(auth_url); } - + /// Returns a LastFM session key - pub async fn get_session_key(api_key: &String, shared_secret: &String, auth_token: &String) -> Result { + pub async fn get_session_key( + api_key: &String, + shared_secret: &String, + auth_token: &String, + ) -> Result { let method = String::from("auth.getSession"); // Creates api_sig as defined in last.fm documentation - let api_sig = format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}"); + let api_sig = + format!("api_key{api_key}methodauth.getSessiontoken{auth_token}{shared_secret}"); // Creates insecure MD5 hash for last.fm api sig let mut hasher = Md5::new(); hasher.update(api_sig); let hash_result = hasher.finalize(); let hex_string_hash = format!("{:#02x}", hash_result); - + let api_request_url = format!("http://ws.audioscrobbler.com/2.0/?method={method}&api_key={api_key}&token={auth_token}&api_sig={hex_string_hash}&format=json"); - + let response = surf::get(api_request_url).recv_string().await?; - + // Sets session key from received response let session_response: Session = serde_json::from_str(&response)?; return Ok(session_response.session.key); } - + /// Creates a new LastFM struct with a given config pub fn new(config: &LastFMConfig) -> LastFM { let last_fm = LastFM { - config: config.clone() + config: config.clone(), }; return last_fm; } - + // Creates an api request with the given parameters - pub async fn api_request(&self, mut params: BTreeMap<&str, &str>) -> Result { + pub async fn api_request( + &self, + mut params: BTreeMap<&str, &str>, + ) -> Result { params.insert("api_key", &self.config.dango_api_key); params.insert("sk", &self.config.session_key); - + // Creates and sets api call signature let api_sig = LastFM::request_sig(¶ms, &self.config.shared_secret); params.insert("api_sig", &api_sig); let mut string_params = String::from(""); - + // Creates method call string // Just iterate over values??? for key in params.keys() { let param_value = params.get(key).unwrap(); string_params.push_str(&format!("{key}={param_value}&")); } - + string_params.pop(); - + let url = "http://ws.audioscrobbler.com/2.0/"; - + let response = surf::post(url).body_string(string_params).await; return response; @@ -218,16 +235,16 @@ impl LastFM { sig_string.push_str(&format!("{key}{}", param_value.unwrap())); } sig_string.push_str(shared_secret); - + // Hashes signature using **INSECURE** MD5 (Required by last.fm api) let mut md5_hasher = Md5::new(); md5_hasher.update(sig_string); let hash_result = md5_hasher.finalize(); let hashed_sig = format!("{:#02x}", hash_result); - + return hashed_sig; } - + // Removes last.fm account from dango-music-player pub fn reset_account() { todo!(); @@ -237,13 +254,13 @@ impl LastFM { #[derive(Serialize, Deserialize, Clone, PartialEq, Eq)] pub struct DiscordRPCConfig { pub enabled: bool, - pub dango_client_id: u64, + pub dango_client_id: u64, pub dango_icon: String, } pub struct DiscordRPC { config: DiscordRPCConfig, - pub client: discord_presence::client::Client + pub client: discord_presence::client::Client, } impl DiscordRPC { @@ -256,7 +273,6 @@ impl DiscordRPC { } } - #[async_trait] impl MusicTracker for DiscordRPC { async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> { @@ -275,16 +291,19 @@ impl MusicTracker for DiscordRPC { } else { &unknown }; - + let _client_thread = self.client.start(); - + // Blocks thread execution until it has connected to local discord client let ready = self.client.block_until_event(Event::Ready); if ready.is_err() { return Err(TrackerError::ServiceUnavailable); } - - let start_time = std::time::SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_millis() as u64; + + let start_time = std::time::SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_millis() as u64; // Sets discord account activity to current playing song let send_activity = self.client.set_activity(|activity| { activity @@ -293,21 +312,21 @@ impl MusicTracker for DiscordRPC { .assets(|assets| assets.large_image(&self.config.dango_icon)) .timestamps(|time| time.start(start_time)) }); - + match send_activity { Ok(_) => return Ok(()), Err(_) => return Err(TrackerError::ServiceUnavailable), } } - + async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> { - return Ok(()) + return Ok(()); } - + async fn test_tracker(&mut self) -> Result<(), TrackerError> { - return Ok(()) + return Ok(()); } - + async fn get_times_tracked(&mut self, song: &Song) -> Result { return Ok(0); } @@ -329,7 +348,7 @@ impl MusicTracker for ListenBrainz { async fn track_now(&mut self, song: Song) -> Result<(), TrackerError> { let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) { (Some(artist), Some(track)) => (artist, track), - _ => return Err(TrackerError::InvalidSong) + _ => return Err(TrackerError::InvalidSong), }; // Creates a json to submit a single song as defined in the listenbrainz documentation let json_req = json!({ @@ -343,21 +362,28 @@ impl MusicTracker for ListenBrainz { } ] }); - - return match self.api_request(&json_req.to_string(), &String::from("/1/submit-listens")).await { + + return match self + .api_request(&json_req.to_string(), &String::from("/1/submit-listens")) + .await + { Ok(_) => Ok(()), - Err(err) => Err(TrackerError::from_surf_error(err)) - } + Err(err) => Err(TrackerError::from_surf_error(err)), + }; } - + async fn track_song(&mut self, song: Song) -> Result<(), TrackerError> { - let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Your time is off.").as_secs() - 30; - + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Your time is off.") + .as_secs() + - 30; + let (artist, track) = match (song.get_tag(&Tag::Artist), song.get_tag(&Tag::Title)) { (Some(artist), Some(track)) => (artist, track), - _ => return Err(TrackerError::InvalidSong) + _ => return Err(TrackerError::InvalidSong), }; - + let json_req = json!({ "listen_type": "single", "payload": [ @@ -370,11 +396,14 @@ impl MusicTracker for ListenBrainz { } ] }); - - return match self.api_request(&json_req.to_string(), &String::from("/1/submit-listens")).await { + + return match self + .api_request(&json_req.to_string(), &String::from("/1/submit-listens")) + .await + { Ok(_) => Ok(()), - Err(err) => Err(TrackerError::from_surf_error(err)) - } + Err(err) => Err(TrackerError::from_surf_error(err)), + }; } async fn test_tracker(&mut self) -> Result<(), TrackerError> { todo!() @@ -387,12 +416,19 @@ impl MusicTracker for ListenBrainz { impl ListenBrainz { pub fn new(config: &ListenBrainzConfig) -> Self { ListenBrainz { - config: config.clone() + config: config.clone(), } } // Makes an api request to configured url with given json - pub async fn api_request(&self, request: &String, endpoint: &String) -> Result { - let reponse = surf::post(format!("{}{}", &self.config.api_url, endpoint)).body_string(request.clone()).header("Authorization", format!("Token {}", self.config.auth_token)).await; - return reponse + pub async fn api_request( + &self, + request: &String, + endpoint: &String, + ) -> Result { + let reponse = surf::post(format!("{}{}", &self.config.api_url, endpoint)) + .body_string(request.clone()) + .header("Authorization", format!("Token {}", self.config.auth_token)) + .await; + return reponse; } }