diff --git a/dist/java/src/mp/code/Client.java b/dist/java/src/mp/code/Client.java index 2643bf8..c2edb30 100644 --- a/dist/java/src/mp/code/Client.java +++ b/dist/java/src/mp/code/Client.java @@ -24,6 +24,26 @@ public class Client { return join_workspace(this.ptr, id); } + private static native void create_workspace(long self, String id) throws CodeMPException; + public void createWorkspace(String id) throws CodeMPException { + return create_workspace(this.ptr, id); + } + + private static native void delete_workspace(long self, String id) throws CodeMPException; + public void deleteWorkspace(String id) throws CodeMPException { + return delete_workspace(this.ptr, id); + } + + private static native void invite_to_workspace(long self, String ws, String usr) throws CodeMPException; + public void inviteToWorkspace(String ws, String usr) throws CodeMPException { + return invite_to_workspace(this.ptr, id); + } + + private static native String[] list_workspaces(long self, boolean owned, boolean invited) throws CodeMPException; + public String[] listWorkspaces(boolean owned, boolean invited) throws CodeMPException { + return list_workspaces(this.ptr, owned, invited); + } + private static native boolean leave_workspace(long self, String id); public boolean leaveWorkspace(String id) { return leave_workspace(this.ptr, id); @@ -33,6 +53,11 @@ public class Client { public Optional getWorkspace() { return Optional.ofNullable(get_workspace(this.ptr)); } + + private static native void refresh_native(long self); + public void refresh() { + return refresh_native(this.ptr); + } private static native void free(long self); @Override diff --git a/dist/py/codemp.pyi b/dist/py/codemp.pyi index cecc983..459f923 100644 --- a/dist/py/codemp.pyi +++ b/dist/py/codemp.pyi @@ -114,7 +114,13 @@ class Client: host: str, username: str, password: str) -> Client: ... def join_workspace(self, workspace: str) -> Promise[Workspace]: ... + def create_workspace(self, workspace: str) -> Promise[None]: ... + def delete_workspace(self, workspace: str) -> Promise[None]: ... + def invite_to_workspace(self, workspace: str, username: str) -> Promise[None]: ... + def list_workspaces(self, owned: bool, invited: bool) -> Promise[list[str]]: ... def leave_workspace(self, workspace: str) -> bool: ... def get_workspace(self, id: str) -> Workspace: ... def active_workspaces(self) -> list[str]: ... def user_id(self) -> str: ... + def user_name(self) -> str: ... + def refresh(self) -> Promise[None]: ... diff --git a/src/ffi/java/client.rs b/src/ffi/java/client.rs index 926d811..3203b56 100644 --- a/src/ffi/java/client.rs +++ b/src/ffi/java/client.rs @@ -1,4 +1,4 @@ -use jni::{objects::{JClass, JString, JValueGen}, sys::{jboolean, jlong, jobject}, JNIEnv}; +use jni::{objects::{JClass, JObject, JString, JValueGen}, sys::{jboolean, jlong, jobject, jobjectArray}, JNIEnv}; use crate::{client::Client, Workspace}; use super::{JExceptable, RT}; @@ -21,7 +21,7 @@ pub extern "system" fn Java_mp_code_Client_connect<'local>( let pwd: String = env.get_string(&pwd) .map(|s| s.into()) .jexcept(&mut env); - RT.block_on(crate::Client::new(&url, &user, &pwd)) + RT.block_on(crate::Client::connect(&url, &user, &pwd)) .map(|client| Box::into_raw(Box::new(client)) as jlong) .map(|ptr| { env.find_class("mp/code/Client") @@ -52,6 +52,87 @@ pub extern "system" fn Java_mp_code_Client_join_1workspace<'local>( }).jexcept(&mut env).as_raw() } +/// Creates 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, + input: JString<'local> +) { + let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; + let workspace_id = unsafe { env.get_string_unchecked(&input) } + .map(|wid| wid.to_string_lossy().to_string()) + .jexcept(&mut env); + RT + .block_on(client.create_workspace(workspace_id)) + .jexcept(&mut env); +} + +/// Deletes 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, + input: JString<'local> +) { + let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; + let workspace_id = unsafe { env.get_string_unchecked(&input) } + .map(|wid| wid.to_string_lossy().to_string()) + .jexcept(&mut env); + RT + .block_on(client.delete_workspace(workspace_id)) + .jexcept(&mut env); +} + +/// Invites 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, + ws: JString<'local>, + usr: JString<'local> +) { + let client = unsafe { Box::leak(Box::from_raw(self_ptr as *mut Client)) }; + let workspace_id = unsafe { env.get_string_unchecked(&ws) } + .map(|wid| wid.to_string_lossy().to_string()) + .jexcept(&mut env); + let user_name = unsafe { env.get_string_unchecked(&usr) } + .map(|wid| wid.to_string_lossy().to_string()) + .jexcept(&mut env); + RT + .block_on(client.invite_to_workspace(workspace_id, user_name)) + .jexcept(&mut env); +} + +/// 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 = RT + .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())) + .map(|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) + } + arr + }).jexcept(&mut env).as_raw() +} + // TODO: this stays until we get rid of the arc then i'll have to find a better way fn spawn_updater(workspace: Workspace) -> Workspace { let w = workspace.clone(); @@ -79,6 +160,7 @@ pub extern "system" fn Java_mp_code_Client_leave_1workspace<'local>( .map(|wid| client.leave_workspace(&wid) as jboolean) .jexcept(&mut env) } + /// Gets a [Workspace] by name and returns a pointer to it. #[no_mangle] pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>( @@ -100,6 +182,17 @@ pub extern "system" fn Java_mp_code_Client_get_1workspace<'local>( }).unwrap_or_default().as_raw() } +/// Refresh 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)) }; + RT.block_on(client.refresh()).jexcept(&mut env); +} + /// Sets up the tracing subscriber. #[no_mangle] pub extern "system" fn Java_mp_code_Client_setup_1tracing<'local>( diff --git a/src/ffi/js/client.rs b/src/ffi/js/client.rs index 6dc54c3..3ce4803 100644 --- a/src/ffi/js/client.rs +++ b/src/ffi/js/client.rs @@ -4,7 +4,7 @@ use crate::{Client, Workspace}; #[napi] /// connect to codemp servers and return a client session pub async fn connect(addr: Option, username: String, password: String) -> napi::Result{ - let client = crate::Client::new(addr.as_deref().unwrap_or("http://codemp.alemi.dev:50053"), username, password) + let client = crate::Client::connect(addr.as_deref().unwrap_or("http://codemp.alemi.dev:50053"), username, password) .await?; Ok(client) @@ -12,12 +12,42 @@ pub async fn connect(addr: Option, username: String, password: String) - #[napi] impl Client { + #[napi(js_name = "create_workspace")] + /// create workspace with given id, if able to + pub async fn js_create_workspace(&self, workspace: String) -> napi::Result<()> { + Ok(self.create_workspace(workspace).await?) + } + + #[napi(js_name = "delete_workspace")] + /// delete workspace with given id, if able to + pub async fn js_delete_workspace(&self, workspace: String) -> napi::Result<()> { + Ok(self.delete_workspace(workspace).await?) + } + + #[napi(js_name = "list_workspaces")] + /// list available workspaces + pub async fn js_list_workspaces(&self, owned: bool, invited: bool) -> napi::Result> { + Ok(self.list_workspaces(owned, invited).await?) + } + + #[napi(js_name = "invite_to_workspace")] + /// invite user to given workspace, if able to + pub async fn js_invite_to_workspace(&self, workspace: String, user: String) -> napi::Result<()> { + Ok(self.invite_to_workspace(workspace, user).await?) + } + #[napi(js_name = "join_workspace")] /// join workspace with given id (will start its cursor controller) pub async fn js_join_workspace(&self, workspace: String) -> napi::Result { Ok(self.join_workspace(workspace).await?) } + #[napi(js_name = "leave_workspace")] + /// leave workspace and disconnect, returns true if workspace was active + pub async fn js_leave_workspace(&self, workspace: String) -> napi::Result { + Ok(self.leave_workspace(&workspace)) + } + #[napi(js_name = "get_workspace")] /// get workspace with given id, if it exists pub fn js_get_workspace(&self, workspace: String) -> Option { @@ -27,7 +57,7 @@ impl Client { #[napi(js_name = "user_id")] /// return current sessions's user id pub fn js_user_id(&self) -> String { - self.user_id().to_string() + self.user().id.to_string() } #[napi(js_name = "active_workspaces")] @@ -35,4 +65,10 @@ impl Client { pub fn js_active_workspaces(&self) -> Vec { self.active_workspaces() } -} \ No newline at end of file + + #[napi(js_name = "refresh")] + /// refresh client session token + pub async fn js_refresh(&self) -> napi::Result<()> { + Ok(self.refresh().await?) + } +} diff --git a/src/ffi/python/client.rs b/src/ffi/python/client.rs index e5f92a7..578f4fe 100644 --- a/src/ffi/python/client.rs +++ b/src/ffi/python/client.rs @@ -8,7 +8,7 @@ use super::tokio; impl Client { #[new] fn __new__(host: String, username: String, password: String) -> crate::Result { - tokio().block_on(Client::new(host, username, password)) + tokio().block_on(Client::connect(host, username, password)) } // #[pyo3(name = "join_workspace")] @@ -37,6 +37,34 @@ impl Client { // })))) } + #[pyo3(name = "create_workspace")] + fn pycreate_workspace(&self, py: Python<'_>, workspace: String) -> PyResult { + tracing::info!("attempting to create workspace {}", workspace); + let this = self.clone(); + crate::a_sync_allow_threads!(py, this.create_workspace(workspace).await) + } + + #[pyo3(name = "delete_workspace")] + fn pydelete_workspace(&self, py: Python<'_>, workspace: String) -> PyResult { + tracing::info!("attempting to delete workspace {}", workspace); + let this = self.clone(); + crate::a_sync_allow_threads!(py, this.delete_workspace(workspace).await) + } + + #[pyo3(name = "invite_to_workspace")] + fn pyinvite_to_workspace(&self, py: Python<'_>, workspace: String, user: String) -> PyResult { + tracing::info!("attempting to invite {user} to workspace {workspace}"); + let this = self.clone(); + crate::a_sync_allow_threads!(py, this.invite_to_workspace(workspace, user).await) + } + + #[pyo3(name = "list_workspaces")] + fn pylist_workspaces(&self, py: Python<'_>, owned: bool, invited: bool) -> PyResult { + tracing::info!("attempting to list workspaces"); + let this = self.clone(); + crate::a_sync_allow_threads!(py, this.list_workspaces(owned, invited).await) + } + #[pyo3(name = "leave_workspace")] fn pyleave_workspace(&self, id: String) -> bool { self.leave_workspace(id.as_str()) @@ -55,6 +83,18 @@ impl Client { #[pyo3(name = "user_id")] fn pyuser_id(&self) -> String { - self.user_id().to_string() + self.user().id.to_string() + } + + #[pyo3(name = "user_name")] + fn pyuser_name(&self) -> String { + self.user().name.clone() + } + + #[pyo3(name = "refresh")] + fn pyrefresh(&self, py: Python<'_>) -> PyResult { + tracing::info!("attempting to refresh token"); + let this = self.clone(); + crate::a_sync_allow_threads!(py, this.refresh().await) } }