From 610576f8b77069600f02438359aa7a249f4e0756 Mon Sep 17 00:00:00 2001 From: cschen Date: Fri, 16 Aug 2024 12:58:43 +0200 Subject: [PATCH] feat: Bumping to pyo3 0.22, removing pyo3-asyncio, moving code around to allow for smoother pyo3 aggregation. --- Cargo.toml | 5 +- src/api/change.rs | 22 ++-- src/api/event.rs | 9 +- src/buffer/controller.rs | 9 +- src/buffer/worker.rs | 2 +- src/ffi/python/client.rs | 6 +- src/ffi/python/controllers.rs | 153 ++++++++----------------- src/ffi/python/mod.rs | 5 +- src/ffi/python/workspace.rs | 207 +++++++++++++++++----------------- src/workspace/worker.rs | 69 ++++++++---- 10 files changed, 227 insertions(+), 260 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b08e72c..1ec49fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,8 +40,7 @@ napi = { version = "2.16", features = ["full"], optional = true } napi-derive = { version="2.16", optional = true} # glue (python) -pyo3 = { version = "0.20", features = ["extension-module"], optional = true} -pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"], optional = true } +pyo3 = { version = "0.22", features = ["extension-module", "experimental-async"], optional = true} [build-dependencies] # glue (js) @@ -54,4 +53,4 @@ default = [] lua = ["mlua", "lazy_static", "tracing-subscriber"] java = ["lazy_static", "jni", "tracing-subscriber"] js = ["napi-build", "tracing-subscriber", "napi", "napi-derive"] -python = ["pyo3", "pyo3-asyncio", "tracing-subscriber", "pyo3-build-config"] +python = ["pyo3", "tracing-subscriber", "pyo3-build-config"] diff --git a/src/api/change.rs b/src/api/change.rs index 8a6c33c..a040bcc 100644 --- a/src/api/change.rs +++ b/src/api/change.rs @@ -20,8 +20,7 @@ /// #[derive(Clone, Debug, Default)] #[cfg_attr(feature = "js", napi_derive::napi(object))] -#[cfg_attr(feature = "python", pyo3::pyclass)] -#[cfg_attr(feature = "python", pyo3(get_all))] +#[cfg_attr(feature = "python", pyo3::pyclass(get_all))] pub struct TextChange { /// range start of text change, as char indexes in buffer previous state pub start: u32, @@ -37,7 +36,10 @@ impl TextChange { pub fn span(&self) -> std::ops::Range { self.start as usize..self.end as usize } +} +#[cfg_attr(feature = "python", pyo3::pymethods)] +impl TextChange { /// returns true if this TextChange deletes existing text pub fn is_delete(&self) -> bool { self.start < self.end @@ -52,12 +54,12 @@ impl TextChange { pub fn is_empty(&self) -> bool { !self.is_delete() && !self.is_insert() } - + /// applies this text change to given text, returning a new string pub fn apply(&self, txt: &str) -> String { - let pre_index = std::cmp::min(self.span().start, txt.len()); + let pre_index = std::cmp::min(self.start as usize, txt.len()); let pre = txt.get(..pre_index).unwrap_or("").to_string(); - let post = txt.get(self.span().end..).unwrap_or("").to_string(); + let post = txt.get(self.end as usize..).unwrap_or("").to_string(); format!("{}{}{}", pre, self.content, post) } } @@ -70,7 +72,7 @@ mod tests { start: 5, end: 5, content: " cruel".to_string(), - hash: None + hash: None, }; let result = change.apply("hello world!"); assert_eq!(result, "hello cruel world!"); @@ -82,7 +84,7 @@ mod tests { start: 5, end: 11, content: "".to_string(), - hash: None + hash: None, }; let result = change.apply("hello cruel world!"); assert_eq!(result, "hello world!"); @@ -94,7 +96,7 @@ mod tests { start: 5, end: 11, content: " not very pleasant".to_string(), - hash: None + hash: None, }; let result = change.apply("hello cruel world!"); assert_eq!(result, "hello not very pleasant world!"); @@ -106,7 +108,7 @@ mod tests { start: 100, end: 110, content: "a very long string \n which totally matters".to_string(), - hash: None + hash: None, }; let result = change.apply("a short text"); assert_eq!( @@ -121,7 +123,7 @@ mod tests { start: 42, end: 42, content: "".to_string(), - hash: None + hash: None, }; let result = change.apply("some important text"); assert_eq!(result, "some important text"); diff --git a/src/api/event.rs b/src/api/event.rs index 31cfad0..f943af3 100644 --- a/src/api/event.rs +++ b/src/api/event.rs @@ -1,7 +1,8 @@ use codemp_proto::workspace::workspace_event::Event as WorkspaceEventInner; +#[cfg_attr(feature = "python", pyo3::pyclass)] pub enum Event { - FileTreeUpdated, + FileTreeUpdated(String), UserJoin(String), UserLeave(String), } @@ -11,9 +12,9 @@ impl From<&WorkspaceEventInner> for Event { match event { WorkspaceEventInner::Join(e) => Self::UserJoin(e.user.id.clone()), WorkspaceEventInner::Leave(e) => Self::UserLeave(e.user.id.clone()), - WorkspaceEventInner::Create(_) - | WorkspaceEventInner::Rename(_) - | WorkspaceEventInner::Delete(_) => Self::FileTreeUpdated, + WorkspaceEventInner::Create(e) => Self::FileTreeUpdated(e.path.clone()), + WorkspaceEventInner::Delete(e) => Self::FileTreeUpdated(e.path.clone()), + WorkspaceEventInner::Rename(e) => Self::FileTreeUpdated(e.after.clone()), } } } diff --git a/src/buffer/controller.rs b/src/buffer/controller.rs index 239a38b..9eee7f5 100644 --- a/src/buffer/controller.rs +++ b/src/buffer/controller.rs @@ -5,7 +5,7 @@ use std::sync::Arc; use diamond_types::LocalVersion; -use tokio::sync::{oneshot, mpsc, watch}; +use tokio::sync::{mpsc, oneshot, watch}; use tonic::async_trait; use crate::api::Controller; @@ -37,7 +37,9 @@ impl BufferController { let (tx, rx) = oneshot::channel(); self.0.content_request.send(tx).await?; let content = rx.await?; - self.0.last_update.set(self.0.latest_version.borrow().clone()); + self.0 + .last_update + .set(self.0.latest_version.borrow().clone()); Ok(content) } } @@ -51,7 +53,8 @@ pub(crate) struct BufferControllerInner { pub(crate) poller: mpsc::UnboundedSender>, pub(crate) stopper: mpsc::UnboundedSender<()>, // just exist pub(crate) content_request: mpsc::Sender>, - pub(crate) delta_request: mpsc::Sender<(LocalVersion, oneshot::Sender<(LocalVersion, TextChange)>)>, + pub(crate) delta_request: + mpsc::Sender<(LocalVersion, oneshot::Sender<(LocalVersion, TextChange)>)>, } #[async_trait] diff --git a/src/buffer/worker.rs b/src/buffer/worker.rs index 14e3913..505dc38 100644 --- a/src/buffer/worker.rs +++ b/src/buffer/worker.rs @@ -101,7 +101,7 @@ impl ControllerWorker for BufferWorker { let last_ver = oplog.local_version(); if change.is_delete() { - branch.delete_without_content(&mut oplog, 1, change.span()); + branch.delete_without_content(&mut oplog, 1, change.start as usize..change.end as usize); } if change.is_insert() { diff --git a/src/ffi/python/client.rs b/src/ffi/python/client.rs index 8a9df71..d9e4a6d 100644 --- a/src/ffi/python/client.rs +++ b/src/ffi/python/client.rs @@ -10,10 +10,8 @@ use pyo3::prelude::*; #[pymethods] impl Client { #[new] - fn pyconnect(host: String, username: String, password: String) -> PyResult { - let cli = - pyo3_asyncio::tokio::get_runtime().block_on(Client::new(host, username, password)); - Ok(cli?) + async fn pyconnect(host: String, username: String, password: String) -> PyResult { + Ok(Client::new(host, username, password)); } #[pyo3(name = "join_workspace")] diff --git a/src/ffi/python/controllers.rs b/src/ffi/python/controllers.rs index 68d5e1e..359c9de 100644 --- a/src/ffi/python/controllers.rs +++ b/src/ffi/python/controllers.rs @@ -4,62 +4,80 @@ use crate::api::TextChange; use crate::buffer::Controller as BufferController; use crate::cursor::Controller as CursorController; use pyo3::prelude::*; -use pyo3_asyncio::tokio::future_into_py; - -// use super::CodempController; +// need to do manually since Controller is a trait implementation #[pymethods] impl CursorController { #[pyo3(name = "send")] - pub fn pysend<'p>( - &self, - py: Python<'p>, - path: String, - start: (i32, i32), - end: (i32, i32), - ) -> PyResult<&'p PyAny> { - let rc = self.clone(); + async fn pysend(&self, path: String, start: (i32, i32), end: (i32, i32)) -> crate::Result<()> { let pos = Cursor { start, end, buffer: path, user: None, }; - let rc = self.clone(); - future_into_py(py, async move { Ok(rc.send(pos).await?) }) + self.send(pos).await } #[pyo3(name = "try_recv")] - pub fn pytry_recv<'p>(&self, py: Python<'p>) -> PyResult<&'p PyAny> { - //PyResult>> - let rc = self.clone(); - - future_into_py(py, async move { Ok(rc.try_recv().await?) }) + async fn pytry_recv(&self) -> crate::Result> { + self.try_recv().await } #[pyo3(name = "recv")] - pub fn pyrecv<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> { - let rc = self.clone(); - - future_into_py(py, async move { - let cur_event: Cursor = rc.recv().await?; - Python::with_gil(|py| Py::new(py, cur_event)) - }) + async fn pyrecv(&self) -> crate::Result { + self.recv().await } #[pyo3(name = "poll")] - pub fn pypoll<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> { - let rc = self.clone(); - - future_into_py(py, async move { Ok(rc.poll().await?) }) + async fn pypoll(&self) -> crate::Result<()> { + self.poll().await } #[pyo3(name = "stop")] - pub fn pystop(&self) -> bool { + fn pystop(&self) -> bool { self.stop() } } +// need to do manually since Controller is a trait implementation +#[pymethods] +impl BufferController { + #[pyo3(name = "content")] + async fn pycontent(&self) -> crate::Result { + self.content().await + } + + #[pyo3(name = "send")] + async fn pysend(&self, start: u32, end: u32, txt: String) -> crate::Result<()> { + let op = TextChange { + start, + end, + content: txt, + hash: None, + }; + self.send(op).await + } + + #[pyo3(name = "try_recv")] + async fn pytry_recv(&self) -> crate::Result> { + self.try_recv().await + } + + #[pyo3(name = "recv")] + async fn pyrecv(&self) -> crate::Result { + self.recv().await + } + + #[pyo3(name = "poll")] + async fn pypoll(&self) -> crate::Result<()> { + self.poll().await + } +} + +// We have to write this manually since +// cursor.user has type Option which cannot be translated +// automatically #[pymethods] impl Cursor { #[getter(start)] @@ -85,78 +103,3 @@ impl Cursor { } } } - -#[pymethods] -impl BufferController { - #[pyo3(name = "content")] - fn pycontent<'p>(&self, py: Python<'p>) -> PyResult<&'p PyAny> { - let rc = self.clone(); - future_into_py(py, async move { Ok(rc.content().await?) }) - } - - #[pyo3(name = "send")] - fn pysend<'p>(&self, py: Python<'p>, start: u32, end: u32, txt: String) -> PyResult<&'p PyAny> { - let op = TextChange { - start, - end, - content: txt, - hash: None, - }; - let rc = self.clone(); - future_into_py(py, async move { Ok(rc.send(op).await?) }) - } - - #[pyo3(name = "try_recv")] - fn pytry_recv<'p>(&self, py: Python<'p>) -> PyResult<&'p PyAny> { - // match self.try_recv()? { - // Some(txt_change) => { - // let evt = txt_change; - // Ok(evt.into_py(py)) - // } - // None => Ok(py.None()), - // } - let rc = self.clone(); - - future_into_py(py, async move { Ok(rc.try_recv().await?) }) - } - - #[pyo3(name = "recv")] - fn pyrecv<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> { - let rc = self.clone(); - - future_into_py(py, async move { - let txt_change: TextChange = rc.recv().await?; - Python::with_gil(|py| Py::new(py, txt_change)) - }) - } - - #[pyo3(name = "poll")] - fn pypoll<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> { - let rc = self.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { Ok(rc.poll().await?) }) - } -} - -#[pymethods] -impl TextChange { - #[pyo3(name = "is_deletion")] - fn pyis_deletion(&self) -> bool { - self.is_delete() - } - - #[pyo3(name = "is_addition")] - fn pyis_addition(&self) -> bool { - self.is_insert() - } - - #[pyo3(name = "is_empty")] - fn pyis_empty(&self) -> bool { - self.is_empty() - } - - #[pyo3(name = "apply")] - fn pyapply(&self, txt: &str) -> String { - self.apply(txt) - } -} diff --git a/src/ffi/python/mod.rs b/src/ffi/python/mod.rs index 9c1b9e2..146266e 100644 --- a/src/ffi/python/mod.rs +++ b/src/ffi/python/mod.rs @@ -82,9 +82,8 @@ impl PyLogger { } } - fn listen<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> { - let rc = self.0.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { Ok(rc.lock().await.recv().await) }) + async fn listen(&self) -> Option { + self.0.lock().await.recv().await } } diff --git a/src/ffi/python/workspace.rs b/src/ffi/python/workspace.rs index 425301d..41d1f0d 100644 --- a/src/ffi/python/workspace.rs +++ b/src/ffi/python/workspace.rs @@ -1,127 +1,126 @@ -use crate::buffer::Controller as BufferController; -use crate::cursor::Controller as CursorController; -use crate::workspace::Workspace; -use pyo3::prelude::*; -use pyo3::types::PyString; -use pyo3_asyncio::generic::future_into_py; +// use crate::buffer::Controller as BufferController; +// use crate::cursor::Controller as CursorController; +// use crate::workspace::Workspace; +// use pyo3::prelude::*; +// use pyo3::types::PyString; -#[pymethods] -impl Workspace { - // join a workspace - #[pyo3(name = "create")] - fn pycreate<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&'p PyAny> { - let ws = self.clone(); +// #[pymethods] +// impl Workspace { +// // join a workspace +// #[pyo3(name = "create")] +// fn pycreate<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&'p PyAny> { +// let ws = self.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - ws.create(path.as_str()).await?; - Ok(()) - }) - } - #[pyo3(name = "attach")] - fn pyattach<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> { - let ws = self.clone(); +// pyo3_asyncio::tokio::future_into_py(py, async move { +// ws.create(path.as_str()).await?; +// Ok(()) +// }) +// } +// #[pyo3(name = "attach")] +// fn pyattach<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> { +// let ws = self.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - let buffctl: BufferController = ws.attach(path.as_str()).await?; - Ok(buffctl) - // Python::with_gil(|py| Py::new(py, buffctl)) - }) - } +// pyo3_asyncio::tokio::future_into_py(py, async move { +// let buffctl: BufferController = ws.attach(path.as_str()).await?; +// Ok(buffctl) +// // Python::with_gil(|py| Py::new(py, buffctl)) +// }) +// } - #[pyo3(name = "detach")] - fn pydetach(&self, path: String) -> bool { - match self.detach(path.as_str()) { - crate::workspace::worker::DetachResult::NotAttached => false, - crate::workspace::worker::DetachResult::Detaching => true, - crate::workspace::worker::DetachResult::AlreadyDetached => true, - } - } +// #[pyo3(name = "detach")] +// fn pydetach(&self, path: String) -> bool { +// match self.detach(path.as_str()) { +// crate::workspace::worker::DetachResult::NotAttached => false, +// crate::workspace::worker::DetachResult::Detaching => true, +// crate::workspace::worker::DetachResult::AlreadyDetached => true, +// } +// } - // #[pyo3(name = "event")] - // fn pyevent(&self, py: Python<'_>, path: String) -> PyResult<&PyAny> { - // let rc = self.clone(); - // future_into_py(py, async move { Ok(rc.event().await?) }) - // } +// // #[pyo3(name = "event")] +// // fn pyevent(&self, py: Python<'_>, path: String) -> PyResult<&PyAny> { +// // let rc = self.clone(); +// // future_into_py(py, async move { Ok(rc.event().await?) }) +// // } - #[pyo3(name = "fetch_buffers")] - fn pyfetch_buffers<'p>(&'p self, py: Python<'p>) -> PyResult<&PyAny> { - let ws = self.clone(); +// #[pyo3(name = "fetch_buffers")] +// fn pyfetch_buffers<'p>(&'p self, py: Python<'p>) -> PyResult<&PyAny> { +// let ws = self.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - ws.fetch_buffers().await?; - Ok(()) - }) - } +// pyo3_asyncio::tokio::future_into_py(py, async move { +// ws.fetch_buffers().await?; +// Ok(()) +// }) +// } - #[pyo3(name = "fetch_users")] - fn pyfetch_users<'p>(&'p self, py: Python<'p>) -> PyResult<&PyAny> { - let ws = self.clone(); +// #[pyo3(name = "fetch_users")] +// fn pyfetch_users<'p>(&'p self, py: Python<'p>) -> PyResult<&PyAny> { +// let ws = self.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - ws.fetch_users().await?; - Ok(()) - }) - } +// pyo3_asyncio::tokio::future_into_py(py, async move { +// ws.fetch_users().await?; +// Ok(()) +// }) +// } - #[pyo3(name = "list_buffer_users")] - fn pylist_buffer_users<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> { - let ws = self.clone(); +// #[pyo3(name = "list_buffer_users")] +// fn pylist_buffer_users<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> { +// let ws = self.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - let usrlist: Vec = ws - .list_buffer_users(path.as_str()) - .await? - .into_iter() - .map(|e| e.id) - .collect(); +// pyo3_asyncio::tokio::future_into_py(py, async move { +// let usrlist: Vec = ws +// .list_buffer_users(path.as_str()) +// .await? +// .into_iter() +// .map(|e| e.id) +// .collect(); - Ok(usrlist) - }) - } +// Ok(usrlist) +// }) +// } - #[pyo3(name = "delete")] - fn pydelete<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> { - let ws = self.clone(); +// #[pyo3(name = "delete")] +// fn pydelete<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> { +// let ws = self.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - ws.delete(path.as_str()).await?; - Ok(()) - }) - } +// pyo3_asyncio::tokio::future_into_py(py, async move { +// ws.delete(path.as_str()).await?; +// Ok(()) +// }) +// } - #[pyo3(name = "id")] - fn pyid(&self, py: Python<'_>) -> Py { - PyString::new(py, self.id().as_str()).into() - } +// #[pyo3(name = "id")] +// fn pyid(&self, py: Python<'_>) -> Py { +// PyString::new(py, self.id().as_str()).into() +// } - #[pyo3(name = "cursor")] - fn pycursor(&self, py: Python<'_>) -> PyResult> { - Py::new(py, self.cursor()) - } +// #[pyo3(name = "cursor")] +// fn pycursor(&self, py: Python<'_>) -> PyResult> { +// Py::new(py, self.cursor()) +// } - #[pyo3(name = "buffer_by_name")] - fn pybuffer_by_name( - &self, - py: Python<'_>, - path: String, - ) -> PyResult>> { - let Some(bufctl) = self.buffer_by_name(path.as_str()) else { - return Ok(None); - }; +// #[pyo3(name = "buffer_by_name")] +// fn pybuffer_by_name( +// &self, +// py: Python<'_>, +// path: String, +// ) -> PyResult>> { +// let Some(bufctl) = self.buffer_by_name(path.as_str()) else { +// return Ok(None); +// }; - Ok(Some(Py::new(py, bufctl)?)) - } +// Ok(Some(Py::new(py, bufctl)?)) +// } - #[pyo3(name = "buffer_list")] - fn pybuffer_list(&self) -> Vec { - self.buffer_list() - } +// #[pyo3(name = "buffer_list")] +// fn pybuffer_list(&self) -> Vec { +// self.buffer_list() +// } - #[pyo3(name = "filetree")] - fn pyfiletree(&self) -> Vec { - self.filetree() - } -} +// #[pyo3(name = "filetree")] +// fn pyfiletree(&self) -> Vec { +// self.filetree() +// } +// } // #[pyclass] // enum PyEvent { diff --git a/src/workspace/worker.rs b/src/workspace/worker.rs index 67e1739..94cb174 100644 --- a/src/workspace/worker.rs +++ b/src/workspace/worker.rs @@ -6,9 +6,8 @@ use crate::{ }; use codemp_proto::{ - common::Empty, auth::Token, - common::Identity, + common::Empty, files::BufferNode, workspace::{ workspace_event::{ @@ -54,14 +53,12 @@ impl Workspace { token: Token, ) -> crate::Result { let services = Services::try_new(dest, token).await?; - let ws_stream = services.ws() - .attach(Empty{}) - .await? - .into_inner(); + let ws_stream = services.ws().attach(Empty {}).await?.into_inner(); let (tx, rx) = mpsc::channel(256); let (ev_tx, ev_rx) = mpsc::unbounded_channel(); - let cur_stream = services.cur() + let cur_stream = services + .cur() .attach(tokio_stream::wrappers::ReceiverStream::new(rx)) .await? .into_inner(); @@ -92,7 +89,11 @@ impl Workspace { Ok(ws) } - pub(crate) fn run_actor(&self, mut stream: Streaming, tx: mpsc::UnboundedSender) { + pub(crate) fn run_actor( + &self, + mut stream: Streaming, + tx: mpsc::UnboundedSender, + ) { // TODO for buffer and cursor controller we invoke the tokio::spawn outside, but here inside..? let inner = self.0.clone(); let name = self.id(); @@ -109,7 +110,9 @@ impl Workspace { match ev { // user WorkspaceEventInner::Join(UserJoin { user }) => { - inner.users.insert(user.clone().into(), User { id: user.into() }); + inner + .users + .insert(user.clone().into(), User { id: user.into() }); } WorkspaceEventInner::Leave(UserLeave { user }) => { inner.users.remove(&user.into()); @@ -132,12 +135,15 @@ impl Workspace { if tx.send(update).is_err() { tracing::warn!("no active controller to receive workspace event"); } - }, + } } } }); } +} +#[cfg_attr(feature = "python", pyo3::pymethods)] //LMAO it works +impl Workspace { /// create a new buffer in current workspace pub async fn create(&self, path: &str) -> crate::Result<()> { let mut workspace_client = self.0.services.ws(); @@ -175,10 +181,7 @@ impl Workspace { tonic::metadata::MetadataValue::try_from(credentials.id.id) .expect("could not represent path as byte sequence"), ); - let stream = self.0.services.buf() - .attach(req) - .await? - .into_inner(); + let stream = self.0.services.buf().attach(req).await?.into_inner(); let worker = BufferWorker::new(self.0.user_id, path); let controller = worker.controller(); @@ -206,17 +209,24 @@ impl Workspace { pub fn detach(&self, path: &str) -> DetachResult { match self.0.buffers.remove(path) { None => DetachResult::NotAttached, - Some((_name, controller)) => if controller.stop() { - DetachResult::Detaching - } else { - DetachResult::AlreadyDetached + Some((_name, controller)) => { + if controller.stop() { + DetachResult::Detaching + } else { + DetachResult::AlreadyDetached + } } } } /// await next workspace [crate::api::Event] and return it pub async fn event(&self) -> crate::Result { - self.0.events.lock().await.recv().await + self.0 + .events + .lock() + .await + .recv() + .await .ok_or(crate::Error::Channel { send: false }) } @@ -261,7 +271,7 @@ impl Workspace { /// get a list of the users attached to a specific buffer /// /// TODO: discuss implementation details - pub async fn list_buffer_users(&self, path: &str) -> crate::Result> { + pub async fn list_buffer_users(&self, path: &str) -> crate::Result> { let mut workspace_client = self.0.services.ws(); let buffer_users = workspace_client .list_buffer_users(tonic::Request::new(BufferNode { @@ -269,7 +279,10 @@ impl Workspace { })) .await? .into_inner() - .users; + .users + .into_iter() + .map(|u| u.id) + .collect(); Ok(buffer_users) } @@ -313,7 +326,11 @@ impl Workspace { /// get a list of all the currently attached to buffers // #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120 pub fn buffer_list(&self) -> Vec { - self.0.buffers.iter().map(|elem| elem.key().clone()).collect() + self.0 + .buffers + .iter() + .map(|elem| elem.key().clone()) + .collect() } /// get the currently cached "filetree" @@ -327,7 +344,11 @@ impl Drop for WorkspaceInner { fn drop(&mut self) { for entry in self.buffers.iter() { if !entry.value().stop() { - tracing::warn!("could not stop buffer worker {} for workspace {}", entry.value().name(), self.id); + tracing::warn!( + "could not stop buffer worker {} for workspace {}", + entry.value().name(), + self.id + ); } } if !self.cursor.stop() { @@ -336,6 +357,8 @@ impl Drop for WorkspaceInner { } } +#[cfg_attr(feature = "python", pyo3::pyclass(eq, eq_int))] +#[cfg_attr(feature = "python", derive(PartialEq))] pub enum DetachResult { NotAttached, Detaching,