Migrated from Uniffi+JNA to Kotars
This commit is contained in:
parent
bdb4bcb702
commit
a542e38ae2
@ -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.
|
||||||
|
|
||||||
|
@ -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")
|
||||||
}
|
}
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
[target.aarch64-unknown-linux-gnu]
|
|
||||||
linker = "aarch64-linux-gnu-gcc"
|
|
@ -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"
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
uniffi::generate_scaffolding("src/gitnuro.udl").unwrap();
|
|
||||||
}
|
|
@ -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;
|
|
||||||
};
|
|
314
rs/src/lib.rs
314
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 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>,
|
||||||
|
}
|
138
rs/src/ssh.rs
138
rs/src/ssh.rs
@ -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>,
|
|
||||||
}
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
uniffi::uniffi_bindgen_main()
|
|
||||||
}
|
|
2
src/main/kotlin/com/jetpackduba/gitnuro/autogenerated/.gitignore
vendored
Normal file
2
src/main/kotlin/com/jetpackduba/gitnuro/autogenerated/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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()
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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() {
|
||||||
|
Loading…
Reference in New Issue
Block a user