codemp/src/client.rs

152 lines
4.6 KiB
Rust
Raw Normal View History

2023-08-20 00:46:55 +02:00
//! ### client
//!
//! codemp client manager, containing grpc services
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,
},
Error, api::controller::ControllerWorker,
buffer::{controller::BufferController, worker::BufferControllerWorker},
};
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
pub struct Client {
id: String,
client: Services,
workspace: Option<Workspace>,
}
struct Services {
buffer: BufferClient<Channel>,
cursor: CursorClient<Channel>,
}
struct Workspace {
cursor: Arc<CursorController>,
buffers: BTreeMap<String, Arc<BufferController>>,
}
impl Client {
2023-08-20 00:46:55 +02:00
/// instantiate and connect a new client
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();
Ok(Client { id, client: Services { buffer, cursor}, workspace: None })
}
2023-08-20 00:46:55 +02:00
/// return a reference to current cursor controller, if currently in a workspace
pub fn get_cursor(&self) -> Option<Arc<CursorController>> {
Some(self.workspace.as_ref()?.cursor.clone())
}
2023-08-20 00:46:55 +02:00
/// leave current workspace if in one, disconnecting buffer and cursor controllers
pub fn leave_workspace(&mut self) {
// TODO need to stop tasks?
self.workspace = None
}
2023-08-20 00:46:55 +02:00
/// disconnect from a specific buffer
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
pub fn get_buffer(&self, path: &str) -> Option<Arc<BufferController>> {
self.workspace.as_ref()?.buffers.get(path).cloned()
}
2023-08-20 00:46:55 +02:00
/// join a workspace, starting a cursorcontroller and returning a new reference to it
///
/// to interact with such workspace [crate::api::Controller::send] cursor events or
/// [crate::api::Controller::recv] for events on the associated [crate::cursor::Controller].
pub async fn join(&mut self, _session: &str) -> crate::Result<Arc<CursorController>> {
// 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
pub async fn create(&mut self, path: &str, content: Option<&str>) -> crate::Result<()> {
if let Some(_workspace) = &self.workspace {
self.client.buffer
.create(BufferPayload {
user: self.id.clone(),
path: path.to_string(),
content: content.map(|x| x.to_string()),
}).await?;
Ok(())
} else {
Err(Error::InvalidState { msg: "join a workspace first".into() })
}
}
2023-08-20 00:46:55 +02:00
/// attach to a buffer, starting a buffer controller and returning a new reference to it
///
2023-11-17 05:47:40 +01:00
/// to interact with such buffer use [crate::api::Controller::send] or
/// [crate::api::Controller::recv] to exchange [crate::api::TextChange]
pub async fn attach(&mut self, path: &str) -> crate::Result<Arc<BufferController>> {
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 stream = client.attach(req).await?.into_inner();
let controller = BufferControllerWorker::new(self.id.clone(), 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 {
Err(Error::InvalidState { msg: "join a workspace first".into() })
}
}
}