diff --git a/Cargo.lock b/Cargo.lock index 07ca3de..6f0253e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,7 @@ dependencies = [ "dashmap", "diamond-types", "jni", + "jni-toolbox", "lazy_static", "mlua-codemp-patch", "napi", @@ -746,6 +747,27 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jni-toolbox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c14725e4fd3fe9ea885cea9df1ff1fa9f4d45a6f4253f6b90802f0941335f6" +dependencies = [ + "jni", + "jni-toolbox-macro", +] + +[[package]] +name = "jni-toolbox-macro" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9970cad895bb316f70956593710d675d27a480ddbb8099f7e313042463a16d9b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + [[package]] name = "js-sys" version = "0.3.70" diff --git a/Cargo.toml b/Cargo.toml index 2698072..72273f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ tracing-subscriber = { version = "0.3", optional = true } # glue (java) lazy_static = { version = "1.5", optional = true } jni = { version = "0.21", features = ["invocation"], optional = true } +jni-toolbox = { version = "0.1.3", optional = true } # glue (lua) mlua-codemp-patch = { version = "0.10.0-beta.2", features = ["module", "send", "serialize"], optional = true } @@ -69,7 +70,7 @@ async-trait = ["dep:async-trait"] serialize = ["dep:serde", "uuid/serde"] # ffi rust = [] # used for ci matrix -java = ["lazy_static", "jni", "tracing-subscriber"] +java = ["lazy_static", "jni", "tracing-subscriber", "jni-toolbox"] js = ["napi-build", "tracing-subscriber", "napi", "napi-derive"] py = ["pyo3", "tracing-subscriber", "pyo3-build-config"] lua = ["mlua-codemp-patch", "tracing-subscriber", "lazy_static", "serialize"] diff --git a/src/ffi/java/client.rs b/src/ffi/java/client.rs index 543458d..7472f75 100644 --- a/src/ffi/java/client.rs +++ b/src/ffi/java/client.rs @@ -1,166 +1,133 @@ use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray}, JNIEnv}; -use crate::{api::Config, client::Client, ffi::java::{handle_error, null_check}, Workspace}; +use jni_toolbox::{jni, FromJava, IntoJava, JniToolboxError}; +use crate::{api::Config, client::Client, errors::{ConnectionError, RemoteError}, ffi::java::{handle_error, null_check}, Workspace}; use super::{Deobjectify, JExceptable, JObjectify, tokio}; -/// Connect using the given credentials to the default server, and return a [Client] to interact with it. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_connect<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - config: JObject<'local> -) -> jobject { - null_check!(env, config, std::ptr::null_mut()); - let config = Config::deobjectify(&mut env, config); - if config.is_err() { - handle_error!(&mut env, config, std::ptr::null_mut()); - } +impl<'j> IntoJava<'j> for Client { + type T = jobject; - let client = tokio().block_on(Client::connect(config.unwrap())); - if let Ok(client) = client { - client.jobjectify(&mut env).jexcept(&mut env).as_raw() - } else { - handle_error!(&mut env, client, std::ptr::null_mut()); + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { + // Ok(Box::into_raw(Box::new(self))) + todo!() } } +impl<'j> FromJava<'j> for Client { + type T = jobject; + + fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result { + let x = unsafe { Box::leak(Box::from_raw(value as *mut Client)) }; + todo!(); + Ok(x.clone()) + } +} + +impl<'j> FromJava<'j> for Config { + type T = JObject<'j>; + fn from_java(env: &mut jni::JNIEnv<'j>, value: Self::T) -> Result { + Ok(Config::deobjectify(env, value)?) + } +} + +impl<'j> IntoJava<'j> for crate::api::User { + type T = jobject; + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { + Ok(self.jobjectify(env)?.into_raw()) + } +} + +impl<'j> IntoJava<'j> for Workspace { + type T = jobject; + fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result { + Ok(self.jobjectify(env)?.into_raw()) + } +} + +impl JniToolboxError for ConnectionError { + fn jclass(&self) -> String { // TODO pick class based on underlying type + "mp/code/exceptions/ConnectionRemoteException".to_string() + } +} + +impl JniToolboxError for RemoteError { + fn jclass(&self) -> String { // TODO pick class based on underlying type + "mp/code/exceptions/ConnectionRemoteException".to_string() + } +} + +#[jni(package = "mp.code", class = "Client", ptr)] +fn connect(config: Config) -> Result { + tokio().block_on(Client::connect(config)) +} + +fn asd(arg: String) -> Result, String> { + Ok(arg.split('/').map(|x| x.to_string()).collect()) +} + /// Gets the current [crate::api::User]. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_get_1user( - mut env: JNIEnv, - _class: JClass, - self_ptr: jlong -) -> jobject { - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; +#[jni(package = "mp.code", class = "Client", ptr)] +fn get_user(client: Client) -> crate::api::User { client.user().clone() - .jobjectify(&mut env) - .jexcept(&mut env) - .as_raw() } /// Join a [Workspace] and return a pointer to it. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_join_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) -> jobject { - null_check!(env, workspace_id, std::ptr::null_mut()); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - let workspace = tokio().block_on(client.join_workspace(workspace_id)) - .map(|workspace| spawn_updater(workspace.clone())); - if let Ok(workspace) = workspace { - workspace.jobjectify(&mut env).jexcept(&mut env).as_raw() - } else { - handle_error!(&mut env, workspace, std::ptr::null_mut()) - } +#[jni(package = "mp.code", class = "Client", ptr)] +fn join_workspace(client: Client, workspace: String) -> Result { + tokio().block_on(client.join_workspace(workspace)) } -/// Create a workspace on server, if allowed to. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_create_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) { - null_check!(env, workspace_id, {}); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - tokio() - .block_on(client.create_workspace(workspace_id)) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Client")] +fn create_workspace(client: Client, workspace: String) -> Result<(), RemoteError> { + tokio().block_on(client.create_workspace(workspace)) } /// Delete a workspace on server, if allowed to. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_delete_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) { - null_check!(env, workspace_id, {}); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - tokio() - .block_on(client.delete_workspace(workspace_id)) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Client")] +fn delete_workspace(client: Client, workspace: String) -> Result<(), RemoteError> { + tokio().block_on(client.delete_workspace(workspace)) } /// Invite another user to an owned workspace. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_invite_1to_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local>, - user: JString<'local> -) { - null_check!(env, workspace_id, {}); - null_check!(env, user, {}); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - let user_name = unsafe { env.get_string_unchecked(&user) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - tokio() - .block_on(client.invite_to_workspace(workspace_id, user_name)) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Client")] +fn invite_to_workspace(client: Client, workspace: String, user: String) -> Result<(), RemoteError> { + tokio().block_on(client.invite_to_workspace(workspace, user)) } /// List available workspaces. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_list_1workspaces<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - owned: jboolean, - invited: jboolean -) -> jobjectArray { - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let list = tokio() - .block_on(client.list_workspaces(owned != 0, invited != 0)) - .jexcept(&mut env); - env.find_class("java/lang/String") - .and_then(|class| env.new_object_array(list.len() as i32, class, JObject::null())) - .inspect(|arr| { - for (idx, path) in list.iter().enumerate() { - env.new_string(path) - .and_then(|path| env.set_object_array_element(arr, idx as i32, path)) - .jexcept(&mut env) - } - }).jexcept(&mut env).as_raw() +#[jni(package = "mp.code", class = "Client", ptr)] +fn list_workspaces(client: Client, owned: bool, invited: bool) -> Result, RemoteError> { + tokio().block_on(client.list_workspaces(owned, invited)) } /// List available workspaces. +#[jni(package = "mp.code", class = "Client", ptr)] +fn active_workspaces(client: Client) -> Vec { + client.active_workspaces() +} + +/// Leave a [Workspace] and return whether or not the client was in such workspace. +#[jni(package = "mp.code", class = "Client")] +fn leave_workspace(client: Client, workspace: String) -> bool { + client.leave_workspace(&workspace) +} + +/// Get a [Workspace] by name and returns a pointer to it. +#[jni(package = "mp.code", class = "Client", ptr)] +fn get_workspace(client: Client, workspace: String) -> Option { + client.get_workspace(&workspace) +} + +/// Refresh the client's session token. +#[jni(package = "mp.code", class = "Client")] +fn refresh(client: Client) -> Result<(), RemoteError> { + tokio().block_on(client.refresh()) +} + +/// Called by the Java GC to drop a [Client]. #[no_mangle] -pub extern "system" fn Java_mp_code_Client_active_1workspaces<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong -) -> jobjectArray { - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let list = client.active_workspaces(); - env.find_class("java/lang/String") - .and_then(|class| env.new_object_array(list.len() as i32, class, JObject::null())) - .inspect(|arr| { - for (idx, path) in list.iter().enumerate() { - env.new_string(path) - .and_then(|path| env.set_object_array_element(arr, idx as i32, path)) - .jexcept(&mut env) - } - }).jexcept(&mut env).as_raw() +pub extern "system" fn Java_mp_code_Client_free(_env: JNIEnv, _class: JClass, input: jlong) { + let _ = unsafe { Box::from_raw(input as *mut Client) }; } // TODO: this stays until we get rid of the arc then i'll have to find a better way @@ -175,57 +142,3 @@ fn spawn_updater(workspace: Workspace) -> Workspace { }); workspace } - -/// Leave a [Workspace] and return whether or not the client was in such workspace. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_leave_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) -> jboolean { - null_check!(env, workspace_id, false as jboolean); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .map(|wid| client.leave_workspace(&wid) as jboolean) - .jexcept(&mut env) -} - -/// Get a [Workspace] by name and returns a pointer to it. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, - workspace_id: JString<'local> -) -> jobject { - null_check!(env, workspace_id, std::ptr::null_mut()); - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - let workspace_id = unsafe { env.get_string_unchecked(&workspace_id) } - .map(|wid| wid.to_string_lossy().to_string()) - .jexcept(&mut env); - if let Some(workspace) = client.get_workspace(&workspace_id) { - workspace.jobjectify(&mut env).jexcept(&mut env).as_raw() - } else { - std::ptr::null_mut() - } -} - -/// Refresh the client's session token. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_refresh<'local>( - mut env: JNIEnv<'local>, - _class: JClass<'local>, - self_ptr: jlong, -) { - let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; - tokio().block_on(client.refresh()) - .jexcept(&mut env); -} - -/// Called by the Java GC to drop a [Client]. -#[no_mangle] -pub extern "system" fn Java_mp_code_Client_free(_env: JNIEnv, _class: JClass, input: jlong) { - let _ = unsafe { Box::from_raw(input as *mut Client) }; -} diff --git a/src/ffi/java/ext.rs b/src/ffi/java/ext.rs index c9403e4..5146641 100644 --- a/src/ffi/java/ext.rs +++ b/src/ffi/java/ext.rs @@ -1,30 +1,19 @@ -use jni::{objects::{JClass, JString}, sys::{jboolean, jlong}, JNIEnv}; +use jni::{objects::{JClass, JString}, JNIEnv}; +use jni_toolbox::jni; use super::{JExceptable, null_check}; /// Calculate the XXH3 hash for a given String. -#[no_mangle] -pub extern "system" fn Java_mp_code_Extensions_hash<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - content: JString<'local>, -) -> jlong { - null_check!(env, content, 0 as jlong); - let content: String = env.get_string(&content) - .map(|s| s.into()) - .jexcept(&mut env); +#[jni(package = "mp.code", class = "Extensions")] +fn hash(content: String) -> i64 { let hash = crate::ext::hash(content.as_bytes()); i64::from_ne_bytes(hash.to_ne_bytes()) } /// Tells the [tokio] runtime how to drive the event loop. -#[no_mangle] -pub extern "system" fn Java_mp_code_Extensions_drive( - _env: JNIEnv, - _class: JClass, - block: jboolean -) { - if block != 0 { +#[jni(package = "mp.code", class = "Extensions")] +fn drive(block: bool) { + if block { super::tokio().block_on(std::future::pending::<()>()); } else { std::thread::spawn(|| { @@ -34,19 +23,7 @@ pub extern "system" fn Java_mp_code_Extensions_drive( } /// Set up the tracing subscriber. -#[no_mangle] -#[allow(non_snake_case)] -pub extern "system" fn Java_mp_code_Extensions_setupTracing<'local>( - mut env: JNIEnv, - _class: JClass<'local>, - path: JString<'local>, - debug: jboolean -) { - super::setup_logger( - debug != 0, - Some(path) - .filter(|p| !p.is_null()) - .map(|p| env.get_string(&p).map(|s| s.into()) - .jexcept(&mut env)) - ); +#[jni(package = "mp.code", class = "Extensions")] +fn setupTracing(path: Option, debug: bool) { + super::setup_logger(debug, path); } diff --git a/src/ffi/java/workspace.rs b/src/ffi/java/workspace.rs index 27a9702..d1a35e5 100644 --- a/src/ffi/java/workspace.rs +++ b/src/ffi/java/workspace.rs @@ -1,4 +1,5 @@ use jni::{objects::{JClass, JObject, JString}, sys::{jboolean, jlong, jobject, jobjectArray, jstring}, JNIEnv}; +use jni_toolbox::jni; use crate::Workspace; use super::{handle_error, null_check, JExceptable, JObjectify}; @@ -45,35 +46,30 @@ pub extern "system" fn Java_mp_code_Workspace_get_1buffer<'local>( } /// Get the filetree. -#[no_mangle] -pub extern "system" fn Java_mp_code_Workspace_get_1file_1tree( +#[jni(package = "mp.code", class = "Workspace", ptr)] +fn file_tree( mut env: JNIEnv, _class: JClass, self_ptr: jlong, filter: JString, strict: jboolean -) -> jobjectArray { +) -> Result { let workspace = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Workspace)) }; let filter: Option = if filter.is_null() { None } else { - Some( - env.get_string(&filter) - .map(|s| s.into()) - .jexcept(&mut env) - ) + Some(env.get_string(&filter)?.into()) }; let file_tree = workspace.filetree(filter.as_deref(), strict != 0); - env.find_class("java/lang/String") - .and_then(|class| env.new_object_array(file_tree.len() as i32, class, JObject::null())) - .inspect(|arr| { - for (idx, path) in file_tree.iter().enumerate() { - env.new_string(path) - .and_then(|path| env.set_object_array_element(arr, idx as i32, path)) - .jexcept(&mut env) - } - }).jexcept(&mut env).as_raw() + let class = env.find_class("java/lang/String")?; + let array = env.new_object_array(file_tree.len() as i32, class, JObject::null())?; + for (idx, path) in file_tree.iter().enumerate() { + let element = env.new_string(path)?; + env.set_object_array_element(&array, idx as i32, element)?; + } + + Ok(array.as_raw()) } /// Gets a list of the active buffers.