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
|
# core
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
# woot
|
# 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
|
# proto
|
||||||
tonic = { version = "0.9", features = ["tls", "tls-roots"], optional = true }
|
tonic = { version = "0.9", features = ["tls", "tls-roots"], optional = true }
|
||||||
prost = { version = "0.11.8", optional = true }
|
prost = { version = "0.11.8", optional = true }
|
||||||
|
|
|
@ -14,4 +14,4 @@ message BufferTree {
|
||||||
message WorkspaceFileTree {
|
message WorkspaceFileTree {
|
||||||
// list of strings may be more efficient but it's a lot more hassle
|
// list of strings may be more efficient but it's a lot more hassle
|
||||||
required string payload = 1; // spappolata di json
|
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
|
// payload identifying user
|
||||||
message UserIdentity {
|
message UserIdentity {
|
||||||
// user identifier
|
// 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 {
|
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";
|
syntax = "proto2";
|
||||||
|
|
||||||
package workspace_service;
|
package workspace_service;
|
||||||
|
@ -29,11 +5,8 @@ import "user.proto";
|
||||||
import "files.proto";
|
import "files.proto";
|
||||||
import "workspace.proto";
|
import "workspace.proto";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
service Workspace {
|
service Workspace {
|
||||||
|
|
||||||
|
|
||||||
rpc Attach (workspace.AttachRequest) returns (workspace.Token);
|
rpc Attach (workspace.AttachRequest) returns (workspace.Token);
|
||||||
|
|
||||||
rpc LeaveWorkspace (workspace.WorkspaceDetails) returns (workspace.Empty);
|
rpc LeaveWorkspace (workspace.WorkspaceDetails) returns (workspace.Empty);
|
||||||
|
@ -46,13 +19,10 @@ service Workspace {
|
||||||
|
|
||||||
rpc ListUsers (workspace.UserListRequest) returns (workspace.UserList);
|
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 Join (workspace.JoinRequest) returns (workspace.Token);
|
||||||
|
|
||||||
rpc Delete (workspace.BufferPayload) returns (workspace.Empty); //deletes buffer
|
rpc Delete (workspace.BufferPayload) returns (workspace.Empty); //deletes buffer
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -84,11 +84,11 @@ impl TextChange {
|
||||||
|
|
||||||
/// convert from byte index to row and column
|
/// convert from byte index to row and column
|
||||||
/// txt must be the whole content of the buffer, in order to count lines
|
/// 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()
|
// FIXME might panic, use .get()
|
||||||
let row = txt[..index].matches('\n').count() as i32;
|
let row = txt[..index].matches('\n').count() as i32;
|
||||||
let col = txt[..index].split('\n').last().unwrap_or("").len() 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]
|
#[async_trait]
|
||||||
impl ControllerWorker<TextChange> for BufferControllerWorker {
|
impl ControllerWorker<TextChange> for BufferWorker {
|
||||||
type Controller = BufferController;
|
type Controller = BufferController;
|
||||||
type Tx = mpsc::Sender<Operation>;
|
type Tx = mpsc::Sender<Operation>;
|
||||||
type Rx = Streaming<Operation>;
|
type Rx = Streaming<Operation>;
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc;
|
|
||||||
|
use tokio::sync::{mpsc, RwLock};
|
||||||
use tonic::service::interceptor::InterceptedService;
|
use tonic::service::interceptor::InterceptedService;
|
||||||
use tonic::service::Interceptor;
|
use tonic::service::Interceptor;
|
||||||
use tonic::transport::{Channel, Endpoint};
|
use tonic::transport::{Channel, Endpoint};
|
||||||
|
@ -14,7 +15,7 @@ use crate::api::controller::ControllerWorker;
|
||||||
use crate::cursor::worker::CursorWorker;
|
use crate::cursor::worker::CursorWorker;
|
||||||
use crate::proto::buffer_service::buffer_client::BufferClient;
|
use crate::proto::buffer_service::buffer_client::BufferClient;
|
||||||
use crate::proto::cursor_service::cursor_client::CursorClient;
|
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::proto::workspace_service::workspace_client::WorkspaceClient;
|
||||||
use crate::workspace::Workspace;
|
use crate::workspace::Workspace;
|
||||||
|
|
||||||
|
@ -26,7 +27,7 @@ use crate::workspace::Workspace;
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
token_tx: Arc<tokio::sync::watch::Sender<Token>>,
|
token_tx: Arc<tokio::sync::watch::Sender<Token>>,
|
||||||
pub workspaces: BTreeMap<String, Workspace>,
|
pub workspaces: BTreeMap<String, Arc<RwLock<Workspace>>>,
|
||||||
services: Arc<Services>
|
services: Arc<Services>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +63,7 @@ fn parse_codemp_connection_string<'a>(string: &'a str) -> (String, String) {
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
/// instantiate and connect a new 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 (_host, _workspace_id) = parse_codemp_connection_string(dest);
|
||||||
|
|
||||||
let channel = Endpoint::from_shared(dest.to_string())?
|
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
|
/// 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>>> {
|
||||||
/// to interact with such workspace [crate::api::Controller::send] cursor events or
|
let mut workspace_client = self.services.workspace.clone();
|
||||||
/// [crate::api::Controller::recv] for events on the associated [crate::cursor::Controller].
|
workspace_client.create_workspace(
|
||||||
pub async fn join(&mut self, workspace_id: &str) -> crate::Result<()> {
|
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(
|
self.token_tx.send(self.services.workspace.clone().join(
|
||||||
tonic::Request::new(JoinRequest { username: "".to_string(), password: "".to_string() }) //TODO
|
tonic::Request::new(JoinRequest { username: "".to_string(), password: "".to_string() }) //TODO
|
||||||
).await?.into_inner())?;
|
).await?.into_inner())?;
|
||||||
|
@ -112,17 +120,32 @@ impl Client {
|
||||||
tracing::debug!("controller worker stopped");
|
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(),
|
workspace_id.to_string(),
|
||||||
self.user_id,
|
self.user_id,
|
||||||
self.token_tx.clone(),
|
self.token_tx.clone(),
|
||||||
controller,
|
controller,
|
||||||
self.services.clone()
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// accessor for user id
|
||||||
pub fn user_id(&self) -> Uuid {
|
pub fn user_id(&self) -> Uuid {
|
||||||
self.user_id.clone()
|
self.user_id.clone()
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,7 +57,7 @@ impl Controller<CursorEvent> for CursorController {
|
||||||
std::mem::swap(&mut cursor.start, &mut cursor.end);
|
std::mem::swap(&mut cursor.start, &mut cursor.end);
|
||||||
}
|
}
|
||||||
Ok(self.op.send(CursorEvent {
|
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,
|
position: cursor,
|
||||||
})?)
|
})?)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ impl CursorWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ControllerWorker<CursorEvent> for CursorControllerWorker {
|
impl ControllerWorker<CursorEvent> for CursorWorker {
|
||||||
type Controller = CursorController;
|
type Controller = CursorController;
|
||||||
type Tx = mpsc::Sender<CursorEvent>;
|
type Tx = mpsc::Sender<CursorEvent>;
|
||||||
type Rx = Streaming<CursorEvent>;
|
type Rx = Streaming<CursorEvent>;
|
||||||
|
@ -58,7 +58,7 @@ impl ControllerWorker<CursorEvent> for CursorControllerWorker {
|
||||||
loop {
|
loop {
|
||||||
tokio::select!{
|
tokio::select!{
|
||||||
Ok(Some(cur)) = rx.message() => {
|
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.channel.send(cur.clone()).unwrap_or_warn("could not broadcast event");
|
||||||
self.changed.send(cur).unwrap_or_warn("could not update last 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 tokio::sync::mpsc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -25,13 +25,11 @@ impl From<Uuid> for UserInfo {
|
||||||
|
|
||||||
impl From<UserIdentity> for Uuid {
|
impl From<UserIdentity> for Uuid {
|
||||||
fn from(uid: UserIdentity) -> 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 {
|
pub struct Workspace {
|
||||||
id: String,
|
id: String,
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
|
@ -68,10 +66,10 @@ impl Workspace {
|
||||||
Ok(ws)
|
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<()> {
|
pub async fn create(&mut self, path: &str) -> crate::Result<()> {
|
||||||
let mut workspace_client = self.services.workspace.clone();
|
let mut workspace_client = self.services.workspace.clone();
|
||||||
workspace_client.create(
|
workspace_client.create_buffer(
|
||||||
tonic::Request::new(BufferPayload { path: path.to_string() })
|
tonic::Request::new(BufferPayload { path: path.to_string() })
|
||||||
).await?;
|
).await?;
|
||||||
|
|
||||||
|
@ -110,6 +108,7 @@ impl Workspace {
|
||||||
Ok(controller)
|
Ok(controller)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// fetch a list of all buffers in a workspace
|
||||||
pub async fn fetch_buffers(&mut self) -> crate::Result<()> {
|
pub async fn fetch_buffers(&mut self) -> crate::Result<()> {
|
||||||
let mut workspace_client = self.services.workspace.clone();
|
let mut workspace_client = self.services.workspace.clone();
|
||||||
let buffers = workspace_client.list_buffers(
|
let buffers = workspace_client.list_buffers(
|
||||||
|
@ -124,6 +123,7 @@ impl Workspace {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// fetch a list of all users in a workspace
|
||||||
pub async fn fetch_users(&mut self) -> crate::Result<()> {
|
pub async fn fetch_users(&mut self) -> crate::Result<()> {
|
||||||
let mut workspace_client = self.services.workspace.clone();
|
let mut workspace_client = self.services.workspace.clone();
|
||||||
let users = BTreeSet::from_iter(workspace_client.list_users(
|
let users = BTreeSet::from_iter(workspace_client.list_users(
|
||||||
|
@ -141,10 +141,27 @@ impl Workspace {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_buffer_users() {
|
/// get a list of the users attached to a specific buffer
|
||||||
todo!(); //TODO what is this
|
///
|
||||||
|
/// 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<()> {
|
pub async fn delete(&mut self, path: &str) -> crate::Result<()> {
|
||||||
let mut workspace_client = self.services.workspace.clone();
|
let mut workspace_client = self.services.workspace.clone();
|
||||||
workspace_client.delete(
|
workspace_client.delete(
|
||||||
|
@ -156,60 +173,13 @@ impl Workspace {
|
||||||
Ok(())
|
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() }
|
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
|
/// 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>> {
|
pub fn buffer_by_name(&self, path: &str) -> Option<Arc<buffer::Controller>> {
|
||||||
self.buffers.get(path).cloned()
|
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