diff --git a/README.md b/README.md index ccbb17e..c09ffeb 100644 --- a/README.md +++ b/README.md @@ -164,3 +164,9 @@ Example for windows (you may want to edit `C:\Program Files\Git\etc\gitconfig`): [credential] helper = C:/Program Files/Git/mingw64/bin/git-credential-manager-core.exe ``` + + +## Sponsors + +Thank you to all the sponsors for helping improve Gitnuro and JetBrains for providing the necessary tooling. + diff --git a/build.gradle.kts b/build.gradle.kts index 84457db..75c6722 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,6 +1,5 @@ import org.gradle.jvm.tasks.Jar import org.jetbrains.compose.compose -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.FileOutputStream import java.nio.file.Files @@ -172,11 +171,11 @@ task("fatJarLinux", type = Jar::class) { with(tasks.jar.get() as CopySpec) } - -task("rust_generateKotlinFromUdl") { - println("Generate Kotlin") - generateKotlinFromUdl() -} +// +//task("rust_generateKotlinFromUdl") { +// println("Generate Kotlin") +// generateKotlinFromUdl() +//} task("rust_build") { buildRust() @@ -186,14 +185,14 @@ tasks.getByName("compileKotlin").doLast { println("compileKotlin called") buildRust() copyRustBuild() - generateKotlinFromUdl() + generateKotlinFromRs() } tasks.getByName("compileTestKotlin").doLast { println("compileTestKotlin called") buildRust() copyRustBuild() - generateKotlinFromUdl() + generateKotlinFromRs() } @@ -207,23 +206,15 @@ task("tasksList") { task("rustTasks") { buildRust() copyRustBuild() - generateKotlinFromUdl() +// generateKotlinFromUdl() } task("rust_copyBuild") { copyRustBuild() } -fun generateKotlinFromUdl() { - exec { - workingDir = File(project.projectDir, "rs") - commandLine = listOf( - "cargo", "run", "--features=uniffi/cli", - "--bin", "uniffi-bindgen", "generate", "src/gitnuro.udl", - "--language", "kotlin", - "--out-dir", rustGeneratedSource - ) - } +fun generateKotlinFromRs() { + } fun buildRust() { @@ -236,7 +227,7 @@ fun buildRust() { } val params = mutableListOf( - binary, "build", "--release", "--features=uniffi/cli", + binary, "build", "--release", ) if (currentOs() == OS.LINUX && useCross) { @@ -270,23 +261,16 @@ fun copyRustBuild() { val directory = File(outputDir) directory.mkdirs() - val originLib = when (currentOs()) { + val lib = when (currentOs()) { OS.LINUX -> "libgitnuro_rs.so" OS.WINDOWS -> "gitnuro_rs.dll" OS.MAC -> "libgitnuro_rs.dylib" } - val destinyLib = when (currentOs()) { - OS.LINUX -> "libuniffi_gitnuro.so" - OS.WINDOWS -> "uniffi_gitnuro.dll" - OS.MAC -> "libuniffi_gitnuro.dylib" - } - - val originFile = File(workingDir, originLib) - val destinyFile = File(directory, destinyLib) + val originFile = File(workingDir, lib) + val destinyFile = File(directory, lib) Files.copy(originFile.toPath(), FileOutputStream(destinyFile)) -// com.google.common.io.Files.copy(originFile, destinyFile) println("Copy rs build completed") } diff --git a/rs/.cargo/config.toml b/rs/.cargo/config.toml deleted file mode 100644 index 3c32d25..0000000 --- a/rs/.cargo/config.toml +++ /dev/null @@ -1,2 +0,0 @@ -[target.aarch64-unknown-linux-gnu] -linker = "aarch64-linux-gnu-gcc" diff --git a/rs/Cargo.toml b/rs/Cargo.toml index 53e28b0..dc03b9e 100644 --- a/rs/Cargo.toml +++ b/rs/Cargo.toml @@ -8,14 +8,8 @@ crate-type = ["cdylib"] name = "gitnuro_rs" [dependencies] -uniffi = { version = "0.27.1" } notify = "6.1.1" thiserror = "1.0.56" -libssh-rs = { version = "0.2.2", features = ["vendored", "vendored-openssl"] } - -[build-dependencies] -uniffi = { version = "0.27.1", features = ["build"] } - -[[bin]] -name = "uniffi-bindgen" -path = "uniffi-bindgen.rs" +libssh-rs = { version = "0.3.3", features = ["vendored", "vendored-openssl"] } +kotars = { path = "/home/abde/Projects/Rust/kotars/kotars" } #{ git = "https://github.com/JetpackDuba/kotars.git" } +jni = "0.21.1" diff --git a/rs/build.rs b/rs/build.rs deleted file mode 100644 index f5c593b..0000000 --- a/rs/build.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - uniffi::generate_scaffolding("src/gitnuro.udl").unwrap(); -} diff --git a/rs/src/gitnuro.udl b/rs/src/gitnuro.udl deleted file mode 100644 index 12ea4f5..0000000 --- a/rs/src/gitnuro.udl +++ /dev/null @@ -1,54 +0,0 @@ -namespace gitnuro { - [Throws=WatcherInitError] - void watch_directory(string path, string git_dir_path, WatchDirectoryNotifier checker); -}; - -callback interface WatchDirectoryNotifier { - boolean should_keep_looping(); - - void detected_change(sequence paths); -}; - -[Error] -interface WatcherInitError { - Generic(string error); - Io(string error); - PathNotFound(); - WatchNotFound(); - InvalidConfig(); - MaxFilesWatch(); -}; - -enum AuthStatus { - "Success", - "Denied", - "Partial", - "Info", - "Again", -}; - - -interface Session { - constructor(); - void setup(string host, string? user, u16? port); - AuthStatus public_key_auth(string password); - AuthStatus password_auth(string password); - void disconnect(); -}; - -interface Channel { - constructor(Session session); - void open_session(); - boolean is_open(); - void close_channel(); - void request_exec(string command); - boolean poll_has_bytes(boolean is_stderr); - ReadResult read(boolean is_stderr, u64 len); - void write_byte(i32 byte); - void write_bytes(bytes data); -}; - -dictionary ReadResult { - u64 read_count; - bytes data; -}; \ No newline at end of file diff --git a/rs/src/lib.rs b/rs/src/lib.rs index 4cb1470..921d2c1 100644 --- a/rs/src/lib.rs +++ b/rs/src/lib.rs @@ -1,10 +1,312 @@ -#[allow(unused_imports)] // Needed to map it to the enum in the UDL file +extern crate notify; + +use std::io::Write; +use std::path::Path; +use std::rc::Rc; +use std::sync::mpsc::{channel, RecvTimeoutError}; +use std::sync::RwLock; +use std::time::{Duration, SystemTime, UNIX_EPOCH}; + +use kotars::{jni_class, jni_data_class, jni_interface, jni_struct_impl}; +use kotars::jni_init; +use libssh_rs::{PollStatus, SshOption}; +#[allow(unused_imports)] use libssh_rs::AuthStatus; +use notify::{Config, Error, Event, RecommendedWatcher, RecursiveMode, Watcher}; -use ssh::{*}; -use watch_directory::{*}; +jni_init!(""); -mod ssh; -mod watch_directory; +#[jni_class] +struct FileWatcher {} -uniffi::include_scaffolding!("gitnuro"); +#[jni_data_class] +struct FileChanged { + path: String, +} + +impl From for FileChanged { + fn from(value: String) -> Self { + FileChanged { path: value } + } +} + +#[jni_struct_impl] +impl FileWatcher { + fn watch( + &self, + path: String, + git_dir_path: String, + notifier: &impl WatchDirectoryNotifier, + ) { + println!("Starting to watch directory {path}"); + watch_directory(path, git_dir_path, notifier); + } + + fn new() -> FileWatcher { + FileWatcher {} + } +} + + +const MIN_TIME_IN_MS_BETWEEN_REFRESHES: u128 = 500; +const WATCH_TIMEOUT: u64 = 500; + + +pub fn watch_directory( + path: String, + git_dir_path: String, + notifier: &impl WatchDirectoryNotifier, +) { + // 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).expect("Init watcher failed"); + + // 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) + .expect("Start watching failed"); + + let mut paths_cached: Vec = Vec::new(); + + let mut last_update: u128 = 0; + + while true { + match rx.recv_timeout(Duration::from_millis(WATCH_TIMEOUT)) { + Ok(e) => { + if let Some(paths) = get_paths_from_event_result(&e, &git_dir_path) { + let mut paths_without_dirs: Vec = paths + .into_iter() + .collect(); + + let first_path = paths_without_dirs.first(); + + if let Some(path) = first_path { + notifier.detected_change(path.clone().into()); + } + + + last_update = SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("We need a TARDIS to fix this") + .as_millis(); + + println!("Event: {e:?}"); + } + } + Err(e) => { + if e != RecvTimeoutError::Timeout { + println!("Watch error: {:?}", e); + } + } + } + } + + watcher + .unwatch(Path::new(path.as_str())) + .expect("Unwatch failed"); + + // Ok(()) +} + +pub fn get_paths_from_event_result(event_result: &Result, git_dir_path: &str) -> Option> { + match event_result { + Ok(event) => { + let events: Vec = event + .paths + .clone() + .into_iter() + .filter_map(|path| { + // Directories are not tracked by Git so we don't care about them (just about their content) + // We won't be able to check if it's a dir if it has been deleted but that's good enough + if path.is_dir() { + println!("Ignoring directory {path:#?}"); + None + } else { + let path_str = path.into_os_string() + .into_string() + .ok()?; + + // JGit may create .probe-UUID files for its internal stuff, we don't care about it + let probe_prefix = format!("{git_dir_path}.probe-"); + if path_str.starts_with(probe_prefix.as_str()) { + None + } else { + Some(path_str) + } + } + }) + .collect(); + + if events.is_empty() { + None + } else { + Some(events) + } + } + Err(err) => { + println!("{:?}", err); + None + } + } +} + +#[jni_interface] +pub trait WatchDirectoryNotifier { + // fn should_keep_looping(&self) -> bool; + fn detected_change(&self, path: FileChanged); +} + +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"; + +#[jni_class] +pub struct Session { + pub session: RwLock, +} + +#[jni_struct_impl] +impl Session { + pub fn new() -> Session { + let session = libssh_rs::Session::new().unwrap(); + + Session { + session: RwLock::new(session) + } + } + + pub fn setup(&self, host: String, user: String, port: Option) { + let session = self.session.write().unwrap(); + session.set_option(SshOption::Hostname(host)).unwrap(); + + if !user.is_empty() { + session.set_option(SshOption::User(Some(user))).unwrap(); + } + + if let Some(port) = port { + session.set_option(SshOption::Port(port as u16)).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) -> i32 { //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:?}"); + + to_int(status) // TODO remove this cast + } + // + pub fn password_auth(&self, password: String) -> i32 { //AuthStatus { + let session = self.session.write().unwrap(); + let status = session.userauth_password(None, Some(&password)).unwrap(); + to_int(status) // TODO remove this cast + } + + pub fn disconnect(&self) { + let session = self.session.write().unwrap(); + session.disconnect() + } +} + +#[jni_class] +pub struct Channel { + channel: RwLock, +} + +#[jni_struct_impl] +impl Channel { + pub fn new(session: &mut Session) -> Channel { + 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_channel(&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) { + println!("Byte is {byte}"); + + 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(); + } +} + +fn to_int(auth_status: AuthStatus) -> i32 { + match auth_status { + AuthStatus::Success => 1, + AuthStatus::Denied => 2, + AuthStatus::Partial => 3, + AuthStatus::Info => 4, + AuthStatus::Again => 5, + } +} + +#[jni_data_class] +pub struct ReadResult { + pub read_count: u64, + pub data: Vec, +} \ No newline at end of file diff --git a/rs/src/ssh.rs b/rs/src/ssh.rs deleted file mode 100644 index 3b07527..0000000 --- a/rs/src/ssh.rs +++ /dev/null @@ -1,138 +0,0 @@ -use std::io::Write; -use std::sync::{Arc, RwLock}; - -use libssh_rs::{AuthStatus, PollStatus, SshOption}; - -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_channel(&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 deleted file mode 100644 index f3b57dd..0000000 --- a/rs/src/watch_directory.rs +++ /dev/null @@ -1,181 +0,0 @@ -extern crate notify; - -use std::fmt::Debug; -use std::path::{Path, PathBuf}; -use std::sync::mpsc::{channel, RecvTimeoutError}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use notify::{Config, Error, ErrorKind, Event, RecommendedWatcher, RecursiveMode, Watcher}; - -const MIN_TIME_IN_MS_BETWEEN_REFRESHES: u128 = 500; -const WATCH_TIMEOUT: u64 = 500; - - -pub fn watch_directory( - path: String, - git_dir_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())?; - - let mut paths_cached: Vec = Vec::new(); - - let mut last_update: u128 = 0; - - while notifier.should_keep_looping() { - let current_time = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("We need a TARDIS to fix this") - .as_millis(); - - // Updates are batched to prevent excessive communication between Kotlin and Rust, as the - // bridge has overhead - if last_update != 0 && current_time > (last_update + MIN_TIME_IN_MS_BETWEEN_REFRESHES) { - last_update = 0; - - if paths_cached.len() == 1 { - let first_path = paths_cached.first().expect("First path not found!"); - let is_dir = PathBuf::from(first_path).is_dir(); - - if !is_dir { - notifier.detected_change(paths_cached.to_vec()); - } - } else if !paths_cached.is_empty() { - paths_cached.sort(); - paths_cached.dedup(); - - println!("Sending {} batched events to Kotlin side", paths_cached.len()); - notifier.detected_change(paths_cached.to_vec()); - } - - paths_cached.clear(); - } - - match rx.recv_timeout(Duration::from_millis(WATCH_TIMEOUT)) { - Ok(e) => { - if let Some(paths) = get_paths_from_event_result(&e, &git_dir_path) { - let mut paths_without_dirs: Vec = paths - .into_iter() - .collect(); - - paths_cached.append(&mut paths_without_dirs); - - last_update = SystemTime::now() - .duration_since(UNIX_EPOCH) - .expect("We need a TARDIS to fix this") - .as_millis(); - - println!("Event: {e:?}"); - } - } - 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, git_dir_path: &str) -> Option> { - match event_result { - Ok(event) => { - let events: Vec = event - .paths - .clone() - .into_iter() - .filter_map(|path| { - // Directories are not tracked by Git so we don't care about them (just about their content) - // We won't be able to check if it's a dir if it has been deleted but that's good enough - if path.is_dir() { - println!("Ignoring directory {path:#?}"); - None - } else { - let path_str = path.into_os_string() - .into_string() - .ok()?; - - // JGit may create .probe-UUID files for its internal stuff, we don't care about it - let probe_prefix = format!("{git_dir_path}.probe-"); - if path_str.starts_with(probe_prefix.as_str()) { - None - } else { - Some(path_str) - } - } - }) - .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, - } - } -} diff --git a/rs/uniffi-bindgen.rs b/rs/uniffi-bindgen.rs deleted file mode 100644 index f6cff6c..0000000 --- a/rs/uniffi-bindgen.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - uniffi::uniffi_bindgen_main() -} diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/autogenerated/.gitignore b/src/main/kotlin/com/jetpackduba/gitnuro/autogenerated/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/src/main/kotlin/com/jetpackduba/gitnuro/autogenerated/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/credentials/SshProcess.kt b/src/main/kotlin/com/jetpackduba/gitnuro/credentials/SshProcess.kt index 25bbef7..537bd1e 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/credentials/SshProcess.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/credentials/SshProcess.kt @@ -1,7 +1,7 @@ package com.jetpackduba.gitnuro.credentials +import Session import com.jetpackduba.gitnuro.ssh.libssh.ChannelWrapper -import uniffi.gitnuro.Session import java.io.InputStream import java.io.OutputStream diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/credentials/SshRemoteSession.kt b/src/main/kotlin/com/jetpackduba/gitnuro/credentials/SshRemoteSession.kt index 02219bb..a7ad9cf 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/credentials/SshRemoteSession.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/credentials/SshRemoteSession.kt @@ -1,14 +1,12 @@ package com.jetpackduba.gitnuro.credentials +import Session import org.eclipse.jgit.transport.RemoteSession import org.eclipse.jgit.transport.URIish -import uniffi.gitnuro.AuthStatus -import uniffi.gitnuro.Session import java.util.concurrent.CancellationException import javax.inject.Inject -private const val DEFAULT_SSH_PORT = 22 private const val NOT_EXPLICIT_PORT = -1 class SshRemoteSession @Inject constructor( @@ -31,18 +29,18 @@ class SshRemoteSession @Inject constructor( } fun setup(uri: URIish) { - val session = Session() + val session = Session.new() val port = if (uri.port == NOT_EXPLICIT_PORT) { null } else uri.port - session.setup(uri.host, uri.user, port?.toUShort()) + session.setup(uri.host, uri.user ?: "", port)//?.toUShort()) var result = session.publicKeyAuth("") - if (result == AuthStatus.DENIED) { + if (result == 2) {//AuthStatus.DENIED) { credentialsStateManager.updateState(CredentialsRequested.SshCredentialsRequested) var credentials = credentialsStateManager.currentCredentialsState @@ -57,13 +55,14 @@ class SshRemoteSession @Inject constructor( result = session.publicKeyAuth(password) - if (result != AuthStatus.SUCCESS) { + if (result != 1) {//AuthStatus.SUCCESS) { result = session.passwordAuth(password) } } - if (result != AuthStatus.SUCCESS) + if (result != 1) {//AuthStatus.SUCCESS) throw Exception("Something went wrong with authentication. Code $result") + } this.session = session } diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/di/modules/NetworkModule.kt b/src/main/kotlin/com/jetpackduba/gitnuro/di/modules/NetworkModule.kt index 2294039..d710b1c 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/di/modules/NetworkModule.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/di/modules/NetworkModule.kt @@ -4,6 +4,7 @@ import com.jetpackduba.gitnuro.updates.UpdatesService import dagger.Provides import retrofit2.Retrofit import retrofit2.converter.scalars.ScalarsConverterFactory +import javax.inject.Inject @dagger.Module class NetworkModule { diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/git/FileChangesWatcher.kt b/src/main/kotlin/com/jetpackduba/gitnuro/git/FileChangesWatcher.kt index 177f3ce..cd0eb1e 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/git/FileChangesWatcher.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/git/FileChangesWatcher.kt @@ -1,17 +1,17 @@ package com.jetpackduba.gitnuro.git +import FileChanged +import FileWatcher +import WatchDirectoryNotifier import com.jetpackduba.gitnuro.git.workspace.GetIgnoreRulesUseCase import com.jetpackduba.gitnuro.system.systemSeparator import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow -import kotlinx.coroutines.isActive import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Repository -import uniffi.gitnuro.WatchDirectoryNotifier -import uniffi.gitnuro.watchDirectory import java.nio.file.Files import java.nio.file.Paths import javax.inject.Inject @@ -37,19 +37,48 @@ class FileChangesWatcher @Inject constructor( Constants.SQUASH_MSG, ) - val checker = object : WatchDirectoryNotifier { - override fun shouldKeepLooping(): Boolean { - return isActive - } +// val checker = object : WatchDirectoryNotifier { +// override fun shouldKeepLooping(): Boolean { +// return isActive +// } +// +// override fun detectedChange(paths: List) = runBlocking { +// val hasGitIgnoreChanged = paths.any { it == "$workspacePath$systemSeparator.gitignore" } +// +// if (hasGitIgnoreChanged) { +// ignoreRules = getIgnoreRulesUseCase(repository) +// } +// +// val areAllPathsIgnored = paths.all { path -> +// val matchesAnyIgnoreRule = ignoreRules.any { rule -> +// rule.isMatch(path, Files.isDirectory(Paths.get(path))) +// } +// +// val isGitIgnoredFile = gitDirIgnoredFiles.any { ignoredFile -> +// "$workspacePath$systemSeparator.git$systemSeparator$ignoredFile" == path +// } +// +// matchesAnyIgnoreRule || isGitIgnoredFile +// } +// +// val hasGitDirChanged = paths.any { it.startsWith("$workspacePath$systemSeparator.git$systemSeparator") } +// +// if (!areAllPathsIgnored) { +// _changesNotifier.emit(hasGitDirChanged) +// } +// } +// } - override fun detectedChange(paths: List) = runBlocking { - val hasGitIgnoreChanged = paths.any { it == "$workspacePath$systemSeparator.gitignore" } + val checker = object : WatchDirectoryNotifier { + override fun detectedChange(path: FileChanged) = runBlocking { + val path = path.path + val hasGitIgnoreChanged = path == "$workspacePath$systemSeparator.gitignore" if (hasGitIgnoreChanged) { ignoreRules = getIgnoreRulesUseCase(repository) } - val areAllPathsIgnored = paths.all { path -> +// val areAllPathsIgnored = paths.all { path -> val matchesAnyIgnoreRule = ignoreRules.any { rule -> rule.isMatch(path, Files.isDirectory(Paths.get(path))) } @@ -58,10 +87,10 @@ class FileChangesWatcher @Inject constructor( "$workspacePath$systemSeparator.git$systemSeparator$ignoredFile" == path } - matchesAnyIgnoreRule || isGitIgnoredFile - } + val areAllPathsIgnored = matchesAnyIgnoreRule || isGitIgnoredFile +// } - val hasGitDirChanged = paths.any { it.startsWith("$workspacePath$systemSeparator.git$systemSeparator") } + val hasGitDirChanged = path.startsWith("$workspacePath$systemSeparator.git$systemSeparator") if (!areAllPathsIgnored) { _changesNotifier.emit(hasGitDirChanged) @@ -69,6 +98,7 @@ class FileChangesWatcher @Inject constructor( } } - watchDirectory(workspacePath, gitRepoPath, checker) + val fileWatcher = FileWatcher.new() + fileWatcher.watch(workspacePath, gitRepoPath, checker) } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/main.kt b/src/main/kotlin/com/jetpackduba/gitnuro/main.kt index 79de145..3bb6549 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/main.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/main.kt @@ -3,6 +3,7 @@ package com.jetpackduba.gitnuro import com.jetpackduba.gitnuro.repositories.initPreferencesPath fun main(args: Array) { + System.load("/home/abde/Projects/Compose/Gitnuro/rs/target/release/libgitnuro_rs.so") initPreferencesPath() val app = App() diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/ChannelWrapper.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/ChannelWrapper.kt index e834eff..4769df7 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/ChannelWrapper.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/ChannelWrapper.kt @@ -1,13 +1,13 @@ package com.jetpackduba.gitnuro.ssh.libssh +import Channel +import Session import com.jetpackduba.gitnuro.ssh.libssh.streams.SshChannelInputErrStream import com.jetpackduba.gitnuro.ssh.libssh.streams.SshChannelInputStream import com.jetpackduba.gitnuro.ssh.libssh.streams.SshChannelOutputStream -import uniffi.gitnuro.Channel -import uniffi.gitnuro.Session class ChannelWrapper internal constructor(sshSession: Session) { - private var channel = Channel(sshSession) + private val channel = Channel.new(sshSession) val outputStream = SshChannelOutputStream(channel) val inputStream = SshChannelInputStream(channel) @@ -27,5 +27,6 @@ class ChannelWrapper internal constructor(sshSession: Session) { fun close() { channel.closeChannel() + channel.close() } } \ No newline at end of file diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelInputErrStream.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelInputErrStream.kt index 89f1d53..bcbebd5 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelInputErrStream.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelInputErrStream.kt @@ -1,6 +1,6 @@ package com.jetpackduba.gitnuro.ssh.libssh.streams -import uniffi.gitnuro.Channel +import Channel import java.io.InputStream class SshChannelInputErrStream(private val sshChannel: Channel) : InputStream() { @@ -8,7 +8,7 @@ class SshChannelInputErrStream(private val sshChannel: Channel) : InputStream() override fun read(): Int { return if (sshChannel.pollHasBytes(true)) { - val read = sshChannel.read(true, 1u) + val read = sshChannel.read(true, 1L) // TODO it was a long val byteArray = read.data val first = byteArray.first() diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelInputStream.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelInputStream.kt index 2404f2b..020db6a 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelInputStream.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelInputStream.kt @@ -1,11 +1,11 @@ package com.jetpackduba.gitnuro.ssh.libssh.streams -import uniffi.gitnuro.Channel +import Channel import java.io.InputStream class SshChannelInputStream(private val sshChannel: Channel) : InputStream() { override fun read(b: ByteArray, off: Int, len: Int): Int { - val result = sshChannel.read(false, len.toULong()) + val result = sshChannel.read(false, len.toLong())//.toULong()) val byteArray = result.data val read = result.readCount @@ -18,7 +18,7 @@ class SshChannelInputStream(private val sshChannel: Channel) : InputStream() { override fun read(): Int { - val result = sshChannel.read(false, 1u) + val result = sshChannel.read(false, 1L)//1u) val first = result.data.first() diff --git a/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelOutputStream.kt b/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelOutputStream.kt index 04b8fa0..36ecb8b 100644 --- a/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelOutputStream.kt +++ b/src/main/kotlin/com/jetpackduba/gitnuro/ssh/libssh/streams/SshChannelOutputStream.kt @@ -1,6 +1,6 @@ package com.jetpackduba.gitnuro.ssh.libssh.streams -import uniffi.gitnuro.Channel +import Channel import java.io.OutputStream class SshChannelOutputStream(private val sshChannel: Channel) : OutputStream() {