codemp/src/client.rs

217 lines
5 KiB
Rust
Raw Normal View History

//! ### Client
//! Main `codemp` client, containing and managing all underlying services.
2023-08-20 00:46:55 +02:00
use std::sync::Arc;
use dashmap::DashMap;
use tonic::{
service::interceptor::InterceptedService,
transport::{Channel, Endpoint},
};
use crate::{
api::User,
errors::{ConnectionResult, RemoteResult},
ext::InternallyMutable,
network,
workspace::Workspace,
};
2024-08-22 00:57:24 +02:00
use codemp_proto::{
auth::{auth_client::AuthClient, LoginRequest},
common::{Empty, Token},
session::{session_client::SessionClient, InviteRequest, WorkspaceRequest},
2024-08-22 00:57:24 +02:00
};
#[cfg(any(feature = "py", feature = "py-noabi"))]
use pyo3::prelude::*;
/// A `codemp` client handle.
2023-08-20 00:46:55 +02:00
///
/// It generates a new UUID and stores user credentials upon connecting.
///
2024-09-04 21:37:35 +02:00
/// A new [`Client`] can be obtained with [`Client::connect`].
#[derive(Debug, Clone)]
#[cfg_attr(feature = "js", napi_derive::napi)]
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyclass)]
pub struct Client(Arc<ClientInner>);
#[derive(Debug)]
struct ClientInner {
2024-08-22 00:57:24 +02:00
user: User,
config: crate::api::Config,
workspaces: DashMap<String, Workspace>,
2024-08-22 00:57:24 +02:00
auth: AuthClient<Channel>,
session: SessionClient<InterceptedService<Channel, network::SessionInterceptor>>,
2024-08-22 00:57:24 +02:00
claims: InternallyMutable<Token>,
}
impl Client {
2024-09-04 21:37:35 +02:00
/// Connect to the server, authenticate and instantiate a new [`Client`].
pub async fn connect(config: crate::api::Config) -> ConnectionResult<Self> {
// TODO move these two into network.rs
let channel = Endpoint::from_shared(config.endpoint())?.connect().await?;
2024-08-22 00:57:24 +02:00
let mut auth = AuthClient::new(channel.clone());
let resp = auth
.login(LoginRequest {
username: config.username.clone(),
password: config.password.clone(),
})
2024-08-22 00:57:24 +02:00
.await?
.into_inner();
let claims = InternallyMutable::new(resp.token);
// TODO move this one into network.rs
let session =
SessionClient::with_interceptor(channel, network::SessionInterceptor(claims.channel()));
Ok(Client(Arc::new(ClientInner {
2024-08-22 00:57:24 +02:00
user: resp.user.into(),
workspaces: DashMap::default(),
claims,
auth,
session,
config,
})))
}
/// Refresh session token.
2024-09-04 21:37:35 +02:00
pub async fn refresh(&self) -> RemoteResult<()> {
let new_token = self
.0
.auth
.clone()
.refresh(self.0.claims.get())
2024-08-22 00:57:24 +02:00
.await?
.into_inner();
self.0.claims.set(new_token);
Ok(())
}
/// Attempt to create a new workspace with given name.
pub async fn create_workspace(&self, name: impl AsRef<str>) -> RemoteResult<()> {
self.0
.session
2024-08-22 00:57:24 +02:00
.clone()
.create_workspace(WorkspaceRequest {
workspace: name.as_ref().to_string(),
})
2024-08-22 00:57:24 +02:00
.await?;
Ok(())
}
/// Delete an existing workspace if possible.
pub async fn delete_workspace(&self, name: impl AsRef<str>) -> RemoteResult<()> {
self.0
.session
2024-08-22 00:57:24 +02:00
.clone()
.delete_workspace(WorkspaceRequest {
workspace: name.as_ref().to_string(),
})
2024-08-22 00:57:24 +02:00
.await?;
Ok(())
}
/// Invite user with given username to the given workspace, if possible.
pub async fn invite_to_workspace(
&self,
workspace_name: impl AsRef<str>,
user_name: impl AsRef<str>,
) -> RemoteResult<()> {
self.0
.session
2024-08-22 00:57:24 +02:00
.clone()
.invite_to_workspace(InviteRequest {
workspace: workspace_name.as_ref().to_string(),
user: user_name.as_ref().to_string(),
})
.await?;
Ok(())
}
/// Fetch the names of all workspaces owned by the current user.
pub async fn fetch_owned_workspaces(&self) -> RemoteResult<Vec<String>> {
self.fetch_workspaces(true).await
}
/// Fetch the names of all workspaces the current user has joined.
pub async fn fetch_joined_workspaces(&self) -> RemoteResult<Vec<String>> {
self.fetch_workspaces(false).await
}
async fn fetch_workspaces(&self, owned: bool) -> RemoteResult<Vec<String>> {
let workspaces = self
.0
.session
2024-08-22 00:57:24 +02:00
.clone()
.list_workspaces(Empty {})
.await?
.into_inner();
if owned {
Ok(workspaces.owned)
} else {
Ok(workspaces.invited)
}
2024-08-22 00:57:24 +02:00
}
2024-09-04 21:37:35 +02:00
/// Join and return a [`Workspace`].
pub async fn attach_workspace(
&self,
workspace: impl AsRef<str>,
) -> ConnectionResult<Workspace> {
let token = self
.0
.session
2024-08-22 00:57:24 +02:00
.clone()
.access_workspace(WorkspaceRequest {
workspace: workspace.as_ref().to_string(),
})
2024-08-22 00:57:24 +02:00
.await?
.into_inner();
let ws = Workspace::connect(
workspace.as_ref().to_string(),
2024-08-22 00:57:24 +02:00
self.0.user.clone(),
self.0.config.clone(),
2024-08-22 00:57:24 +02:00
token,
self.0.claims.channel(),
)
.await?;
self.0
.workspaces
.insert(workspace.as_ref().to_string(), ws.clone());
Ok(ws)
}
2024-09-04 21:37:35 +02:00
/// Leave the [`Workspace`] with the given name.
pub fn leave_workspace(&self, id: &str) -> bool {
match self.0.workspaces.remove(id) {
None => true,
Some(x) => x.1.consume(),
}
}
2024-09-04 21:37:35 +02:00
/// Gets a [`Workspace`] handle by name.
pub fn get_workspace(&self, id: &str) -> Option<Workspace> {
self.0.workspaces.get(id).map(|x| x.clone())
}
2024-09-04 21:37:35 +02:00
/// Get the names of all active [`Workspace`]s.
pub fn active_workspaces(&self) -> Vec<String> {
self.0
.workspaces
.iter()
.map(|x| x.key().to_string())
.collect()
}
/// Get the currently logged in user.
2024-10-15 22:21:19 +02:00
pub fn current_user(&self) -> &User {
2024-08-22 00:57:24 +02:00
&self.0.user
}
}