feat: implemented leave workspace and list buffer users, various fixes

This commit is contained in:
zaaarf 2024-01-25 16:31:38 +01:00
parent 6a061ca432
commit 741a074464
13 changed files with 88 additions and 125 deletions

View file

@ -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 }

View file

@ -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
}

View file

@ -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
}

View file

@ -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;
}

View file

@ -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
}
}

View file

@ -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 }
}
}

View file

@ -11,4 +11,4 @@ pub mod controller;
pub(crate) mod worker;
pub use controller::BufferController as Controller;
pub use controller::BufferController as Controller;

View file

@ -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>;

View file

@ -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(
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()
}

View file

@ -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,
})?)
}

View file

@ -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");
},

View file

@ -47,4 +47,4 @@ pub async fn select_buffer(
},
}
}
}
}

View file

@ -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,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<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(
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<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
}
*/
}