1use std::path::PathBuf;
2
3use anyhow::{Context, Result};
4
5use crate::config;
6use crate::db::DEFAULT_SPACE;
7
8pub struct Space {
11 pub root: PathBuf,
12}
13
14impl Space {
15 pub fn open() -> Result<Self> {
18 let root = config::project_dirs()?.data_dir().to_path_buf();
19 let space = Self { root };
20 space.migrate_legacy_layout()?;
21 std::fs::create_dir_all(space.root.join("spaces"))
22 .with_context(|| format!("creating {}", space.root.join("spaces").display()))?;
23 Ok(space)
24 }
25
26 fn migrate_legacy_layout(&self) -> Result<()> {
29 let legacy_dir = self.root.join("spaces").join("global");
30 let legacy_db = legacy_dir.join("nexus.db");
31 let new_db = self.root.join("nexus.db");
32 if legacy_db.exists() && !new_db.exists() {
33 std::fs::create_dir_all(&self.root)
34 .with_context(|| format!("creating {}", self.root.display()))?;
35 std::fs::rename(&legacy_db, &new_db).with_context(|| {
36 format!("moving {} to {}", legacy_db.display(), new_db.display())
37 })?;
38 }
39 let default_dir = self.root.join("spaces").join(DEFAULT_SPACE);
40 if legacy_dir.exists() && !default_dir.exists() {
41 std::fs::rename(&legacy_dir, &default_dir).with_context(|| {
42 format!(
43 "moving {} to {}",
44 legacy_dir.display(),
45 default_dir.display()
46 )
47 })?;
48 }
49 Ok(())
50 }
51
52 pub fn db_path(&self) -> PathBuf {
53 self.root.join("nexus.db")
54 }
55
56 pub fn ensure_space_dir(&self, name: &str) -> Result<()> {
58 let dir = self.space_dir(name);
59 std::fs::create_dir_all(&dir).with_context(|| format!("creating {}", dir.display()))
60 }
61
62 pub(crate) fn space_dir(&self, name: &str) -> PathBuf {
65 self.root.join("spaces").join(name)
66 }
67
68 pub fn memory_path(&self, name: &str) -> PathBuf {
69 self.space_dir(name).join("memory.md")
70 }
71
72 pub fn instructions_path(&self, name: &str) -> PathBuf {
73 self.space_dir(name).join("instructions.md")
74 }
75
76 pub fn blocked_domains_path(&self, name: &str) -> PathBuf {
78 self.space_dir(name).join("blocked_domains.txt")
79 }
80
81 pub fn files_dir(&self, name: &str) -> PathBuf {
83 self.space_dir(name).join("files")
84 }
85
86 pub fn apps_dir(&self, name: &str) -> PathBuf {
88 self.space_dir(name).join("apps")
89 }
90
91 pub fn scripts_dir(&self, name: &str) -> PathBuf {
93 self.space_dir(name).join("scripts")
94 }
95
96 pub fn spaces_root(&self) -> PathBuf {
98 self.root.join("spaces")
99 }
100
101 pub fn rename_space_dir(&self, old: &str, new: &str) -> Result<()> {
103 let from = self.space_dir(old);
104 let to = self.space_dir(new);
105 if from.exists() {
106 std::fs::rename(&from, &to)
107 .with_context(|| format!("renaming {} to {}", from.display(), to.display()))?;
108 }
109 Ok(())
110 }
111
112 pub fn remove_space_dir(&self, name: &str) -> Result<()> {
114 let dir = self.space_dir(name);
115 if dir.exists() {
116 std::fs::remove_dir_all(&dir).with_context(|| format!("removing {}", dir.display()))?;
117 }
118 Ok(())
119 }
120}