Implemented the client handle interface. TODO: controllers

Former-commit-id: 6b273dffce4e28a1e41f31a71f5a4ce78ecfe264
This commit is contained in:
Camillo Schenone 2023-08-23 18:22:11 +02:00
parent 31f296a55c
commit bfad81f958
2 changed files with 190 additions and 150 deletions

View file

@ -9,7 +9,7 @@ name = "codemp_client"
crate-type = ["cdylib"] crate-type = ["cdylib"]
[dependencies] [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 = { version = "0.19", features = ["extension-module"] }
pyo3-asyncio = { version = "0.19", features = ["tokio-runtime"] } pyo3-asyncio = { version = "0.19", features = ["tokio-runtime"] }
tokio = "1.29.1" tokio = "1.29.1"

View file

@ -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 codemp::errors::Error as CodempError;
use tokio::sync::Mutex;
use pyo3::{ use pyo3::{
prelude::*, prelude::*,
exceptions::{PyConnectionError, PyRuntimeError}, exceptions::{PyConnectionError, PyRuntimeError, PyBaseException},
types::{PyBool, PyString} types::PyString
}; };
struct PyCodempError(CodempError); struct PyCodempError(CodempError);
@ -18,7 +16,7 @@ impl From::<CodempError> for PyCodempError {
} }
} }
impl std::convert::From<PyCodempError> for PyErr { impl From<PyCodempError> for PyErr {
fn from(err: PyCodempError) -> PyErr { fn from(err: PyCodempError) -> PyErr {
match err.0 { match err.0 {
CodempError::Transport { status, message } => { CodempError::Transport { status, message } => {
@ -29,227 +27,269 @@ impl std::convert::From<PyCodempError> for PyErr {
}, },
CodempError::InvalidState { msg } => { CodempError::InvalidState { msg } => {
PyRuntimeError::new_err(format!("Invalid state: {}", msg)) PyRuntimeError::new_err(format!("Invalid state: {}", msg))
},
CodempError::Filler { message } => {
PyBaseException::new_err(format!("Generic error: {}", message))
} }
} }
} }
} }
#[pyfunction] #[pyfunction]
fn connect<'a>(py: Python<'a>, dest: String) -> PyResult<&'a PyAny> { fn codemp_init<'a>(py: Python<'a>) -> PyResult<Py<PyClientHandle>> {
// construct a python coroutine let py_instance: PyClientHandle = CodempInstance::default().into();
pyo3_asyncio::tokio::future_into_py(py, async move { Python::with_gil(|py| {
match CodempClient::new(dest.as_str()).await { Ok(Py::new(py, py_instance)?)
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())) }
}
}) })
} }
#[pyclass] #[pyclass]
#[derive(Clone)] #[derive(Clone)]
struct PyClientHandle(Arc<Mutex<CodempClient>>); struct PyClientHandle(Arc<CodempInstance>);
impl From::<CodempClient> for PyClientHandle { impl From::<CodempInstance> for PyClientHandle {
fn from(value: CodempClient) -> Self { fn from(value: CodempInstance) -> Self {
PyClientHandle(Arc::new(Mutex::new(value))) PyClientHandle(Arc::new(value))
} }
} }
#[pymethods] #[pymethods]
impl PyClientHandle { 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(); let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move { 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<String>) -> 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| { Python::with_gil(|py| {
let id: Py<PyString> = PyString::new(py, binding.id()).into(); Ok(Py::new(py, PyBufferController(buffctrl))?)
Ok(id)
}) })
}) })
} }
fn create<'a>(&self, py: Python<'a>, path: String, content: Option<String>) -> PyResult<&'a PyAny> { fn join<'a>(&'a self, py: Python<'a>, session: String) -> PyResult<&'a PyAny> {
let rc = self.0.clone(); let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move { pyo3_asyncio::tokio::future_into_py(py, async move {
match rc.lock().await.create(path, content).await { let curctrl = rc.join(session.as_str())
Ok(accepted) => { .await
Python::with_gil(|py| { .map_err(PyCodempError::from)?;
let accepted: Py<PyBool> = PyBool::new(py, accepted).into();
Ok(accepted)
})
},
Err(e) => { Err(PyConnectionError::new_err(e.source().unwrap().to_string())) }
}
Python::with_gil(|py| {
Ok(Py::new(py, PyCursorController(curctrl))?)
})
}) })
} }
fn listen<'a>(&self, py: Python<'a>) -> PyResult<&'a PyAny> { fn get_cursor<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
let rc = self.0.clone(); let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move { pyo3_asyncio::tokio::future_into_py(py, async move {
match rc.lock().await.listen().await { let curctrl = rc.get_cursor()
Ok(controller) => { .await
Python::with_gil(|py| { .map_err(PyCodempError::from)?;
let cc: PyControllerHandle = controller.into();
let contr = Py::new(py, cc)?; Python::with_gil(|py| {
Ok(contr) Ok(Py::new(py, PyCursorController(curctrl))?)
}) })
},
Err(e) => {Err(PyConnectionError::new_err(e.source().unwrap().to_string()))}
}
}) })
} }
fn attach<'a>(&self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { fn get_buffer<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> {
let rc = self.0.clone(); let rc = self.0.clone();
let uri = path.clone();
pyo3_asyncio::tokio::future_into_py(py, async move { pyo3_asyncio::tokio::future_into_py(py, async move {
match rc.lock().await.attach(uri).await { let buffctrl = rc.get_buffer(path.as_str())
Ok(factory) => { .await
Python::with_gil(|py| { .map_err(PyCodempError::from)?;
let ff: PyOperationsHandle = factory.into();
let fact = Py::new(py, ff)?; Python::with_gil(|py| {
Ok(fact) Ok(Py::new(py, PyBufferController(buffctrl))?)
}) })
}, })
Err(e) => {Err(PyConnectionError::new_err(e.source().unwrap().to_string()))} }
}
fn leave_workspace<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
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::<CodempCursorController> for PyCursorController {
impl From::<CursorControllerHandle> for PyControllerHandle { fn from(value: CodempCursorController) -> Self {
fn from(value: CursorControllerHandle) -> Self { PyCursorController(Arc::new(value))
PyControllerHandle(value)
} }
} }
impl From::<OperationControllerHandle> for PyOperationsHandle { impl From::<CodempBufferController> for PyBufferController {
fn from(value: OperationControllerHandle) -> Self { fn from(value: CodempBufferController) -> Self {
PyOperationsHandle(value) PyBufferController(Arc::new(value))
} }
} }
#[pyclass] #[pyclass]
struct PyControllerHandle(CursorControllerHandle); struct PyCursorController(Arc<CodempCursorController>);
#[pymethods] #[pymethods]
impl PyControllerHandle { impl PyCursorController {
fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py<PyAny>, caller_id: Py<PyString>) -> PyResult<&'a PyAny> { // fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py<PyAny>, caller_id: Py<PyString>) -> PyResult<&'a PyAny> {
let mut rc = self.0.clone(); // let mut rc = self.0.clone();
let cb = coro_py.clone(); // let cb = coro_py.clone();
// We want to start polling the ControlHandle and call the callback every time // // We want to start polling the ControlHandle and call the callback every time
// we have something. // // 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 { pyo3_asyncio::tokio::future_into_py(py, async move {
while let Some(op) = rc.poll().await { rc.send(pos)
let start = op.start.unwrap_or(Position { row: 0, col: 0}); .map_err(PyCodempError::from)?;
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(()) 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(); let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move { pyo3_asyncio::tokio::future_into_py(py, async move {
let startpos = Position { row: start.0, col: start.1 }; let cur_event = rc.recv()
let endpos = Position { row: end.0, col: end.1 }; .await
.map_err(PyCodempError::from)?;
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<PyAny>, caller_id: Py<PyString>) -> 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(()) Ok(())
}) })
} //to call after polling text changes }
}
#[pyclass]
struct PyBufferController(Arc<CodempBufferController>);
#[pymethods]
impl PyBufferController {
// fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py<PyAny>, caller_id: Py<PyString>) -> 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<Py<PyString>> { fn content(&self, py: Python<'_>) -> PyResult<Py<PyString>> {
let cont: Py<PyString> = PyString::new(py, self.0.content().as_str()).into(); let cont: Py<PyString> = PyString::new(py, self.0.content().as_str()).into();
Ok(cont) 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(); let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move { pyo3_asyncio::tokio::future_into_py(py, async move {
Ok(())
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> = PyBool::new(py, true).into();
// Ok(accepted)
// })
// } else {
// Python::with_gil(|py| {
// let accepted: Py<PyBool> = PyBool::new(py, false).into();
// Ok(accepted)
// })
// }
}) })
} //after making text mofications. }
} }
// #[pyclass]
// struct PyCursorEvent {
// user: String,
// buffer: Some(String),
// start: (i32, i32),
// end: (i32, i32)
// }
// impl From<CodempCursorEvent> 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 // Python module
#[pymodule] #[pymodule]
fn codemp_client(_py: Python, m: &PyModule) -> PyResult<()> { 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::<PyClientHandle>()?; m.add_class::<PyClientHandle>()?;
m.add_class::<PyControllerHandle>()?; m.add_class::<PyCursorController>()?;
m.add_class::<PyOperationsHandle>()?; m.add_class::<PyBufferController>()?;
Ok(()) Ok(())
} }