diff --git a/Cargo.toml b/Cargo.toml index be46c38..b8a5deb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,10 @@ edition = "2021" [dependencies] clap = { version = "4.5.4", features = ["derive"] } ignore = "0.4.22" +lazy_static = "1.4.0" +prost = "0.12.4" +tokio = { version = "1", features = ["full"] } +tonic = "0.11.0" + +[build-dependencies] +tonic-build = "0.11.0" diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..db9dbf8 --- /dev/null +++ b/build.rs @@ -0,0 +1,5 @@ +fn main() -> Result<(), Box> { + tonic_build::compile_protos("proto/juno.proto")?; + + Ok(()) +} diff --git a/proto/juno.proto b/proto/juno.proto new file mode 100644 index 0000000..aad85d6 --- /dev/null +++ b/proto/juno.proto @@ -0,0 +1,23 @@ +syntax = "proto3"; + +package juno; + +service JunoRequest { + rpc Ping (PingRequestMessage) returns (PingResponseMessage); + rpc GetFiles (GetFilesRequest) returns (GetFilesResponse); +} + +message PingRequestMessage { +} + +message PingResponseMessage { + string message = 1; +} + +message GetFilesRequest { + string path = 1; +} + +message GetFilesResponse { + repeated string files = 1; +} diff --git a/src/configuration.rs b/src/configuration.rs new file mode 100644 index 0000000..73bcf2b --- /dev/null +++ b/src/configuration.rs @@ -0,0 +1,46 @@ +use clap::Parser; +use lazy_static::lazy_static; +use std::env; +use std::path::PathBuf; + +lazy_static! { + pub static ref CONFIG: Config = Config::new(); +} + +#[derive(Parser)] +#[command(version, about, long_about = None)] +struct Args { + #[arg(help = "Directory to scan for files")] + path: Option, +} + +#[derive(Debug)] +pub struct Config { + pub base_path: PathBuf, +} + +impl Default for Config { + fn default() -> Self { + Config { + base_path: env::current_dir().expect("Current directory is not available."), + } + } +} + +impl Config { + pub fn new() -> Self { + let mut config = Self::default(); + + let cli = Self::get_cli_args(); + + if let Some(path) = cli.path { + config.base_path = path; + } + + config + } + + fn get_cli_args() -> Args { + Args::parse() + } +} diff --git a/src/file_explorer.rs b/src/file_explorer.rs index f394d7c..e932592 100644 --- a/src/file_explorer.rs +++ b/src/file_explorer.rs @@ -1,8 +1,10 @@ use ignore::types::TypesBuilder; use ignore::WalkBuilder; -use std::{io, path::PathBuf}; +use std::path::PathBuf; -pub fn walk_dir(path: &PathBuf) -> io::Result> { +use crate::configuration::CONFIG; + +pub fn walk_dir(path: &PathBuf) -> Result, &str> { let mut types_builder = TypesBuilder::new(); types_builder.add_defaults(); @@ -14,7 +16,19 @@ pub fn walk_dir(path: &PathBuf) -> io::Result> { types_builder.select("sound"); - let entries: Vec = WalkBuilder::new(path) + let search_path = CONFIG.base_path.join(path); + eprintln!( + "DEBUGPRINT[1]: file_explorer.rs:19: search_path={:#?}", + search_path + ); + + // 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."); + } + + let entries: Vec = WalkBuilder::new(search_path) .types(types_builder.build().unwrap()) .build() .filter_map(|entry| entry.ok()) diff --git a/src/grpc.rs b/src/grpc.rs new file mode 100644 index 0000000..77e1b60 --- /dev/null +++ b/src/grpc.rs @@ -0,0 +1,37 @@ +use std::error::Error; +use std::net::{SocketAddr, TcpListener}; + +use tonic::async_trait; + +use self::client::GRPCClient; +use self::server::GRPCServer; + +mod client; +mod server; + +pub mod grpc_juno { + tonic::include_proto!("juno"); +} + +#[async_trait] +pub trait Connection { + async fn connect(&self) -> Result<(), Box>; +} + +fn is_socket_in_use(addr: String) -> bool { + let socket: SocketAddr = addr.parse().expect("Failed to create socket"); + match TcpListener::bind(socket) { + Ok(_) => true, + Err(_) => false, + } +} + +pub fn run() -> Result, Box> { + let addr = "[::1]:50051"; + + if is_socket_in_use(addr.to_string()) { + Ok(Box::new(GRPCServer::new(addr.to_string()))) + } else { + Ok(Box::new(GRPCClient::new(addr.to_string()))) + } +} diff --git a/src/grpc/client.rs b/src/grpc/client.rs new file mode 100644 index 0000000..b5026b1 --- /dev/null +++ b/src/grpc/client.rs @@ -0,0 +1,34 @@ +use super::grpc_juno; + +use grpc_juno::juno_request_client::JunoRequestClient; +use grpc_juno::GetFilesRequest; +use tonic::async_trait; +use tonic::Request; + +#[derive(Debug, Default)] +pub struct GRPCClient { + address: String, +} + +impl GRPCClient { + pub fn new(address: String) -> Self { + Self { address } + } +} + +#[async_trait] +impl super::Connection for GRPCClient { + async fn connect(&self) -> Result<(), Box> { + let mut client = JunoRequestClient::connect(format!("http://{}", self.address)).await?; + + let request = Request::new(GetFilesRequest { + path: "/home/aleidk/Documents/".to_string(), + }); + + let response = client.get_files(request).await?.into_inner(); + + println!("RESPONSE={:?}", response.files); + + Ok(()) + } +} diff --git a/src/grpc/server.rs b/src/grpc/server.rs new file mode 100644 index 0000000..9b1363b --- /dev/null +++ b/src/grpc/server.rs @@ -0,0 +1,71 @@ +use crate::file_explorer; + +use super::grpc_juno; +use grpc_juno::juno_request_server::{JunoRequest, JunoRequestServer}; +use grpc_juno::{GetFilesRequest, GetFilesResponse, PingRequestMessage, PingResponseMessage}; +use std::error::Error; +use std::net::SocketAddr; +use std::path::PathBuf; +use std::str::FromStr; +use tonic::transport::Server; +use tonic::{async_trait, Request, Response, Result, Status}; + +#[derive(Debug, Default)] +pub struct GRPCServer { + address: String, +} + +impl GRPCServer { + pub fn new(address: String) -> Self { + Self { address } + } +} + +#[tonic::async_trait] +impl JunoRequest for GRPCServer { + async fn ping( + &self, + _request: Request, + ) -> Result, Status> { + let reply = PingResponseMessage { + message: "pong!".to_string(), + }; + + Ok(Response::new(reply)) + } + + async fn get_files( + &self, + request: Request, + ) -> Result, Status> { + let path = PathBuf::from_str(request.into_inner().path.as_str()) + .expect("Failed to create pathbuf"); + + let files = match file_explorer::walk_dir(&path) { + Ok(files) => files, + Err(err) => return Err(Status::invalid_argument(err)), + }; + + let reply = GetFilesResponse { + files: files.iter().map(|x| x.display().to_string()).collect(), + }; + + Ok(Response::new(reply)) + } +} + +#[async_trait] +impl super::Connection for GRPCServer { + async fn connect(&self) -> Result<(), Box> { + println!("Starting server on: \"{}\"", self.address); + + let socket: SocketAddr = self.address.parse()?; + + Server::builder() + .add_service(JunoRequestServer::new(GRPCServer::default())) + .serve(socket) + .await?; + + Ok(()) + } +} diff --git a/src/main.rs b/src/main.rs index 749161a..dee7a89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,14 @@ -use std::{env, path::PathBuf}; - -use clap::Parser; +use std::error::Error; +mod configuration; mod file_explorer; +mod grpc; -#[derive(Parser)] -#[command(version, about, long_about = None)] -struct Args { - #[arg(help = "Directory to scan for files")] - path: Option, -} - -fn main() { - let cli = Args::parse(); - let path = cli - .path - .unwrap_or(env::current_dir().expect("Current directory is not available.")); - - let files = file_explorer::walk_dir(&path).expect("error"); - - eprintln!("DEBUGPRINT[4]: main.rs:20: files={:#?}", files); +#[tokio::main()] +async fn main() -> Result<(), Box> { + let server = grpc::run()?; + + server.connect().await?; + + Ok(()) }