From 2292035b8d12bb3dc8327114c450da378d6e1447 Mon Sep 17 00:00:00 2001 From: aleidk Date: Thu, 18 Jul 2024 13:25:37 -0400 Subject: [PATCH] refactor(cli): move paths to subcommands --- proto/juno.proto | 15 +++++++---- src/configuration.rs | 31 ++++++++++++++--------- src/file_explorer.rs | 30 ++++++++++++++++++---- src/grpc/client.rs | 59 +++++++++++++++++++++++++++++++++++++------- src/grpc/server.rs | 59 ++++++++++++++++++++++++++++++++++---------- src/main.rs | 20 ++++++++++++--- src/player.rs | 52 ++++++++++++++++++++------------------ 7 files changed, 195 insertions(+), 71 deletions(-) diff --git a/proto/juno.proto b/proto/juno.proto index b81cb82..d2870d7 100644 --- a/proto/juno.proto +++ b/proto/juno.proto @@ -5,15 +5,21 @@ package juno; service JunoServices { rpc Ping (EmptyRequest) returns (PingResponse); rpc GetFiles (GetFilesRequest) returns (GetFilesResponse); - rpc SkipSong (EmptyRequest) returns (StatusResponse); + rpc SkipSong (EmptyRequest) returns (EmptyResponse); + rpc Play (EmptyRequest) returns (EmptyResponse); + rpc Pause (EmptyRequest) returns (EmptyResponse); + rpc PlayPause (EmptyRequest) returns (EmptyResponse); +} + +enum Status { + SUCCESS = 0; + ERROR = 1; } message EmptyRequest { } -// TODO: add an enmurator and a "message" so this act as a generic response to -// services that don't need to return valuable data -message StatusResponse { +message EmptyResponse { } message PingResponse { @@ -27,4 +33,3 @@ message GetFilesRequest { message GetFilesResponse { repeated string files = 1; } - diff --git a/src/configuration.rs b/src/configuration.rs index 118f2fb..852bcc2 100644 --- a/src/configuration.rs +++ b/src/configuration.rs @@ -19,13 +19,30 @@ pub enum ConfigMode { #[derive(Subcommand, Debug, Clone)] pub enum Commands { + /// Start the GRPC server Start { - #[arg(help = "Directory to scan for files")] - path: Option, + #[arg(help = "Directory to scan for files", default_value = ".")] + base_path: PathBuf, }, + /// Resume the playback Play, + /// Pause the playback + Pause, + /// Resume the playback if pause, pause if is playing + PlayPause, + /// Skip the current song SkipSong, Set, + /// List the available files + GetFiles { + #[arg( + help = "Directory to scan for files, relative to server base path", + default_value = "." + )] + path: PathBuf, + }, + /// Test server connection + Ping, } #[derive(Parser)] @@ -48,7 +65,6 @@ struct Args { #[derive(Debug)] pub struct Config { pub command: Commands, - pub base_path: PathBuf, pub address: SocketAddr, pub mode: ConfigMode, pub volume: f32, @@ -58,7 +74,6 @@ impl Default for Config { fn default() -> Self { Config { command: Commands::Play, - base_path: env::current_dir().expect("Current directory is not available."), mode: ConfigMode::Server, address: SocketAddr::from_str("[::1]:50051").unwrap(), volume: 1.0, @@ -72,13 +87,7 @@ impl Config { let mut config = Self::default(); config.address = SocketAddr::from_str(format!("[::1]:{}", cli.port).as_str()).unwrap(); config.volume = cli.volume; - config.command = cli.cmd.to_owned(); - - if let Commands::Start { path } = cli.cmd { - if let Some(path) = path { - config.base_path = path; - } - } + config.command = cli.cmd; if grpc::is_socket_in_use(config.address) { config.mode = ConfigMode::Client; diff --git a/src/file_explorer.rs b/src/file_explorer.rs index 13cb57b..5913a42 100644 --- a/src/file_explorer.rs +++ b/src/file_explorer.rs @@ -1,10 +1,11 @@ use ignore::types::TypesBuilder; use ignore::WalkBuilder; +use std::env; use std::path::PathBuf; -use crate::configuration::CONFIG; +use crate::configuration::{Commands, CONFIG}; -pub fn walk_dir(path: &PathBuf) -> Result, &str> { +pub fn walk_dir(scan_dir: Option<&PathBuf>) -> Result, &str> { let mut types_builder = TypesBuilder::new(); types_builder.add_defaults(); @@ -16,12 +17,31 @@ pub fn walk_dir(path: &PathBuf) -> Result, &str> { types_builder.select("sound"); - let search_path = CONFIG.base_path.join(path); + let mut base_path = env::current_dir().expect("Error accesing the enviroment"); + + if let Commands::Start { + base_path: config_path, + } = &CONFIG.command + { + base_path = config_path.to_owned(); + }; + + let search_path; + + match scan_dir { + Some(dir) => { + search_path = base_path + .join(dir) + .canonicalize() + .expect("Couldn't canonicalizice the path") + } + None => search_path = base_path.to_owned(), + } // PathBuf.join() can override the hole path, this ensure we're not accessing files outside // base_dir - if !search_path.starts_with(&CONFIG.base_path) { - return Err("Tried to access file or directory outside of server `base_dir` config."); + if !search_path.starts_with(base_path) { + return Err("Tried to access file or directory outside of server `base_path` config."); } let entries: Vec = WalkBuilder::new(search_path) diff --git a/src/grpc/client.rs b/src/grpc/client.rs index 9523785..3778f58 100644 --- a/src/grpc/client.rs +++ b/src/grpc/client.rs @@ -1,11 +1,12 @@ -use crate::configuration::CONFIG; +use core::panic; + +use crate::configuration::{Commands, CONFIG}; use crate::grpc::grpc_juno::EmptyRequest; use super::grpc_juno; use grpc_juno::juno_services_client::JunoServicesClient; use grpc_juno::GetFilesRequest; -use tonic::async_trait; use tonic::transport::Channel; use tonic::Request; @@ -32,6 +33,42 @@ impl GRPCClient { Ok(()) } + pub async fn play(&self) -> Result<(), Box> { + let mut client = self.get_client().await?; + + let request = Request::new(EmptyRequest {}); + + let response = client.play(request).await?.into_inner(); + + println!("RESPONSE={:?}", response); + + Ok(()) + } + + pub async fn pause(&self) -> Result<(), Box> { + let mut client = self.get_client().await?; + + let request = Request::new(EmptyRequest {}); + + let response = client.pause(request).await?.into_inner(); + + println!("RESPONSE={:?}", response); + + Ok(()) + } + + pub async fn play_pause(&self) -> Result<(), Box> { + let mut client = self.get_client().await?; + + let request = Request::new(EmptyRequest {}); + + let response = client.play_pause(request).await?.into_inner(); + + println!("RESPONSE={:?}", response); + + Ok(()) + } + pub async fn skip_song(&self) -> Result<(), Box> { let mut client = self.get_client().await?; @@ -44,17 +81,21 @@ impl GRPCClient { Ok(()) } - pub async fn list_files(&self) -> Result<(), Box> { + pub async fn get_files(&self) -> Result<(), Box> { let mut client = self.get_client().await?; - let request = Request::new(GetFilesRequest { - path: CONFIG.base_path.display().to_string(), - }); + if let Commands::GetFiles { path } = &CONFIG.command { + let request = Request::new(GetFilesRequest { + path: path.display().to_string(), + }); - let response = client.get_files(request).await?.into_inner(); + let response = client.get_files(request).await?.into_inner(); - println!("RESPONSE={:?}", response.files); + println!("RESPONSE={:?}", response.files); - Ok(()) + return Ok(()); + }; + + panic!("Error"); } } diff --git a/src/grpc/server.rs b/src/grpc/server.rs index c80641c..631db4c 100644 --- a/src/grpc/server.rs +++ b/src/grpc/server.rs @@ -1,29 +1,29 @@ -use crate::configuration::CONFIG; -use crate::{file_explorer, PlayerAction}; +use crate::configuration::{Commands, CONFIG}; +use crate::file_explorer; use super::grpc_juno; use grpc_juno::juno_services_server::{JunoServices, JunoServicesServer}; -use grpc_juno::{EmptyRequest, GetFilesRequest, GetFilesResponse, PingResponse, StatusResponse}; +use grpc_juno::{EmptyRequest, EmptyResponse, GetFilesRequest, GetFilesResponse, PingResponse}; use std::error::Error; use std::path::PathBuf; use std::str::FromStr; use tokio::sync::mpsc::Sender; use tonic::transport::Server; -use tonic::{async_trait, Request, Response, Result, Status}; +use tonic::{Request, Response, Result, Status}; #[derive(Debug, Default)] pub struct GRPCServer { - transmitter: Option>, + transmitter: Option>, } impl GRPCServer { - pub fn new(tx: Sender) -> Self { + pub fn new(tx: Sender) -> Self { Self { transmitter: Some(tx), } } - async fn send_message(&self, message: PlayerAction) -> Result<(), Box> { + async fn send_message(&self, message: Commands) -> Result<(), Box> { if let Some(tx) = &self.transmitter { tx.send(message).await?; } @@ -31,7 +31,7 @@ impl GRPCServer { Ok(()) } - pub async fn serve(tx: Sender) -> Result<(), Box> { + pub async fn serve(tx: Sender) -> Result<(), Box> { println!("Starting server on: \"{}\"", CONFIG.address.to_string()); Server::builder() @@ -63,7 +63,7 @@ impl JunoServices for GRPCServer { let path = PathBuf::from_str(request.into_inner().path.as_str()) .expect("Failed to create pathbuf"); - let files = match file_explorer::walk_dir(&path) { + let files = match file_explorer::walk_dir(Some(&path)) { Ok(files) => files, Err(err) => return Err(Status::invalid_argument(err)), }; @@ -75,14 +75,47 @@ impl JunoServices for GRPCServer { Ok(Response::new(reply)) } - async fn skip_song( + async fn play( &self, _request: Request, - ) -> Result, Status> { - if let Err(_err) = self.send_message(PlayerAction::SkipSong).await { + ) -> Result, Status> { + if let Err(_err) = self.send_message(Commands::Play).await { return Err(Status::internal("An internal error has occurred.")); } - Ok(Response::new(StatusResponse {})) + Ok(Response::new(EmptyResponse {})) + } + + async fn pause( + &self, + _request: Request, + ) -> Result, Status> { + if let Err(_err) = self.send_message(Commands::Pause).await { + return Err(Status::internal("An internal error has occurred.")); + } + + Ok(Response::new(EmptyResponse {})) + } + + async fn play_pause( + &self, + _request: Request, + ) -> Result, Status> { + if let Err(_err) = self.send_message(Commands::PlayPause).await { + return Err(Status::internal("An internal error has occurred.")); + } + + Ok(Response::new(EmptyResponse {})) + } + + async fn skip_song( + &self, + _request: Request, + ) -> Result, Status> { + if let Err(_err) = self.send_message(Commands::SkipSong).await { + return Err(Status::internal("An internal error has occurred.")); + } + + Ok(Response::new(EmptyResponse {})) } } diff --git a/src/main.rs b/src/main.rs index e68622e..96362b0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,7 @@ use tokio::sync::mpsc; use crate::player::Player; -use self::configuration::{ConfigMode, CONFIG}; -use self::player::PlayerAction; +use self::configuration::{Commands, ConfigMode, CONFIG}; mod configuration; mod file_explorer; @@ -15,7 +14,7 @@ mod grpc; mod player; async fn init_server() -> Result<(), Box> { - let (tx, mut rx) = mpsc::channel::(32); + let (tx, mut rx) = mpsc::channel::(32); tokio::spawn(async move { let _ = grpc::GRPCServer::serve(tx).await; @@ -52,7 +51,20 @@ async fn init_server() -> Result<(), Box> { async fn init_client() -> Result<(), Box> { let client = grpc::GRPCClient::default(); - let _ = client.skip_song().await; + + match &CONFIG.command { + Commands::Play => client.play().await?, + Commands::Pause => client.pause().await?, + Commands::PlayPause => client.play_pause().await?, + Commands::SkipSong => client.skip_song().await?, + Commands::Set => todo!(), + Commands::GetFiles { path: _ } => client.get_files().await?, + Commands::Ping => client.ping().await?, + _ => { + println!("This command doesn't apply to client mode") + } + } + Ok(()) } diff --git a/src/player.rs b/src/player.rs index 3fe7363..557a660 100644 --- a/src/player.rs +++ b/src/player.rs @@ -6,16 +6,9 @@ use std::path::PathBuf; use rodio::{OutputStream, Sink}; -use crate::configuration::CONFIG; +use crate::configuration::{self, CONFIG}; use crate::file_explorer::walk_dir; -#[derive(Debug)] -pub enum PlayerAction { - Play, - SkipSong, - Set, -} - pub struct Player { queue: VecDeque, sink: Sink, @@ -32,10 +25,11 @@ impl std::ops::Deref for Player { impl Player { pub fn new() -> Result> { - let queue = walk_dir(&CONFIG.base_path)?; + let queue = walk_dir(None)?; let (stream, stream_handle) = OutputStream::try_default()?; let sink = Sink::try_new(&stream_handle)?; sink.set_volume(CONFIG.volume); + Ok(Player { queue: VecDeque::from(queue), sink, @@ -43,12 +37,20 @@ impl Player { }) } - pub fn handle_message(&mut self, message: PlayerAction) -> Result<(), Box> { + pub fn handle_message( + &mut self, + message: configuration::Commands, + ) -> Result<(), Box> { match message { - PlayerAction::Play => self.play()?, - PlayerAction::SkipSong => self.skip_song()?, - PlayerAction::Set => unimplemented!(), - } + configuration::Commands::Play => self.play(), + configuration::Commands::Pause => self.pause(), + configuration::Commands::PlayPause => self.play_pause(), + configuration::Commands::SkipSong => self.skip_song()?, + configuration::Commands::Set => todo!(), + _ => { + println!("This command doesn't apply to client mode") + } + }; Ok(()) } @@ -76,10 +78,20 @@ impl Player { Ok(()) } - fn play(&mut self) -> Result<(), Box> { + fn play(&mut self) { self.sink.play(); + } - Ok(()) + fn pause(&mut self) { + self.sink.pause(); + } + + fn play_pause(&self) { + if self.sink.is_paused() { + self.sink.play(); + } else { + self.sink.pause(); + }; } fn skip_song(&mut self) -> Result<(), Box> { @@ -98,12 +110,4 @@ impl Player { self.sink.append(rodio::Decoder::new(BufReader::new(file))?); Ok(()) } - - fn _play_pause(&self) { - if self.sink.is_paused() { - self.sink.play(); - } else { - self.sink.pause(); - }; - } }