From bfad81f958bc1f3ce2d95c5ad1b40dff542c4464 Mon Sep 17 00:00:00 2001 From: Camillo Schenone Date: Wed, 23 Aug 2023 18:22:11 +0200 Subject: [PATCH] Implemented the client handle interface. TODO: controllers Former-commit-id: 6b273dffce4e28a1e41f31a71f5a4ce78ecfe264 --- Cargo.toml | 2 +- src/lib.rs | 338 ++++++++++++++++++++++++++++++----------------------- 2 files changed, 190 insertions(+), 150 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 9e8f171..61fb93d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ name = "codemp_client" crate-type = ["cdylib"] [dependencies] -codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", tag = "v0.4.4" } +codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", tag = "v0.4.4"} pyo3 = { version = "0.19", features = ["extension-module"] } pyo3-asyncio = { version = "0.19", features = ["tokio-runtime"] } tokio = "1.29.1" diff --git a/src/lib.rs b/src/lib.rs index e957cd6..240fa27 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,14 +1,12 @@ -use std::{sync::Arc, error::Error, format}; +use std::{sync::Arc, format}; -use codemp::prelude::*; +use codemp::{prelude::*}; use codemp::errors::Error as CodempError; -use tokio::sync::Mutex; - use pyo3::{ prelude::*, - exceptions::{PyConnectionError, PyRuntimeError}, - types::{PyBool, PyString} + exceptions::{PyConnectionError, PyRuntimeError, PyBaseException}, + types::PyString }; struct PyCodempError(CodempError); @@ -18,7 +16,7 @@ impl From:: for PyCodempError { } } -impl std::convert::From for PyErr { +impl From for PyErr { fn from(err: PyCodempError) -> PyErr { match err.0 { CodempError::Transport { status, message } => { @@ -29,227 +27,269 @@ impl std::convert::From for PyErr { }, CodempError::InvalidState { msg } => { PyRuntimeError::new_err(format!("Invalid state: {}", msg)) + }, + CodempError::Filler { message } => { + PyBaseException::new_err(format!("Generic error: {}", message)) } } } } #[pyfunction] -fn connect<'a>(py: Python<'a>, dest: String) -> PyResult<&'a PyAny> { - // construct a python coroutine - pyo3_asyncio::tokio::future_into_py(py, async move { - match CodempClient::new(dest.as_str()).await { - Ok(c) => { - Python::with_gil(|py|{ - let cc: PyClientHandle = c.into(); - let handle = Py::new(py, cc)?; - Ok(handle) - }) - }, - Err(e) => { Err(PyConnectionError::new_err(e.source().unwrap().to_string())) } - } +fn codemp_init<'a>(py: Python<'a>) -> PyResult> { + let py_instance: PyClientHandle = CodempInstance::default().into(); + Python::with_gil(|py| { + Ok(Py::new(py, py_instance)?) }) } #[pyclass] #[derive(Clone)] -struct PyClientHandle(Arc>); +struct PyClientHandle(Arc); -impl From:: for PyClientHandle { - fn from(value: CodempClient) -> Self { - PyClientHandle(Arc::new(Mutex::new(value))) +impl From:: for PyClientHandle { + fn from(value: CodempInstance) -> Self { + PyClientHandle(Arc::new(value)) } } #[pymethods] impl PyClientHandle { - fn get_id<'a>(&self, py: Python<'a>) -> PyResult<&'a PyAny> { + fn connect<'a>(&'a self, py: Python<'a>, addr: String) ->PyResult<&'a PyAny> { let rc = self.0.clone(); + pyo3_asyncio::tokio::future_into_py(py, async move { - let binding = rc.lock().await; + rc.connect(addr.as_str()).await.map_err(PyCodempError::from)?; + Ok(()) + }) + } + + fn create<'a>(&'a self, py: Python<'a>, path: String, content: Option) -> PyResult<&'a PyAny> { + let rc = self.0.clone(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + rc.create(path.as_str(), content.as_deref()) + .await + .map_err(PyCodempError::from)?; + Ok(()) + }) + } + + fn attach<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { + let rc = self.0.clone(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + let buffctrl = rc.attach(path.as_str()) + .await + .map_err(PyCodempError::from)?; Python::with_gil(|py| { - let id: Py = PyString::new(py, binding.id()).into(); - Ok(id) + Ok(Py::new(py, PyBufferController(buffctrl))?) }) }) } - fn create<'a>(&self, py: Python<'a>, path: String, content: Option) -> PyResult<&'a PyAny> { + fn join<'a>(&'a self, py: Python<'a>, session: String) -> PyResult<&'a PyAny> { let rc = self.0.clone(); pyo3_asyncio::tokio::future_into_py(py, async move { - match rc.lock().await.create(path, content).await { - Ok(accepted) => { - Python::with_gil(|py| { - let accepted: Py = PyBool::new(py, accepted).into(); - Ok(accepted) - }) - }, - Err(e) => { Err(PyConnectionError::new_err(e.source().unwrap().to_string())) } - } - + let curctrl = rc.join(session.as_str()) + .await + .map_err(PyCodempError::from)?; + + Python::with_gil(|py| { + Ok(Py::new(py, PyCursorController(curctrl))?) + }) + }) + } + + fn get_cursor<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> { + let rc = self.0.clone(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + let curctrl = rc.get_cursor() + .await + .map_err(PyCodempError::from)?; + + Python::with_gil(|py| { + Ok(Py::new(py, PyCursorController(curctrl))?) + }) }) } - fn listen<'a>(&self, py: Python<'a>) -> PyResult<&'a PyAny> { + fn get_buffer<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { let rc = self.0.clone(); pyo3_asyncio::tokio::future_into_py(py, async move { - match rc.lock().await.listen().await { - Ok(controller) => { - Python::with_gil(|py| { - let cc: PyControllerHandle = controller.into(); - let contr = Py::new(py, cc)?; - Ok(contr) - }) - }, - Err(e) => {Err(PyConnectionError::new_err(e.source().unwrap().to_string()))} - } + let buffctrl = rc.get_buffer(path.as_str()) + .await + .map_err(PyCodempError::from)?; + + Python::with_gil(|py| { + Ok(Py::new(py, PyBufferController(buffctrl))?) + }) }) } - fn attach<'a>(&self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { + fn leave_workspace<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> { let rc = self.0.clone(); - let uri = path.clone(); pyo3_asyncio::tokio::future_into_py(py, async move { - match rc.lock().await.attach(uri).await { - Ok(factory) => { - Python::with_gil(|py| { - let ff: PyOperationsHandle = factory.into(); - let fact = Py::new(py, ff)?; - Ok(fact) - }) - }, - Err(e) => {Err(PyConnectionError::new_err(e.source().unwrap().to_string()))} - } + rc.leave_workspace() + .await + .map_err(PyCodempError::from)?; + Ok(()) + }) + } + + fn disconnect_buffer<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { + let rc = self.0.clone(); + + pyo3_asyncio::tokio::future_into_py(py, async move { + rc.disconnect_buffer(path.as_str()) + .await + .map_err(PyCodempError::from)?; + Ok(()) }) } } - -impl From:: for PyControllerHandle { - fn from(value: CursorControllerHandle) -> Self { - PyControllerHandle(value) +impl From:: for PyCursorController { + fn from(value: CodempCursorController) -> Self { + PyCursorController(Arc::new(value)) } } -impl From:: for PyOperationsHandle { - fn from(value: OperationControllerHandle) -> Self { - PyOperationsHandle(value) +impl From:: for PyBufferController { + fn from(value: CodempBufferController) -> Self { + PyBufferController(Arc::new(value)) } } #[pyclass] -struct PyControllerHandle(CursorControllerHandle); +struct PyCursorController(Arc); #[pymethods] -impl PyControllerHandle { - fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py, caller_id: Py) -> PyResult<&'a PyAny> { - let mut rc = self.0.clone(); - let cb = coro_py.clone(); - // We want to start polling the ControlHandle and call the callback every time - // we have something. +impl PyCursorController { + // fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py, caller_id: Py) -> PyResult<&'a PyAny> { + // let mut rc = self.0.clone(); + // let cb = coro_py.clone(); + // // We want to start polling the ControlHandle and call the callback every time + // // we have something. + + // pyo3_asyncio::tokio::future_into_py(py, async move { + // while let Some(op) = rc.poll().await { + // let start = op.start.unwrap_or(Position { row: 0, col: 0}); + // let end = op.end.unwrap_or(Position { row: 0, col: 0}); + + // let cb_fut = Python::with_gil(|py| -> PyResult<_> { + // let args = (op.user, caller_id.clone(), op.buffer, (start.row, start.col), (end.row, end.col)); + // let coro = cb.call1(py, args)?; + // pyo3_asyncio::tokio::into_future(coro.into_ref(py)) + // })?; + + // cb_fut.await?; + // } + // Ok(()) + // }) + // } + + fn send<'a>(&'a self, py: Python<'a>, path: String, start: (i32, i32), end: (i32, i32)) -> PyResult<&'a PyAny> { + let rc = self.0.clone(); + let pos = CodempCursorPosition { + buffer: path, + start: Some(CodempRowCol { row: start.0, col: start.1 }), + end: Some(CodempRowCol { row: end.0, col: end.1 }) + }; pyo3_asyncio::tokio::future_into_py(py, async move { - while let Some(op) = rc.poll().await { - let start = op.start.unwrap_or(Position { row: 0, col: 0}); - let end = op.end.unwrap_or(Position { row: 0, col: 0}); - - let cb_fut = Python::with_gil(|py| -> PyResult<_> { - let args = (op.user, caller_id.clone(), op.buffer, (start.row, start.col), (end.row, end.col)); - let coro = cb.call1(py, args)?; - pyo3_asyncio::tokio::into_future(coro.into_ref(py)) - })?; - - cb_fut.await?; - } + rc.send(pos) + .map_err(PyCodempError::from)?; Ok(()) }) - } // to call after polling cursor movements. - fn send<'a>(&self, py: Python<'a>, path: String, start: (i32, i32), end: (i32, i32)) -> PyResult<&'a PyAny> { + } + + fn recv<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> { let rc = self.0.clone(); pyo3_asyncio::tokio::future_into_py(py, async move { - let startpos = Position { row: start.0, col: start.1 }; - let endpos = Position { row: end.0, col: end.1 }; - - rc.send(path.as_str(), startpos, endpos).await; - Ok(Python::with_gil(|py| py.None())) - }) - - } // when we change the cursor ourselves. -} - -#[pyclass] -struct PyOperationsHandle(OperationControllerHandle); - -#[pymethods] -impl PyOperationsHandle { - fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py, caller_id: Py) -> PyResult<&'a PyAny> { - let mut rc = self.0.clone(); - let cb = coro_py.clone(); - // We want to start polling the ControlHandle and call the callback every time - // we have something. - - pyo3_asyncio::tokio::future_into_py(py, async move { - while let Some(edit) = rc.poll().await { - let start = edit.span.start; - let end = edit.span.end; - let text = edit.content; - - let cb_fut = Python::with_gil(|py| -> PyResult<_> { - let args = (caller_id.clone(), start, end, text); - let coro = cb.call1(py, args)?; - pyo3_asyncio::tokio::into_future(coro.into_ref(py)) - })?; - - cb_fut.await?; - } + let cur_event = rc.recv() + .await + .map_err(PyCodempError::from)?; Ok(()) }) - } //to call after polling text changes + } +} +#[pyclass] +struct PyBufferController(Arc); + +#[pymethods] +impl PyBufferController { + // fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py, caller_id: Py) -> PyResult<&'a PyAny> { + // let mut rc = self.0.clone(); + // let cb = coro_py.clone(); + // // We want to start polling the ControlHandle and call the callback every time + // // we have something. + + // pyo3_asyncio::tokio::future_into_py(py, async move { + // while let Some(edit) = rc.poll().await { + // let start = edit.span.start; + // let end = edit.span.end; + // let text = edit.content; + + // let cb_fut = Python::with_gil(|py| -> PyResult<_> { + // let args = (caller_id.clone(), start, end, text); + // let coro = cb.call1(py, args)?; + // pyo3_asyncio::tokio::into_future(coro.into_ref(py)) + // })?; + + // cb_fut.await?; + // } + // Ok(()) + // }) + // } fn content(&self, py: Python<'_>) -> PyResult> { let cont: Py = PyString::new(py, self.0.content().as_str()).into(); Ok(cont) } - fn apply<'a>(&self, py: Python<'a>, skip: usize, text: String, tail: usize) -> PyResult<&'a PyAny>{ + fn send<'a>(&self, py: Python<'a>, skip: usize, text: String, tail: usize) -> PyResult<&'a PyAny>{ let rc = self.0.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - - match rc.delta(skip, text.as_str(), tail) { - Some(op) => { rc.apply(op).await; Ok(()) }, - None => Err(PyConnectionError::new_err("delta failed")) - } - // if let Some(op) = rc.delta(skip, text.as_str(), tail) { - // rc.apply(op).await; - // Python::with_gil(|py| { - // let accepted: Py = PyBool::new(py, true).into(); - // Ok(accepted) - // }) - // } else { - // Python::with_gil(|py| { - // let accepted: Py = PyBool::new(py, false).into(); - // Ok(accepted) - // }) - // } + Ok(()) }) - } //after making text mofications. + } } +// #[pyclass] +// struct PyCursorEvent { +// user: String, +// buffer: Some(String), +// start: (i32, i32), +// end: (i32, i32) +// } + +// impl From for PyCursorEvent { +// fn from(value: CodempCursorEvent) -> Self { +// PyCursorEvent { +// user: value.user, +// buffer: value.position.buffer, +// start: (value.position.start.row, value.position.start.col), +// end: (value.position.end.row, value.position.end.col) +// } +// } +// } + // Python module #[pymodule] fn codemp_client(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(connect, m)?)?; + m.add_function(wrap_pyfunction!(codemp_init, m)?)?; m.add_class::()?; - m.add_class::()?; - m.add_class::()?; + m.add_class::()?; + m.add_class::()?; Ok(()) } \ No newline at end of file