Migrated from Uniffi+JNA to Kotars

This commit is contained in:
Abdelilah El Aissaoui 2024-07-13 19:22:44 +02:00
parent bdb4bcb702
commit a542e38ae2
No known key found for this signature in database
GPG Key ID: 7587FC860F594869
20 changed files with 397 additions and 458 deletions

View File

@ -164,3 +164,9 @@ Example for windows (you may want to edit `C:\Program Files\Git\etc\gitconfig`):
[credential] [credential]
helper = C:/Program Files/Git/mingw64/bin/git-credential-manager-core.exe 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.

View File

@ -1,6 +1,5 @@
import org.gradle.jvm.tasks.Jar import org.gradle.jvm.tasks.Jar
import org.jetbrains.compose.compose import org.jetbrains.compose.compose
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.FileOutputStream import java.io.FileOutputStream
import java.nio.file.Files import java.nio.file.Files
@ -172,11 +171,11 @@ task("fatJarLinux", type = Jar::class) {
with(tasks.jar.get() as CopySpec) with(tasks.jar.get() as CopySpec)
} }
//
task("rust_generateKotlinFromUdl") { //task("rust_generateKotlinFromUdl") {
println("Generate Kotlin") // println("Generate Kotlin")
generateKotlinFromUdl() // generateKotlinFromUdl()
} //}
task("rust_build") { task("rust_build") {
buildRust() buildRust()
@ -186,14 +185,14 @@ tasks.getByName("compileKotlin").doLast {
println("compileKotlin called") println("compileKotlin called")
buildRust() buildRust()
copyRustBuild() copyRustBuild()
generateKotlinFromUdl() generateKotlinFromRs()
} }
tasks.getByName("compileTestKotlin").doLast { tasks.getByName("compileTestKotlin").doLast {
println("compileTestKotlin called") println("compileTestKotlin called")
buildRust() buildRust()
copyRustBuild() copyRustBuild()
generateKotlinFromUdl() generateKotlinFromRs()
} }
@ -207,23 +206,15 @@ task("tasksList") {
task("rustTasks") { task("rustTasks") {
buildRust() buildRust()
copyRustBuild() copyRustBuild()
generateKotlinFromUdl() // generateKotlinFromUdl()
} }
task("rust_copyBuild") { task("rust_copyBuild") {
copyRustBuild() copyRustBuild()
} }
fun generateKotlinFromUdl() { fun generateKotlinFromRs() {
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 buildRust() { fun buildRust() {
@ -236,7 +227,7 @@ fun buildRust() {
} }
val params = mutableListOf( val params = mutableListOf(
binary, "build", "--release", "--features=uniffi/cli", binary, "build", "--release",
) )
if (currentOs() == OS.LINUX && useCross) { if (currentOs() == OS.LINUX && useCross) {
@ -270,23 +261,16 @@ fun copyRustBuild() {
val directory = File(outputDir) val directory = File(outputDir)
directory.mkdirs() directory.mkdirs()
val originLib = when (currentOs()) { val lib = when (currentOs()) {
OS.LINUX -> "libgitnuro_rs.so" OS.LINUX -> "libgitnuro_rs.so"
OS.WINDOWS -> "gitnuro_rs.dll" OS.WINDOWS -> "gitnuro_rs.dll"
OS.MAC -> "libgitnuro_rs.dylib" OS.MAC -> "libgitnuro_rs.dylib"
} }
val destinyLib = when (currentOs()) { val originFile = File(workingDir, lib)
OS.LINUX -> "libuniffi_gitnuro.so" val destinyFile = File(directory, lib)
OS.WINDOWS -> "uniffi_gitnuro.dll"
OS.MAC -> "libuniffi_gitnuro.dylib"
}
val originFile = File(workingDir, originLib)
val destinyFile = File(directory, destinyLib)
Files.copy(originFile.toPath(), FileOutputStream(destinyFile)) Files.copy(originFile.toPath(), FileOutputStream(destinyFile))
// com.google.common.io.Files.copy(originFile, destinyFile)
println("Copy rs build completed") println("Copy rs build completed")
} }

View File

@ -1,2 +0,0 @@
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

View File

@ -8,14 +8,8 @@ crate-type = ["cdylib"]
name = "gitnuro_rs" name = "gitnuro_rs"
[dependencies] [dependencies]
uniffi = { version = "0.27.1" }
notify = "6.1.1" notify = "6.1.1"
thiserror = "1.0.56" thiserror = "1.0.56"
libssh-rs = { version = "0.2.2", features = ["vendored", "vendored-openssl"] } 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" }
[build-dependencies] jni = "0.21.1"
uniffi = { version = "0.27.1", features = ["build"] }
[[bin]]
name = "uniffi-bindgen"
path = "uniffi-bindgen.rs"

View File

@ -1,3 +0,0 @@
fn main() {
uniffi::generate_scaffolding("src/gitnuro.udl").unwrap();
}

View File

@ -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<string> 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;
};

View File

@ -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 libssh_rs::AuthStatus;
use notify::{Config, Error, Event, RecommendedWatcher, RecursiveMode, Watcher};
use ssh::{*}; jni_init!("");
use watch_directory::{*};
mod ssh; #[jni_class]
mod watch_directory; struct FileWatcher {}
uniffi::include_scaffolding!("gitnuro"); #[jni_data_class]
struct FileChanged {
path: String,
}
impl From<String> 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<String> = 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<String> = 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<Event, Error>, git_dir_path: &str) -> Option<Vec<String>> {
match event_result {
Ok(event) => {
let events: Vec<String> = 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<libssh_rs::Session>,
}
#[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<i32>) {
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<libssh_rs::Channel>,
}
#[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<u8>) {
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<u8>,
}

View File

@ -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<libssh_rs::Session>,
}
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<String>, port: Option<u16>) {
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<libssh_rs::Channel>,
}
unsafe impl Send for Channel {}
unsafe impl Sync for Channel {}
impl Channel {
pub fn new(session: Arc<Session>) -> 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<u8>) {
let channel = self.channel.write().unwrap();
channel.stdin().write_all(&data).unwrap();
}
}
pub struct ReadResult {
pub read_count: u64,
pub data: Vec<u8>,
}

View File

@ -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<dyn WatchDirectoryNotifier>,
) -> 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<String> = 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<String> = 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<Event, Error>, git_dir_path: &str) -> Option<Vec<String>> {
match event_result {
Ok(event) => {
let events: Vec<String> = 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<String>);
}
#[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,
}
}
}

