diff --git a/Cargo.toml b/Cargo.toml index d3abf75..7cca7ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 } diff --git a/proto/files.proto b/proto/files.proto index e57cdc2..a7ab7d2 100644 --- a/proto/files.proto +++ b/proto/files.proto @@ -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 \ No newline at end of file +} \ No newline at end of file diff --git a/proto/user.proto b/proto/user.proto index 322a935..2fa52ac 100644 --- a/proto/user.proto +++ b/proto/user.proto @@ -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 } diff --git a/proto/workspace.proto b/proto/workspace.proto index 192b813..d4c1a01 100644 --- a/proto/workspace.proto +++ b/proto/workspace.proto @@ -20,8 +20,8 @@ message WorkspaceMessage { } message JoinRequest { - required string username=1; - required string password=2; + required string username = 1; + required string password = 2; } message AttachRequest { @@ -67,6 +67,6 @@ message UserList { repeated user.UserIdentity users = 1; } -message WorkspaceDetails{ - required int32 id=1; +message WorkspaceDetails { + required string id = 1; } \ No newline at end of file diff --git a/proto/workspace_service.proto b/proto/workspace_service.proto index 108f5c1..3fe7c2e 100644 --- a/proto/workspace_service.proto +++ b/proto/workspace_service.proto @@ -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 - -} - - +} \ No newline at end of file diff --git a/src/api/change.rs b/src/api/change.rs index e9e6792..041e628 100644 --- a/src/api/change.rs +++ b/src/api/change.rs @@ -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 } } } diff --git a/src/buffer/mod.rs b/src/buffer/mod.rs index 1610400..7722e9b 100644 --- a/src/buffer/mod.rs +++ b/src/buffer/mod.rs @@ -11,4 +11,4 @@ pub mod controller; pub(crate) mod worker; -pub use controller::BufferController as Controller; +pub use controller::BufferController as Controller; \ No newline at end of file diff --git a/src/buffer/worker.rs b/src/buffer/worker.rs index c2e77bc..031fc57 100644 --- a/src/buffer/worker.rs +++ b/src/buffer/worker.rs @@ -63,7 +63,7 @@ impl BufferWorker { } #[async_trait] -impl ControllerWorker for BufferControllerWorker { +impl ControllerWorker for BufferWorker { type Controller = BufferController; type Tx = mpsc::Sender; type Rx = Streaming; diff --git a/src/client.rs b/src/client.rs index 41b5c66..35b3faa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -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>, - pub workspaces: BTreeMap, + pub workspaces: BTreeMap>>, services: Arc } @@ -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 { //TODO interceptor + pub async fn new(dest: &str) -> crate::Result { 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>> { + 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>> { 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( - workspace_id.to_string(), - self.user_id, - self.token_tx.clone(), - controller, - self.services.clone() - ).await?); + let lock = Arc::new(RwLock::new( + Workspace::new( + workspace_id.to_string(), + self.user_id, + self.token_tx.clone(), + controller, + self.services.clone() + ).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() } diff --git a/src/cursor/controller.rs b/src/cursor/controller.rs index af1f783..2dd5cd2 100644 --- a/src/cursor/controller.rs +++ b/src/cursor/controller.rs @@ -57,7 +57,7 @@ impl Controller 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, })?) } diff --git a/src/cursor/worker.rs b/src/cursor/worker.rs index 08045be..1d27c6b 100644 --- a/src/cursor/worker.rs +++ b/src/cursor/worker.rs @@ -39,7 +39,7 @@ impl CursorWorker { } #[async_trait] -impl ControllerWorker for CursorControllerWorker { +impl ControllerWorker for CursorWorker { type Controller = CursorController; type Tx = mpsc::Sender; type Rx = Streaming; @@ -58,7 +58,7 @@ impl ControllerWorker 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"); }, diff --git a/src/tools.rs b/src/tools.rs index 5e1870d..bf5d997 100644 --- a/src/tools.rs +++ b/src/tools.rs @@ -47,4 +47,4 @@ pub async fn select_buffer( }, } } -} +} \ No newline at end of file diff --git a/src/workspace.rs b/src/workspace.rs index 6a20afa..d03e43a 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -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 for UserInfo { impl From 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,75 +141,45 @@ 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> { + 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( tonic::Request::new(BufferPayload { path: path.to_string() }) ).await?; - + self.filetree.remove(path); - + 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 { 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> { self.buffers.get(path).cloned() } - - /// return a reference to current cursor controller, if currently in a workspace - pub fn cursor(&self) -> Arc { self.cursor.clone() } - -} - -/* -impl Interceptor for Workspace { //TODO - fn call(&mut self, mut request: tonic::Request<()>) -> Result, 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), -} - fn file_tree_rec(path: &str, root: &mut Vec) { - 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 { - let mut root = vec![]; - for path in &self.filetree { - Self::file_tree_rec(&path, &mut root); - } - root - } -*/ \ No newline at end of file +} \ No newline at end of file