2023-08-20 00:46:55 +02:00
|
|
|
//! ### client
|
|
|
|
//!
|
|
|
|
//! codemp client manager, containing grpc services
|
|
|
|
|
2023-08-16 23:09:47 +02:00
|
|
|
use std::{sync::Arc, collections::BTreeMap};
|
|
|
|
|
|
|
|
use tonic::transport::Channel;
|
|
|
|
|
|
|
|
use crate::{
|
|
|
|
cursor::{worker::CursorControllerWorker, controller::CursorController},
|
|
|
|
proto::{
|
|
|
|
buffer_client::BufferClient, cursor_client::CursorClient, UserIdentity, BufferPayload,
|
|
|
|
},
|
2023-09-03 23:04:08 +02:00
|
|
|
Error, api::ControllerWorker, buffer::{controller::BufferController, worker::BufferControllerWorker},
|
2023-08-16 23:09:47 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2023-08-20 00:46:55 +02:00
|
|
|
/// codemp client manager
|
|
|
|
///
|
|
|
|
/// contains all required grpc services and the unique user id
|
|
|
|
/// will disconnect when dropped
|
|
|
|
/// can be used to interact with server
|
2023-08-17 02:58:55 +02:00
|
|
|
pub struct Client {
|
2023-08-16 23:09:47 +02:00
|
|
|
id: String,
|
2023-08-17 02:58:55 +02:00
|
|
|
client: Services,
|
2023-08-16 23:09:47 +02:00
|
|
|
workspace: Option<Workspace>,
|
|
|
|
}
|
|
|
|
|
2023-08-17 02:58:55 +02:00
|
|
|
struct Services {
|
2023-08-16 23:09:47 +02:00
|
|
|
buffer: BufferClient<Channel>,
|
|
|
|
cursor: CursorClient<Channel>,
|
|
|
|
}
|
|
|
|
|
|
|
|
struct Workspace {
|
|
|
|
cursor: Arc<CursorController>,
|
|
|
|
buffers: BTreeMap<String, Arc<BufferController>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2023-08-17 02:58:55 +02:00
|
|
|
impl Client {
|
2023-08-20 00:46:55 +02:00
|
|
|
/// instantiate and connect a new client
|
2023-08-16 23:09:47 +02:00
|
|
|
pub async fn new(dst: &str) -> Result<Self, tonic::transport::Error> {
|
|
|
|
let buffer = BufferClient::connect(dst.to_string()).await?;
|
|
|
|
let cursor = CursorClient::connect(dst.to_string()).await?;
|
|
|
|
let id = uuid::Uuid::new_v4().to_string();
|
|
|
|
|
2023-08-17 02:58:55 +02:00
|
|
|
Ok(Client { id, client: Services { buffer, cursor}, workspace: None })
|
2023-08-16 23:09:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-20 00:46:55 +02:00
|
|
|
/// return a reference to current cursor controller, if currently in a workspace
|
2023-08-16 23:09:47 +02:00
|
|
|
pub fn get_cursor(&self) -> Option<Arc<CursorController>> {
|
2023-08-17 00:04:37 +02:00
|
|
|
Some(self.workspace.as_ref()?.cursor.clone())
|
2023-08-16 23:09:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-20 00:46:55 +02:00
|
|
|
/// leave current workspace if in one, disconnecting buffer and cursor controllers
|
2023-08-19 04:02:21 +02:00
|
|
|
pub fn leave_workspace(&mut self) {
|
2023-08-19 18:28:27 +02:00
|
|
|
// TODO need to stop tasks?
|
2023-08-19 04:02:21 +02:00
|
|
|
self.workspace = None
|
|
|
|
}
|
|
|
|
|
2023-08-20 00:46:55 +02:00
|
|
|
/// disconnect from a specific buffer
|
2023-08-19 04:02:21 +02:00
|
|
|
pub fn disconnect_buffer(&mut self, path: &str) -> bool {
|
|
|
|
match &mut self.workspace {
|
|
|
|
Some(w) => w.buffers.remove(path).is_some(),
|
|
|
|
None => false,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-20 00:46:55 +02:00
|
|
|
/// get a new reference to a buffer controller, if any is active to given path
|
2023-08-16 23:09:47 +02:00
|
|
|
pub fn get_buffer(&self, path: &str) -> Option<Arc<BufferController>> {
|
2023-08-17 00:04:37 +02:00
|
|
|
self.workspace.as_ref()?.buffers.get(path).cloned()
|
2023-08-16 23:09:47 +02:00
|
|
|
}
|
|
|
|
|
2023-08-20 00:46:55 +02:00
|
|
|
/// join a workspace, starting a cursorcontroller and returning a new reference to it
|
|
|
|
///
|
2023-09-03 23:04:08 +02:00
|
|
|
/// to interact with such workspace [crate::api::Controller::send] cursor events or
|
|
|
|
/// [crate::api::Controller::recv] for events on the associated [crate::cursor::Controller].
|
2023-08-17 02:58:55 +02:00
|
|
|
pub async fn join(&mut self, _session: &str) -> Result<Arc<CursorController>, Error> {
|
2023-08-16 23:09:47 +02:00
|
|
|
// TODO there is no real workspace handling in codemp server so it behaves like one big global
|
|
|
|
// session. I'm still creating this to start laying out the proper use flow
|
|
|
|
let stream = self.client.cursor.listen(UserIdentity { id: "".into() }).await?.into_inner();
|
|
|
|
|
|
|
|
let controller = CursorControllerWorker::new(self.id.clone());
|
|
|
|
let client = self.client.cursor.clone();
|
|
|
|
|
|
|
|
let handle = Arc::new(controller.subscribe());
|
|
|
|
|
|
|
|
tokio::spawn(async move {
|
|
|
|
tracing::debug!("cursor worker started");
|
|
|
|
controller.work(client, stream).await;
|
|
|
|
tracing::debug!("cursor worker stopped");
|
|
|
|
});
|
|
|
|
|
|
|
|
self.workspace = Some(
|
|
|
|
Workspace {
|
|
|
|
cursor: handle.clone(),
|
|
|
|
buffers: BTreeMap::new()
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
Ok(handle)
|
|
|
|
}
|
|
|
|
|
2023-08-20 00:46:55 +02:00
|
|
|
/// create a new buffer in current workspace, with optional given content
|
2023-08-17 02:58:55 +02:00
|
|
|
pub async fn create(&mut self, path: &str, content: Option<&str>) -> Result<(), Error> {
|
2023-08-17 00:04:37 +02:00
|
|
|
if let Some(_workspace) = &self.workspace {
|
2023-08-16 23:09:47 +02:00
|
|
|
self.client.buffer
|
|
|
|
.create(BufferPayload {
|
|
|
|
user: self.id.clone(),
|
|
|
|
path: path.to_string(),
|
|
|
|
content: content.map(|x| x.to_string()),
|
|
|
|
}).await?;
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
} else {
|
2023-08-17 02:58:55 +02:00
|
|
|
Err(Error::InvalidState { msg: "join a workspace first".into() })
|
2023-08-16 23:09:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-08-20 00:46:55 +02:00
|
|
|
/// attach to a buffer, starting a buffer controller and returning a new reference to it
|
|
|
|
///
|
2023-09-03 23:04:08 +02:00
|
|
|
/// to interact with such buffer [crate::api::Controller::send] operation sequences
|
|
|
|
/// or [crate::api::Controller::recv] for text events using its [crate::buffer::Controller].
|
2023-08-20 00:46:55 +02:00
|
|
|
/// to generate operation sequences use the [crate::buffer::OperationFactory]
|
|
|
|
/// methods, which are implemented on [crate::buffer::Controller], such as
|
|
|
|
/// [crate::buffer::OperationFactory::delta].
|
2023-08-17 02:58:55 +02:00
|
|
|
pub async fn attach(&mut self, path: &str) -> Result<Arc<BufferController>, Error> {
|
2023-08-16 23:09:47 +02:00
|
|
|
if let Some(workspace) = &mut self.workspace {
|
|
|
|
let mut client = self.client.buffer.clone();
|
|
|
|
let req = BufferPayload {
|
|
|
|
path: path.to_string(), user: self.id.clone(), content: None
|
|
|
|
};
|
|
|
|
|
|
|
|
let content = client.sync(req.clone()).await?.into_inner().content;
|
|
|
|
|
|
|
|
let stream = client.attach(req).await?.into_inner();
|
|
|
|
|
|
|
|
let controller = BufferControllerWorker::new(self.id.clone(), &content, path);
|
|
|
|
let handler = Arc::new(controller.subscribe());
|
|
|
|
|
|
|
|
let _path = path.to_string();
|
|
|
|
tokio::spawn(async move {
|
|
|
|
tracing::debug!("buffer[{}] worker started", _path);
|
|
|
|
controller.work(client, stream).await;
|
|
|
|
tracing::debug!("buffer[{}] worker stopped", _path);
|
|
|
|
});
|
|
|
|
|
|
|
|
workspace.buffers.insert(path.to_string(), handler.clone());
|
|
|
|
|
|
|
|
Ok(handler)
|
|
|
|
} else {
|
2023-08-17 02:58:55 +02:00
|
|
|
Err(Error::InvalidState { msg: "join a workspace first".into() })
|
2023-08-16 23:09:47 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|