From 4aa581cf1fcc55f10a5760c05413cd16058cd481 Mon Sep 17 00:00:00 2001 From: Abdelilah El Aissaoui Date: Wed, 20 Sep 2023 15:50:15 +0200 Subject: [PATCH] Split rust code into different files to avoid having a single big file --- rs/src/lib.rs | 252 +------------------------------------- rs/src/ssh.rs | 136 ++++++++++++++++++++ rs/src/watch_directory.rs | 113 +++++++++++++++++ 3 files changed, 255 insertions(+), 246 deletions(-) create mode 100644 rs/src/ssh.rs create mode 100644 rs/src/watch_directory.rs diff --git a/rs/src/lib.rs b/rs/src/lib.rs index cacfd3f..8e2b96f 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -1,251 +1,11 @@ -extern crate notify; - -use std::fmt::Debug; -use std::io::{Write}; -use std::path::Path; -use std::sync::mpsc::{channel, RecvTimeoutError}; -use std::sync::{Arc, RwLock}; -use std::time::Duration; - -use libssh_rs::{AuthStatus, PollStatus, SshOption}; -use notify::{Config, Error, ErrorKind, Event, RecommendedWatcher, RecursiveMode, Watcher}; +mod ssh; +mod watch_directory; +use watch_directory::{ * }; +use ssh::{ * }; +#[allow(unused_imports)] +use libssh_rs::AuthStatus; uniffi::include_scaffolding!("gitnuro"); -const ACCEPTED_SSH_TYPES: &str = "ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss"; -fn watch_directory( - path: String, - notifier: Box, -) -> Result<(), WatcherInitError> { - // Create a channel to receive the events. - let (tx, rx) = channel(); - - // Create a watcher object, delivering debounced events. - // The notification back-end is selected based on the platform. - let config = Config::default(); - config.with_poll_interval(Duration::from_secs(3600)); - - let mut watcher = - RecommendedWatcher::new(tx, config).map_err(|err| err.kind.into_watcher_init_error())?; - - // Add a path to be watched. All files and directories at that path and - // below will be monitored for changes. - watcher - .watch(Path::new(path.as_str()), RecursiveMode::Recursive) - .map_err(|err| err.kind.into_watcher_init_error())?; - - while notifier.should_keep_looping() { - match rx.recv_timeout(Duration::from_secs(1)) { - Ok(e) => { - if let Some(paths) = get_paths_from_event_result(&e) { - notifier.detected_change(paths) - } - } - Err(e) => { - if e != RecvTimeoutError::Timeout { - println!("Watch error: {:?}", e) - } - } - } - } - - watcher - .unwatch(Path::new(path.as_str())) - .map_err(|err| err.kind.into_watcher_init_error())?; - - Ok(()) -} - -fn get_paths_from_event_result(event_result: &Result) -> Option> { - match event_result { - Ok(event) => { - let events: Vec = event - .paths - .clone() - .into_iter() - .filter_map(|path| path.into_os_string().into_string().ok()) - .collect(); - - if events.is_empty() { - None - } else { - Some(events) - } - } - Err(err) => { - println!("{:?}", err); - None - } - } -} - -pub trait WatchDirectoryNotifier: Send + Sync + Debug { - fn should_keep_looping(&self) -> bool; - fn detected_change(&self, paths: Vec); -} - -#[derive(Debug, thiserror::Error)] -pub enum WatcherInitError { - #[error("{error}")] - Generic { error: String }, - #[error("IO Error")] - Io { error: String }, - #[error("Path not found")] - PathNotFound, - #[error("Can not remove watch, it has not been found")] - WatchNotFound, - #[error("Invalid configuration")] - InvalidConfig, - #[error("Max files reached. Check the inotify limit")] - MaxFilesWatch, -} - -trait WatcherInitErrorConverter { - fn into_watcher_init_error(self) -> WatcherInitError; -} - -impl WatcherInitErrorConverter for ErrorKind { - fn into_watcher_init_error(self) -> WatcherInitError { - match self { - ErrorKind::Generic(err) => WatcherInitError::Generic { error: err }, - ErrorKind::Io(err) => WatcherInitError::Generic { - error: err.to_string(), - }, - ErrorKind::PathNotFound => WatcherInitError::PathNotFound, - ErrorKind::WatchNotFound => WatcherInitError::WatchNotFound, - ErrorKind::InvalidConfig(_) => WatcherInitError::InvalidConfig, - ErrorKind::MaxFilesWatch => WatcherInitError::MaxFilesWatch, - } - } -} - -struct Session { - pub session: RwLock, -} - -impl Session { - fn new() -> Self { - let session = libssh_rs::Session::new().unwrap(); - - Session { - session: RwLock::new(session) - } - } - - fn setup(&self, host: String, user: Option, port: Option) { - let session = self.session.write().unwrap(); - session.set_option(SshOption::Hostname(host)).unwrap(); - - if let Some(user) = user { - session.set_option(SshOption::User(Some(user))).unwrap(); - } - - if let Some(port) = port { - session.set_option(SshOption::Port(port)).unwrap(); - } - - session.set_option(SshOption::PublicKeyAcceptedTypes(ACCEPTED_SSH_TYPES.to_string())).unwrap(); - session.options_parse_config(None).unwrap(); - session.connect().unwrap(); - } - - fn public_key_auth(&self, password: String) -> AuthStatus { - println!("Public key auth"); - - let session = self.session.write().unwrap(); - - let status = session.userauth_public_key_auto(None, Some(&password)).unwrap(); - - println!("Status is {status:?}"); - - status - } - - fn password_auth(&self, password: String) -> AuthStatus { - let session = self.session.write().unwrap(); - session.userauth_password(None, Some(&password)).unwrap() - } - - fn disconnect(&self) { - let session = self.session.write().unwrap(); - session.disconnect() - } -} - - -struct Channel { - channel: RwLock, -} - -unsafe impl Send for Channel {} -unsafe impl Sync for Channel {} - -impl Channel { - fn new(session: Arc) -> Self { - let session = session.session.write().unwrap(); - let channel = session.new_channel().unwrap(); - - Channel { - channel: RwLock::new(channel) - } - } - fn open_session(&self) { - let channel = self.channel.write().unwrap(); - channel.open_session().unwrap(); - } - fn is_open(&self) -> bool { - let channel = self.channel.write().unwrap(); - channel.is_open() - } - - fn close(&self) { - let channel = self.channel.write().unwrap(); - channel.close().unwrap(); - } - - fn request_exec(&self, command: String) { - let channel = self.channel.write().unwrap(); - channel.request_exec(&command).unwrap(); - } - - fn poll_has_bytes(&self, is_stderr: bool) -> bool { - let channel = self.channel.write().unwrap(); - let poll_timeout = channel.poll_timeout(is_stderr, None).unwrap(); - - match poll_timeout { - PollStatus::AvailableBytes(count) => count > 0, - PollStatus::EndOfFile => false - } - } - - fn read(&self, is_stderr: bool, len: u64) -> ReadResult { - let ulen = len as usize; - - let channel = self.channel.write().unwrap(); - - let mut buffer = vec![0; ulen]; - let read = channel.read_timeout(&mut buffer, is_stderr, None).unwrap(); - - ReadResult { - read_count: read as u64, - data: buffer, - } - } - - fn write_byte(&self, byte: i32) { - let channel = self.channel.write().unwrap(); - channel.stdin().write_all(&byte.to_ne_bytes()).unwrap(); - } - - fn write_bytes(&self, data: Vec) { - let channel = self.channel.write().unwrap(); - channel.stdin().write_all(&data).unwrap(); - } -} - -pub struct ReadResult { - read_count: u64, - data: Vec, -} \ No newline at end of file diff --git a/rs/src/ssh.rs b/rs/src/ssh.rs new file mode 100644 index 0000000..e597482 --- /dev/null +++ b/rs/src/ssh.rs @@ -0,0 +1,136 @@ +use libssh_rs::{AuthStatus, PollStatus, SshOption}; +use std::sync::{Arc, RwLock}; +use std::io::{Write}; + +const ACCEPTED_SSH_TYPES: &str = "ssh-ed25519,ecdsa-sha2-nistp256,ecdsa-sha2-nistp384,ecdsa-sha2-nistp521,ssh-rsa,rsa-sha2-512,rsa-sha2-256,ssh-dss"; + +pub struct Session { + pub session: RwLock, +} + +impl Session { + pub fn new() -> Self { + let session = libssh_rs::Session::new().unwrap(); + + Session { + session: RwLock::new(session) + } + } + + pub fn setup(&self, host: String, user: Option, port: Option) { + let session = self.session.write().unwrap(); + session.set_option(SshOption::Hostname(host)).unwrap(); + + if let Some(user) = user { + session.set_option(SshOption::User(Some(user))).unwrap(); + } + + if let Some(port) = port { + session.set_option(SshOption::Port(port)).unwrap(); + } + + session.set_option(SshOption::PublicKeyAcceptedTypes(ACCEPTED_SSH_TYPES.to_string())).unwrap(); + session.options_parse_config(None).unwrap(); + session.connect().unwrap(); + } + + pub fn public_key_auth(&self, password: String) -> AuthStatus { + println!("Public key auth"); + + let session = self.session.write().unwrap(); + + let status = session.userauth_public_key_auto(None, Some(&password)).unwrap(); + + println!("Status is {status:?}"); + + status + } + + pub fn password_auth(&self, password: String) -> AuthStatus { + let session = self.session.write().unwrap(); + session.userauth_password(None, Some(&password)).unwrap() + } + + pub fn disconnect(&self) { + let session = self.session.write().unwrap(); + session.disconnect() + } +} + + +pub struct Channel { + channel: RwLock, +} + +unsafe impl Send for Channel {} +unsafe impl Sync for Channel {} + +impl Channel { + pub fn new(session: Arc) -> Self { + let session = session.session.write().unwrap(); + let channel = session.new_channel().unwrap(); + + Channel { + channel: RwLock::new(channel) + } + } + + pub fn open_session(&self) { + let channel = self.channel.write().unwrap(); + channel.open_session().unwrap(); + } + + pub fn is_open(&self) -> bool { + let channel = self.channel.write().unwrap(); + channel.is_open() + } + + pub fn close(&self) { + let channel = self.channel.write().unwrap(); + channel.close().unwrap(); + } + + pub fn request_exec(&self, command: String) { + let channel = self.channel.write().unwrap(); + channel.request_exec(&command).unwrap(); + } + + pub fn poll_has_bytes(&self, is_stderr: bool) -> bool { + let channel = self.channel.write().unwrap(); + let poll_timeout = channel.poll_timeout(is_stderr, None).unwrap(); + + match poll_timeout { + PollStatus::AvailableBytes(count) => count > 0, + PollStatus::EndOfFile => false + } + } + + pub fn read(&self, is_stderr: bool, len: u64) -> ReadResult { + let ulen = len as usize; + + let channel = self.channel.write().unwrap(); + + let mut buffer = vec![0; ulen]; + let read = channel.read_timeout(&mut buffer, is_stderr, None).unwrap(); + + ReadResult { + read_count: read as u64, + data: buffer, + } + } + + pub fn write_byte(&self, byte: i32) { + let channel = self.channel.write().unwrap(); + channel.stdin().write_all(&byte.to_ne_bytes()).unwrap(); + } + + pub fn write_bytes(&self, data: Vec) { + let channel = self.channel.write().unwrap(); + channel.stdin().write_all(&data).unwrap(); + } +} + +pub struct ReadResult { + pub read_count: u64, + pub data: Vec, +} \ No newline at end of file diff --git a/rs/src/watch_directory.rs b/rs/src/watch_directory.rs new file mode 100644 index 0000000..34b8452 --- /dev/null +++ b/rs/src/watch_directory.rs @@ -0,0 +1,113 @@ +extern crate notify; + +use std::fmt::Debug; +use std::path::Path; +use std::sync::mpsc::{channel, RecvTimeoutError}; +use std::time::Duration; +use notify::{Config, Error, ErrorKind, Event, RecommendedWatcher, RecursiveMode, Watcher}; + +pub fn watch_directory( + path: String, + notifier: Box, +) -> Result<(), WatcherInitError> { + // Create a channel to receive the events. + let (tx, rx) = channel(); + + // Create a watcher object, delivering debounced events. + // The notification back-end is selected based on the platform. + let config = Config::default(); + config.with_poll_interval(Duration::from_secs(3600)); + + let mut watcher = + RecommendedWatcher::new(tx, config).map_err(|err| err.kind.into_watcher_init_error())?; + + // Add a path to be watched. All files and directories at that path and + // below will be monitored for changes. + watcher + .watch(Path::new(path.as_str()), RecursiveMode::Recursive) + .map_err(|err| err.kind.into_watcher_init_error())?; + + while notifier.should_keep_looping() { + match rx.recv_timeout(Duration::from_secs(1)) { + Ok(e) => { + if let Some(paths) = get_paths_from_event_result(&e) { + notifier.detected_change(paths) + } + } + Err(e) => { + if e != RecvTimeoutError::Timeout { + println!("Watch error: {:?}", e) + } + } + } + } + + watcher + .unwatch(Path::new(path.as_str())) + .map_err(|err| err.kind.into_watcher_init_error())?; + + Ok(()) +} + +pub fn get_paths_from_event_result(event_result: &Result) -> Option> { + match event_result { + Ok(event) => { + let events: Vec = event + .paths + .clone() + .into_iter() + .filter_map(|path| path.into_os_string().into_string().ok()) + .collect(); + + if events.is_empty() { + None + } else { + Some(events) + } + } + Err(err) => { + println!("{:?}", err); + None + } + } +} + +pub trait WatchDirectoryNotifier: Send + Sync + Debug { + fn should_keep_looping(&self) -> bool; + fn detected_change(&self, paths: Vec); +} + +#[derive(Debug, thiserror::Error)] +pub enum WatcherInitError { + #[error("{error}")] + Generic { error: String }, + #[error("IO Error")] + Io { error: String }, + #[error("Path not found")] + PathNotFound, + #[error("Can not remove watch, it has not been found")] + WatchNotFound, + #[error("Invalid configuration")] + InvalidConfig, + #[error("Max files reached. Check the inotify limit")] + MaxFilesWatch, +} + +trait WatcherInitErrorConverter { + fn into_watcher_init_error(self) -> WatcherInitError; +} + +impl WatcherInitErrorConverter for ErrorKind { + fn into_watcher_init_error(self) -> WatcherInitError { + match self { + ErrorKind::Generic(err) => WatcherInitError::Generic { error: err }, + ErrorKind::Io(err) => WatcherInitError::Generic { + error: err.to_string(), + }, + ErrorKind::PathNotFound => WatcherInitError::PathNotFound, + ErrorKind::WatchNotFound => WatcherInitError::WatchNotFound, + ErrorKind::InvalidConfig(_) => WatcherInitError::InvalidConfig, + ErrorKind::MaxFilesWatch => WatcherInitError::MaxFilesWatch, + } + } +}