View File

@ -1,3 +0,0 @@
fn main() {
uniffi::uniffi_bindgen_main()
}

View File

@ -0,0 +1,2 @@
*
!.gitignore

View File

@ -1,7 +1,7 @@
package com.jetpackduba.gitnuro.credentials package com.jetpackduba.gitnuro.credentials
import Session
import com.jetpackduba.gitnuro.ssh.libssh.ChannelWrapper import com.jetpackduba.gitnuro.ssh.libssh.ChannelWrapper
import uniffi.gitnuro.Session
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream

View File

@ -1,14 +1,12 @@
package com.jetpackduba.gitnuro.credentials package com.jetpackduba.gitnuro.credentials
import Session
import org.eclipse.jgit.transport.RemoteSession import org.eclipse.jgit.transport.RemoteSession
import org.eclipse.jgit.transport.URIish import org.eclipse.jgit.transport.URIish
import uniffi.gitnuro.AuthStatus
import uniffi.gitnuro.Session
import java.util.concurrent.CancellationException import java.util.concurrent.CancellationException
import javax.inject.Inject import javax.inject.Inject
private const val DEFAULT_SSH_PORT = 22
private const val NOT_EXPLICIT_PORT = -1 private const val NOT_EXPLICIT_PORT = -1
class SshRemoteSession @Inject constructor( class SshRemoteSession @Inject constructor(
@ -31,18 +29,18 @@ class SshRemoteSession @Inject constructor(
} }
fun setup(uri: URIish) { fun setup(uri: URIish) {
val session = Session() val session = Session.new()
val port = if (uri.port == NOT_EXPLICIT_PORT) { val port = if (uri.port == NOT_EXPLICIT_PORT) {
null null
} else } else
uri.port uri.port
session.setup(uri.host, uri.user, port?.toUShort()) session.setup(uri.host, uri.user ?: "", port)//?.toUShort())
var result = session.publicKeyAuth("") var result = session.publicKeyAuth("")
if (result == AuthStatus.DENIED) { if (result == 2) {//AuthStatus.DENIED) {
credentialsStateManager.updateState(CredentialsRequested.SshCredentialsRequested) credentialsStateManager.updateState(CredentialsRequested.SshCredentialsRequested)
var credentials = credentialsStateManager.currentCredentialsState var credentials = credentialsStateManager.currentCredentialsState
@ -57,13 +55,14 @@ class SshRemoteSession @Inject constructor(
result = session.publicKeyAuth(password) result = session.publicKeyAuth(password)
if (result != AuthStatus.SUCCESS) { if (result != 1) {//AuthStatus.SUCCESS) {
result = session.passwordAuth(password) result = session.passwordAuth(password)
} }
} }
if (result != AuthStatus.SUCCESS) if (result != 1) {//AuthStatus.SUCCESS)
throw Exception("Something went wrong with authentication. Code $result") throw Exception("Something went wrong with authentication. Code $result")
}
this.session = session this.session = session
} }

View File

@ -4,6 +4,7 @@ import com.jetpackduba.gitnuro.updates.UpdatesService
import dagger.Provides import dagger.Provides
import retrofit2.Retrofit import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory import retrofit2.converter.scalars.ScalarsConverterFactory
import javax.inject.Inject
@dagger.Module @dagger.Module
class NetworkModule { class NetworkModule {

View File

@ -1,17 +1,17 @@
package com.jetpackduba.gitnuro.git package com.jetpackduba.gitnuro.git
import FileChanged
import FileWatcher
import WatchDirectoryNotifier
import com.jetpackduba.gitnuro.git.workspace.GetIgnoreRulesUseCase import com.jetpackduba.gitnuro.git.workspace.GetIgnoreRulesUseCase
import com.jetpackduba.gitnuro.system.systemSeparator import com.jetpackduba.gitnuro.system.systemSeparator
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.eclipse.jgit.lib.Constants import org.eclipse.jgit.lib.Constants
import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.lib.Repository
import uniffi.gitnuro.WatchDirectoryNotifier
import uniffi.gitnuro.watchDirectory
import java.nio.file.Files import java.nio.file.Files
import java.nio.file.Paths import java.nio.file.Paths
import javax.inject.Inject import javax.inject.Inject
@ -37,19 +37,48 @@ class FileChangesWatcher @Inject constructor(
Constants.SQUASH_MSG, Constants.SQUASH_MSG,
) )
val checker = object : WatchDirectoryNotifier { // val checker = object : WatchDirectoryNotifier {
override fun shouldKeepLooping(): Boolean { // override fun shouldKeepLooping(): Boolean {
return isActive // return isActive
} // }
//
// override fun detectedChange(paths: List<String>) = 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<String>) = runBlocking { val checker = object : WatchDirectoryNotifier {
val hasGitIgnoreChanged = paths.any { it == "$workspacePath$systemSeparator.gitignore" } override fun detectedChange(path: FileChanged) = runBlocking {
val path = path.path
val hasGitIgnoreChanged = path == "$workspacePath$systemSeparator.gitignore"
if (hasGitIgnoreChanged) { if (hasGitIgnoreChanged) {
ignoreRules = getIgnoreRulesUseCase(repository) ignoreRules = getIgnoreRulesUseCase(repository)
} }
val areAllPathsIgnored = paths.all { path -> // val areAllPathsIgnored = paths.all { path ->
val matchesAnyIgnoreRule = ignoreRules.any { rule -> val matchesAnyIgnoreRule = ignoreRules.any { rule ->
rule.isMatch(path, Files.isDirectory(Paths.get(path))) rule.isMatch(path, Files.isDirectory(Paths.get(path)))
} }
@ -58,10 +87,10 @@ class FileChangesWatcher @Inject constructor(
"$workspacePath$systemSeparator.git$systemSeparator$ignoredFile" == path "$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) { if (!areAllPathsIgnored) {
_changesNotifier.emit(hasGitDirChanged) _changesNotifier.emit(hasGitDirChanged)
@ -69,6 +98,7 @@ class FileChangesWatcher @Inject constructor(
} }
} }
watchDirectory(workspacePath, gitRepoPath, checker) val fileWatcher = FileWatcher.new()
fileWatcher.watch(workspacePath, gitRepoPath, checker)
} }
} }

