feat(daemon): registry for workspaces and surfaces with idempotent open
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
mod lifecycle;
|
||||
mod registry;
|
||||
mod surface;
|
||||
|
||||
fn main() {
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use spacesh_proto::{SurfaceId, WorkspaceId};
|
||||
use crate::surface::SurfaceHandle;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WorkspaceMeta {
|
||||
pub id: WorkspaceId,
|
||||
pub path: PathBuf,
|
||||
}
|
||||
|
||||
/// Single-threaded owner of all live surfaces and workspaces.
|
||||
/// Lives inside the server task; not shared across threads.
|
||||
#[derive(Default)]
|
||||
pub struct Registry {
|
||||
counter: AtomicU64,
|
||||
workspaces: HashMap<WorkspaceId, WorkspaceMeta>,
|
||||
/// path → workspace, so `open` is idempotent.
|
||||
by_path: HashMap<PathBuf, WorkspaceId>,
|
||||
surfaces: HashMap<SurfaceId, SurfaceHandle>,
|
||||
}
|
||||
|
||||
impl Registry {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn next_id(&self, prefix: &str) -> String {
|
||||
let n = self.counter.fetch_add(1, Ordering::Relaxed);
|
||||
format!("{prefix}_{n:x}")
|
||||
}
|
||||
|
||||
/// Idempotent: opening the same canonicalized path returns the existing workspace.
|
||||
pub fn open_workspace(&mut self, path: PathBuf) -> WorkspaceMeta {
|
||||
let canonical = path.canonicalize().unwrap_or(path);
|
||||
if let Some(id) = self.by_path.get(&canonical) {
|
||||
return self.workspaces[id].clone();
|
||||
}
|
||||
let id = WorkspaceId(self.next_id("w"));
|
||||
let meta = WorkspaceMeta { id: id.clone(), path: canonical.clone() };
|
||||
self.workspaces.insert(id.clone(), meta.clone());
|
||||
self.by_path.insert(canonical, id);
|
||||
meta
|
||||
}
|
||||
|
||||
pub fn workspace(&self, id: &WorkspaceId) -> Option<&WorkspaceMeta> {
|
||||
self.workspaces.get(id)
|
||||
}
|
||||
|
||||
pub fn new_surface_id(&self) -> SurfaceId {
|
||||
SurfaceId(self.next_id("s"))
|
||||
}
|
||||
|
||||
pub fn insert_surface(&mut self, handle: SurfaceHandle) {
|
||||
self.surfaces.insert(handle.id.clone(), handle);
|
||||
}
|
||||
|
||||
pub fn surface(&self, id: &SurfaceId) -> Option<&SurfaceHandle> {
|
||||
self.surfaces.get(id)
|
||||
}
|
||||
|
||||
pub fn remove_surface(&mut self, id: &SurfaceId) -> Option<SurfaceHandle> {
|
||||
self.surfaces.remove(id)
|
||||
}
|
||||
|
||||
/// Snapshot for the `status` command: (workspace, its surface ids).
|
||||
pub fn status(&self) -> Vec<(WorkspaceMeta, Vec<SurfaceId>)> {
|
||||
self.workspaces
|
||||
.values()
|
||||
.map(|w| {
|
||||
let sids = self
|
||||
.surfaces
|
||||
.values()
|
||||
.filter(|s| s.workspace_id == w.id)
|
||||
.map(|s| s.id.clone())
|
||||
.collect();
|
||||
(w.clone(), sids)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn open_is_idempotent_per_path() {
|
||||
let mut reg = Registry::new();
|
||||
let dir = std::env::temp_dir();
|
||||
let a = reg.open_workspace(dir.clone());
|
||||
let b = reg.open_workspace(dir.clone());
|
||||
assert_eq!(a.id, b.id);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ids_are_unique_and_prefixed() {
|
||||
let reg = Registry::new();
|
||||
let s1 = reg.new_surface_id();
|
||||
let s2 = reg.new_surface_id();
|
||||
assert!(s1.0.starts_with("s_"));
|
||||
assert_ne!(s1, s2);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user