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]
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.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")
}

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"
[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"

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 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<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
import Session
import com.jetpackduba.gitnuro.ssh.libssh.ChannelWrapper
import uniffi.gitnuro.Session
import java.io.InputStream
import java.io.OutputStream

View File

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

View File

@ -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 {

View File

@ -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<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 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)
}
}

View File

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

View File

@ -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()
}
}

View File

@ -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()

View File

@ -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()

View File

@ -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() {