use arc_swap::ArcSwap;
use dashmap::DashMap;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::time::SystemTime;
use uuid::Uuid;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum SessionRole {
Primary,
Related,
Dependency,
Shared,
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct WorkspaceMetadata {
pub project_type: Option<String>,
pub tags: Vec<String>,
pub root_paths: Vec<PathBuf>,
pub custom_data: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Workspace {
pub id: Uuid,
pub name: String,
pub description: String,
pub created_at: SystemTime,
#[serde(skip)]
pub session_ids: Arc<DashMap<Uuid, SessionRole>>,
#[serde(skip)]
pub metadata: Arc<ArcSwap<WorkspaceMetadata>>,
}
impl Workspace {
pub fn new(id: Uuid, name: String, description: String) -> Self {
Self {
id,
name,
description,
created_at: SystemTime::now(),
session_ids: Arc::new(DashMap::new()),
metadata: Arc::new(ArcSwap::from_pointee(WorkspaceMetadata::default())),
}
}
pub fn add_session(&self, session_id: Uuid, role: SessionRole) {
self.session_ids.insert(session_id, role);
}
pub fn remove_session(&self, session_id: &Uuid) -> Option<SessionRole> {
self.session_ids.remove(session_id).map(|(_, role)| role)
}
pub fn get_session_role(&self, session_id: &Uuid) -> Option<SessionRole> {
self.session_ids.get(session_id).map(|entry| *entry.value())
}
pub fn get_all_sessions(&self) -> Vec<(Uuid, SessionRole)> {
self.session_ids
.iter()
.map(|entry| (*entry.key(), *entry.value()))
.collect()
}
pub fn update_metadata<F>(&self, f: F)
where
F: FnOnce(&mut WorkspaceMetadata),
{
let current = self.metadata.load();
let mut new_metadata = (**current).clone();
f(&mut new_metadata);
self.metadata.store(Arc::new(new_metadata));
}
}
pub struct WorkspaceManager {
workspaces: Arc<DashMap<Uuid, Arc<ArcSwap<Workspace>>>>,
name_index: Arc<DashMap<String, Uuid>>,
total_workspaces: Arc<AtomicU64>,
}
impl WorkspaceManager {
pub fn new() -> Self {
Self {
workspaces: Arc::new(DashMap::new()),
name_index: Arc::new(DashMap::new()),
total_workspaces: Arc::new(AtomicU64::new(0)),
}
}
pub fn create_workspace(&self, name: String, description: String) -> Uuid {
let id = Uuid::new_v4();
let workspace = Workspace::new(id, name.clone(), description);
self.workspaces
.insert(id, Arc::new(ArcSwap::from_pointee(workspace)));
self.name_index.insert(name, id);
self.total_workspaces.fetch_add(1, Ordering::Relaxed);
id
}
pub fn restore_workspace(
&self,
id: Uuid,
name: String,
description: String,
sessions: Vec<(Uuid, SessionRole)>,
) {
let workspace = Workspace::new(id, name.clone(), description);
for (session_id, role) in sessions {
workspace.add_session(session_id, role);
}
self.workspaces
.insert(id, Arc::new(ArcSwap::from_pointee(workspace)));
self.name_index.insert(name, id);
self.total_workspaces.fetch_add(1, Ordering::Relaxed);
}
pub fn get_workspace(&self, id: &Uuid) -> Option<Arc<Workspace>> {
self.workspaces.get(id).map(|entry| {
let arc_swap = entry.value();
arc_swap.load_full()
})
}
pub fn get_workspace_by_name(&self, name: &str) -> Option<Arc<Workspace>> {
self.name_index.get(name).and_then(|entry| {
let id = *entry.value();
self.get_workspace(&id)
})
}
pub fn list_workspaces(&self) -> Vec<Arc<Workspace>> {
self.workspaces
.iter()
.map(|entry| entry.value().load_full())
.collect()
}
pub fn delete_workspace(&self, id: &Uuid) -> Option<Arc<Workspace>> {
if let Some((_, arc_swap)) = self.workspaces.remove(id) {
let workspace = arc_swap.load_full();
self.name_index.remove(&workspace.name);
self.total_workspaces.fetch_sub(1, Ordering::Relaxed);
Some(workspace)
} else {
None
}
}
pub fn add_session_to_workspace(
&self,
workspace_id: &Uuid,
session_id: Uuid,
role: SessionRole,
) -> Result<(), String> {
self.get_workspace(workspace_id)
.ok_or_else(|| format!("Workspace {} not found", workspace_id))?
.add_session(session_id, role);
Ok(())
}
pub fn remove_session_from_workspace(
&self,
workspace_id: &Uuid,
session_id: &Uuid,
) -> Result<Option<SessionRole>, String> {
self.get_workspace(workspace_id)
.ok_or_else(|| format!("Workspace {} not found", workspace_id))?
.remove_session(session_id)
.ok_or_else(|| format!("Session {} not in workspace", session_id))
.map(Some)
}
pub fn total_workspaces(&self) -> u64 {
self.total_workspaces.load(Ordering::Relaxed)
}
}
impl Default for WorkspaceManager {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_workspace_creation() {
let manager = WorkspaceManager::new();
let ws_id = manager.create_workspace(
"Test Workspace".to_string(),
"Testing workspace".to_string(),
);
let workspace = manager.get_workspace(&ws_id).unwrap();
assert_eq!(workspace.name, "Test Workspace");
assert_eq!(workspace.description, "Testing workspace");
assert_eq!(manager.total_workspaces(), 1);
}
#[test]
fn test_session_management() {
let manager = WorkspaceManager::new();
let ws_id = manager.create_workspace("Test".to_string(), "".to_string());
let session1 = Uuid::new_v4();
let session2 = Uuid::new_v4();
manager
.add_session_to_workspace(&ws_id, session1, SessionRole::Primary)
.unwrap();
manager
.add_session_to_workspace(&ws_id, session2, SessionRole::Related)
.unwrap();
let workspace = manager.get_workspace(&ws_id).unwrap();
assert_eq!(workspace.session_ids.len(), 2);
assert_eq!(
workspace.get_session_role(&session1),
Some(SessionRole::Primary)
);
assert_eq!(
workspace.get_session_role(&session2),
Some(SessionRole::Related)
);
}
#[test]
fn test_workspace_lookup_by_name() {
let manager = WorkspaceManager::new();
let ws_id = manager.create_workspace("MyWorkspace".to_string(), "".to_string());
let workspace = manager.get_workspace_by_name("MyWorkspace").unwrap();
assert_eq!(workspace.id, ws_id);
}
#[tokio::test]
async fn test_concurrent_workspace_operations() {
let manager = Arc::new(WorkspaceManager::new());
let tasks: Vec<_> = (0..50)
.map(|i| {
let mgr = manager.clone();
tokio::spawn(async move {
let ws_id =
mgr.create_workspace(format!("Workspace {}", i), format!("Test {}", i));
for _ in 0..5 {
let session_id = Uuid::new_v4();
mgr.add_session_to_workspace(&ws_id, session_id, SessionRole::Related)
.unwrap();
}
ws_id
})
})
.collect();
let workspace_ids: Vec<_> = futures::future::join_all(tasks)
.await
.into_iter()
.map(|r| r.unwrap())
.collect();
assert_eq!(manager.total_workspaces(), 50);
for ws_id in workspace_ids {
let workspace = manager.get_workspace(&ws_id).unwrap();
assert_eq!(workspace.session_ids.len(), 5);
}
}
}