View File

@ -3,6 +3,7 @@ package com.jetpackduba.gitnuro
import com.jetpackduba.gitnuro.repositories.initPreferencesPath import com.jetpackduba.gitnuro.repositories.initPreferencesPath
fun main(args: Array<String>) { fun main(args: Array<String>) {
System.load("/home/abde/Projects/Compose/Gitnuro/rs/target/release/libgitnuro_rs.so")
initPreferencesPath() initPreferencesPath()
val app = App() val app = App()

View File

@ -1,13 +1,13 @@
package com.jetpackduba.gitnuro.ssh.libssh 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.SshChannelInputErrStream
import com.jetpackduba.gitnuro.ssh.libssh.streams.SshChannelInputStream import com.jetpackduba.gitnuro.ssh.libssh.streams.SshChannelInputStream
import com.jetpackduba.gitnuro.ssh.libssh.streams.SshChannelOutputStream import com.jetpackduba.gitnuro.ssh.libssh.streams.SshChannelOutputStream
import uniffi.gitnuro.Channel
import uniffi.gitnuro.Session
class ChannelWrapper internal constructor(sshSession: Session) { class ChannelWrapper internal constructor(sshSession: Session) {
private var channel = Channel(sshSession) private val channel = Channel.new(sshSession)
val outputStream = SshChannelOutputStream(channel) val outputStream = SshChannelOutputStream(channel)
val inputStream = SshChannelInputStream(channel) val inputStream = SshChannelInputStream(channel)
@ -27,5 +27,6 @@ class ChannelWrapper internal constructor(sshSession: Session) {
fun close() { fun close() {
channel.closeChannel() channel.closeChannel()
channel.close()
} }
} }

View File

@ -1,6 +1,6 @@
package com.jetpackduba.gitnuro.ssh.libssh.streams package com.jetpackduba.gitnuro.ssh.libssh.streams
import uniffi.gitnuro.Channel import Channel
import java.io.InputStream import java.io.InputStream
class SshChannelInputErrStream(private val sshChannel: Channel) : InputStream() { class SshChannelInputErrStream(private val sshChannel: Channel) : InputStream() {
@ -8,7 +8,7 @@ class SshChannelInputErrStream(private val sshChannel: Channel) : InputStream()
override fun read(): Int { override fun read(): Int {
return if (sshChannel.pollHasBytes(true)) { 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 byteArray = read.data
val first = byteArray.first() val first = byteArray.first()

View File

@ -1,11 +1,11 @@
package com.jetpackduba.gitnuro.ssh.libssh.streams package com.jetpackduba.gitnuro.ssh.libssh.streams
import uniffi.gitnuro.Channel import Channel
import java.io.InputStream import java.io.InputStream
class SshChannelInputStream(private val sshChannel: Channel) : InputStream() { class SshChannelInputStream(private val sshChannel: Channel) : InputStream() {
override fun read(b: ByteArray, off: Int, len: Int): Int { 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 byteArray = result.data
val read = result.readCount val read = result.readCount
@ -18,7 +18,7 @@ class SshChannelInputStream(private val sshChannel: Channel) : InputStream() {
override fun read(): Int { override fun read(): Int {
val result = sshChannel.read(false, 1u) val result = sshChannel.read(false, 1L)//1u)
val first = result.data.first() val first = result.data.first()

View File

@ -1,6 +1,6 @@
package com.jetpackduba.gitnuro.ssh.libssh.streams package com.jetpackduba.gitnuro.ssh.libssh.streams
import uniffi.gitnuro.Channel import Channel
import java.io.OutputStream import java.io.OutputStream
class SshChannelOutputStream(private val sshChannel: Channel) : OutputStream() { class SshChannelOutputStream(private val sshChannel: Channel) : OutputStream() {