mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-22 15:24:48 +01:00
feat: implemented leave workspace and list buffer users, various fixes
This commit is contained in:
parent
6a061ca432
commit
741a074464
13 changed files with 88 additions and 125 deletions
|
@ -10,7 +10,7 @@ name = "codemp"
|
|||
# core
|
||||
tracing = "0.1"
|
||||
# woot
|
||||
codemp-woot = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/woot.git", features = ["serde"], tag = "v0.1.0", optional = true }
|
||||
codemp-woot = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/woot.git", features = ["serde"], tag = "v0.1.1", optional = true }
|
||||
# proto
|
||||
tonic = { version = "0.9", features = ["tls", "tls-roots"], optional = true }
|
||||
prost = { version = "0.11.8", optional = true }
|
||||
|
|
|
@ -14,4 +14,4 @@ message BufferTree {
|
|||
message WorkspaceFileTree {
|
||||
// list of strings may be more efficient but it's a lot more hassle
|
||||
required string payload = 1; // spappolata di json
|
||||
} //Alla fine non si usa questo per ora ma BufferTree
|
||||
}
|
|
@ -6,5 +6,5 @@ package user;
|
|||
// payload identifying user
|
||||
message UserIdentity {
|
||||
// user identifier
|
||||
required string id = 1;
|
||||
required bytes id = 1; //since uuid is 8 bytes we prefer to just send the raw bytes instead of string
|
||||
}
|
||||
|
|
|
@ -68,5 +68,5 @@ message UserList {
|
|||
}
|
||||
|
||||
message WorkspaceDetails {
|
||||
required int32 id=1;
|
||||
required string id = 1;
|
||||
}
|
|
@ -1,27 +1,3 @@
|
|||
// Workspace effimero: sta in /tmp o proprio in memoria
|
||||
// Workspace e` autenticato: come si decide mentre si rifa il server
|
||||
// Workspace ha id univoco (stringa), usato per connettercisi
|
||||
// Workspace implementera` access control:
|
||||
// * accedere al workspace
|
||||
// * i singoli buffer
|
||||
// - i metadati maybe????
|
||||
// Workspace offre le seguenti features:
|
||||
// * listare i buffer DONE
|
||||
// * listare gli user connessi DONE
|
||||
// * creare buffers DONE REPLACE THE ONE ON buffer.proto
|
||||
// * NO ATTACH: responsabilita` del buffer service
|
||||
// * contiene metadata dei buffers:
|
||||
// * path
|
||||
// * data creazione
|
||||
// Buffer id NON E` il path DONE
|
||||
// BufferService NON ha metadata:
|
||||
// Workspace tiene traccia di utenti attached (nel futuro) DONE
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
syntax = "proto2";
|
||||
|
||||
package workspace_service;
|
||||
|
@ -29,11 +5,8 @@ import "user.proto";
|
|||
import "files.proto";
|
||||
import "workspace.proto";
|
||||
|
||||
|
||||
|
||||
service Workspace {
|
||||
|
||||
|
||||
rpc Attach (workspace.AttachRequest) returns (workspace.Token);
|
||||
|
||||
rpc LeaveWorkspace (workspace.WorkspaceDetails) returns (workspace.Empty);
|
||||
|
@ -46,13 +19,10 @@ service Workspace {
|
|||
|
||||
rpc ListUsers (workspace.UserListRequest) returns (workspace.UserList);
|
||||
|
||||
rpc ListBufferUsers (workspace.BufferPayload) returns (workspace.Empty);
|
||||
rpc ListBufferUsers (workspace.BufferPayload) returns (workspace.UserList); //TODO discuss
|
||||
|
||||
rpc Join (workspace.JoinRequest) returns (workspace.Token);
|
||||
|
||||
rpc Delete (workspace.BufferPayload) returns (workspace.Empty); //deletes buffer
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -84,11 +84,11 @@ impl TextChange {
|
|||
|
||||
/// convert from byte index to row and column
|
||||
/// txt must be the whole content of the buffer, in order to count lines
|
||||
pub fn index_to_rowcol(txt: &str, index: usize) -> crate::proto::RowCol {
|
||||
pub fn index_to_rowcol(txt: &str, index: usize) -> RowCol {
|
||||
// FIXME might panic, use .get()
|
||||
let row = txt[..index].matches('\n').count() as i32;
|
||||
let col = txt[..index].split('\n').last().unwrap_or("").len() as i32;
|
||||
crate::proto::RowCol { row, col }
|
||||
RowCol { row, col }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ impl BufferWorker {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ControllerWorker<TextChange> for BufferControllerWorker {
|
||||
impl ControllerWorker<TextChange> for BufferWorker {
|
||||
type Controller = BufferController;
|
||||
type Tx = mpsc::Sender<Operation>;
|
||||
type Rx = Streaming<Operation>;
|
||||
|
|
|
@ -4,7 +4,8 @@
|
|||
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
use tokio::sync::{mpsc, RwLock};
|
||||
use tonic::service::interceptor::InterceptedService;
|
||||
use tonic::service::Interceptor;
|
||||
use tonic::transport::{Channel, Endpoint};
|
||||
|
@ -14,7 +15,7 @@ use crate::api::controller::ControllerWorker;
|
|||
use crate::cursor::worker::CursorWorker;
|
||||
use crate::proto::buffer_service::buffer_client::BufferClient;
|
||||
use crate::proto::cursor_service::cursor_client::CursorClient;
|
||||
use crate::proto::workspace::{JoinRequest, Token};
|
||||
use crate::proto::workspace::{JoinRequest, Token, WorkspaceDetails};
|
||||
use crate::proto::workspace_service::workspace_client::WorkspaceClient;
|
||||
use crate::workspace::Workspace;
|
||||
|
||||
|
@ -26,7 +27,7 @@ use crate::workspace::Workspace;
|
|||
pub struct Client {
|
||||
user_id: Uuid,
|
||||
token_tx: Arc<tokio::sync::watch::Sender<Token>>,
|
||||
pub workspaces: BTreeMap<String, Workspace>,
|
||||
pub workspaces: BTreeMap<String, Arc<RwLock<Workspace>>>,
|
||||
services: Arc<Services>
|
||||
}
|
||||
|
||||
|
@ -62,7 +63,7 @@ fn parse_codemp_connection_string<'a>(string: &'a str) -> (String, String) {
|
|||
|
||||
impl Client {
|
||||
/// instantiate and connect a new client
|
||||
pub async fn new(dest: &str) -> crate::Result<Self> { //TODO interceptor
|
||||
pub async fn new(dest: &str) -> crate::Result<Self> {
|
||||
let (_host, _workspace_id) = parse_codemp_connection_string(dest);
|
||||
|
||||
let channel = Endpoint::from_shared(dest.to_string())?
|
||||
|
@ -89,11 +90,18 @@ impl Client {
|
|||
})
|
||||
}
|
||||
|
||||
/// 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, workspace_id: &str) -> crate::Result<()> {
|
||||
/// creates a new workspace (and joins it implicitly), returns an [tokio::sync::RwLock] to interact with it
|
||||
pub async fn create_workspace(&mut self, workspace_id: &str) -> crate::Result<Arc<RwLock<Workspace>>> {
|
||||
let mut workspace_client = self.services.workspace.clone();
|
||||
workspace_client.create_workspace(
|
||||
tonic::Request::new(WorkspaceDetails { id: workspace_id.to_string() })
|
||||
).await?;
|
||||
|
||||
self.join_workspace(workspace_id).await
|
||||
}
|
||||
|
||||
/// join a workspace, returns an [tokio::sync::RwLock] to interact with it
|
||||
pub async fn join_workspace(&mut self, workspace_id: &str) -> crate::Result<Arc<RwLock<Workspace>>> {
|
||||
self.token_tx.send(self.services.workspace.clone().join(
|
||||
tonic::Request::new(JoinRequest { username: "".to_string(), password: "".to_string() }) //TODO
|
||||
).await?.into_inner())?;
|
||||
|
@ -112,17 +120,32 @@ impl Client {
|
|||
tracing::debug!("controller worker stopped");
|
||||
});
|
||||
|
||||
self.workspaces.insert(workspace_id.to_string(), Workspace::new(
|
||||
let lock = Arc::new(RwLock::new(
|
||||
Workspace::new(
|
||||
workspace_id.to_string(),
|
||||
self.user_id,
|
||||
self.token_tx.clone(),
|
||||
controller,
|
||||
self.services.clone()
|
||||
).await?);
|
||||
).await?
|
||||
));
|
||||
|
||||
self.workspaces.insert(workspace_id.to_string(), lock.clone());
|
||||
|
||||
Ok(lock)
|
||||
}
|
||||
|
||||
/// leave given workspace, disconnecting buffer and cursor controllers
|
||||
pub async fn leave_workspace(&self, workspace_id: &str) -> crate::Result<()> {
|
||||
let mut workspace_client = self.services.workspace.clone();
|
||||
workspace_client.leave_workspace(
|
||||
tonic::Request::new(WorkspaceDetails { id: workspace_id.to_string() })
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// accessor for user id
|
||||
pub fn user_id(&self) -> Uuid {
|
||||
self.user_id.clone()
|
||||
}
|
||||
|
|
|
@ -57,7 +57,7 @@ impl Controller<CursorEvent> for CursorController {
|
|||
std::mem::swap(&mut cursor.start, &mut cursor.end);
|
||||
}
|
||||
Ok(self.op.send(CursorEvent {
|
||||
user: UserIdentity { id: self.user_id.to_string() },
|
||||
user: UserIdentity { id: self.user_id.as_bytes().to_vec() },
|
||||
position: cursor,
|
||||
})?)
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ impl CursorWorker {
|
|||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ControllerWorker<CursorEvent> for CursorControllerWorker {
|
||||
impl ControllerWorker<CursorEvent> for CursorWorker {
|
||||
type Controller = CursorController;
|
||||
type Tx = mpsc::Sender<CursorEvent>;
|
||||
type Rx = Streaming<CursorEvent>;
|
||||
|
@ -58,7 +58,7 @@ impl ControllerWorker<CursorEvent> for CursorControllerWorker {
|
|||
loop {
|
||||
tokio::select!{
|
||||
Ok(Some(cur)) = rx.message() => {
|
||||
if cur.user.id == self.user_id.to_string() { continue }
|
||||
if Uuid::from(cur.user.clone()) == self.user_id { continue }
|
||||
self.channel.send(cur.clone()).unwrap_or_warn("could not broadcast event");
|
||||
self.changed.send(cur).unwrap_or_warn("could not update last event");
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::{BTreeMap, BTreeSet}, str::FromStr, sync::Arc};
|
||||
use std::{collections::{BTreeMap, BTreeSet}, sync::Arc};
|
||||
use tokio::sync::mpsc;
|
||||
use uuid::Uuid;
|
||||
use crate::{
|
||||
|
@ -25,13 +25,11 @@ impl From<Uuid> for UserInfo {
|
|||
|
||||
impl From<UserIdentity> for Uuid {
|
||||
fn from(uid: UserIdentity) -> Uuid {
|
||||
Uuid::from_str(&uid.id).expect("expected an uuid")
|
||||
let b: [u8; 16] = uid.id.try_into().expect("expected an uuid");
|
||||
Uuid::from_bytes(b)
|
||||
}
|
||||
}
|
||||
|
||||
/// list_users -> A() , B()
|
||||
/// get_user_info(B) -> B(cacca, pipu@piu)
|
||||
|
||||
pub struct Workspace {
|
||||
id: String,
|
||||
user_id: Uuid,
|
||||
|
@ -68,10 +66,10 @@ impl Workspace {
|
|||
Ok(ws)
|
||||
}
|
||||
|
||||
/// create a new buffer in current workspace, with optional given content
|
||||
/// create a new buffer in current workspace
|
||||
pub async fn create(&mut self, path: &str) -> crate::Result<()> {
|
||||
let mut workspace_client = self.services.workspace.clone();
|
||||
workspace_client.create(
|
||||
workspace_client.create_buffer(
|
||||
tonic::Request::new(BufferPayload { path: path.to_string() })
|
||||
).await?;
|
||||
|
||||
|
@ -110,6 +108,7 @@ impl Workspace {
|
|||
Ok(controller)
|
||||
}
|
||||
|
||||
/// fetch a list of all buffers in a workspace
|
||||
pub async fn fetch_buffers(&mut self) -> crate::Result<()> {
|
||||
let mut workspace_client = self.services.workspace.clone();
|
||||
let buffers = workspace_client.list_buffers(
|
||||
|
@ -124,6 +123,7 @@ impl Workspace {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// fetch a list of all users in a workspace
|
||||
pub async fn fetch_users(&mut self) -> crate::Result<()> {
|
||||
let mut workspace_client = self.services.workspace.clone();
|
||||
let users = BTreeSet::from_iter(workspace_client.list_users(
|
||||
|
@ -141,10 +141,27 @@ impl Workspace {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_buffer_users() {
|
||||
todo!(); //TODO what is this
|
||||
/// get a list of the users attached to a specific buffer
|
||||
///
|
||||
/// TODO: discuss implementation details
|
||||
pub async fn list_buffer_users(&mut self, path: &str) -> crate::Result<Vec<UserIdentity>> {
|
||||
let mut workspace_client = self.services.workspace.clone();
|
||||
let buffer_users = workspace_client.list_buffer_users(
|
||||
tonic::Request::new(BufferPayload { path: path.to_string() })
|
||||
).await?.into_inner().users;
|
||||
|
||||
Ok(buffer_users)
|
||||
}
|
||||
|
||||
/// detach from a specific buffer, returns false if there
|
||||
pub fn detach(&mut self, path: &str) -> bool {
|
||||
match &mut self.buffers.remove(path) {
|
||||
None => false,
|
||||
Some(_) => true
|
||||
}
|
||||
}
|
||||
|
||||
/// delete a buffer
|
||||
pub async fn delete(&mut self, path: &str) -> crate::Result<()> {
|
||||
let mut workspace_client = self.services.workspace.clone();
|
||||
workspace_client.delete(
|
||||
|
@ -156,60 +173,13 @@ impl Workspace {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// leave current workspace if in one, disconnecting buffer and cursor controllers
|
||||
pub fn leave_workspace(&self) {
|
||||
todo!(); //TODO need proto
|
||||
}
|
||||
|
||||
/// disconnect from a specific buffer
|
||||
pub fn disconnect_buffer(&mut self, path: &str) -> bool {
|
||||
match &mut self.buffers.remove(path) {
|
||||
None => false,
|
||||
Some(_) => true
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> String { self.id.clone() }
|
||||
|
||||
/// return a reference to current cursor controller, if currently in a workspace
|
||||
pub fn cursor(&self) -> Arc<cursor::Controller> { self.cursor.clone() }
|
||||
|
||||
/// get a new reference to a buffer controller, if any is active to given path
|
||||
pub fn buffer_by_name(&self, path: &str) -> Option<Arc<buffer::Controller>> {
|
||||
self.buffers.get(path).cloned()
|
||||
}
|
||||
|
||||
/// return a reference to current cursor controller, if currently in a workspace
|
||||
pub fn cursor(&self) -> Arc<cursor::Controller> { self.cursor.clone() }
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
impl Interceptor for Workspace { //TODO
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
|
||||
request.metadata_mut().insert("auth", self.token.token.parse().unwrap());
|
||||
Ok(request)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub enum FSNode {
|
||||
File(String),
|
||||
Directory(String, Vec<FSNode>),
|
||||
}
|
||||
fn file_tree_rec(path: &str, root: &mut Vec<FSNode>) {
|
||||
if let Some(idx) = path.find("/") {
|
||||
let dir = path[..idx].to_string();
|
||||
let mut dir_node = vec![];
|
||||
Self::file_tree_rec(&path[idx..], &mut dir_node);
|
||||
root.push(FSNode::Directory(dir, dir_node));
|
||||
} else {
|
||||
root.push(FSNode::File(path.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
fn file_tree(&self) -> Vec<FSNode> {
|
||||
let mut root = vec![];
|
||||
for path in &self.filetree {
|
||||
Self::file_tree_rec(&path, &mut root);
|
||||
}
|
||||
root
|
||||
}
|
||||
*/
|
Loading…
Reference in a new issue