Skip to main content

packset_daemon/
home.rs

1//! Where the pack lives on disk.
2//!
3//! Cards are files and atoms are a database, and both hang off one home so a
4//! seat has exactly one place to back up. A workspace becomes a directory
5//! through its slug, which is why a name carrying slashes does not nest.
6
7use std::path::{Path, PathBuf};
8
9use packset_core::identity::workspace_slug;
10
11/// The root of one seat's pack.
12#[derive(Debug, Clone)]
13pub struct Home {
14    root: PathBuf,
15}
16
17impl Home {
18    /// A home at `root`.
19    #[must_use]
20    pub fn new(root: impl Into<PathBuf>) -> Self {
21        Self { root: root.into() }
22    }
23
24    /// The default home, `~/.grokinside/memory`.
25    #[must_use]
26    pub fn default_root() -> PathBuf {
27        let home = std::env::var_os("HOME").map_or_else(|| PathBuf::from("."), PathBuf::from);
28        home.join(".grokinside").join("memory")
29    }
30
31    /// The root path.
32    #[must_use]
33    pub fn root(&self) -> &Path {
34        &self.root
35    }
36
37    /// The atom database.
38    #[must_use]
39    pub fn db_path(&self) -> PathBuf {
40        self.root.join("memory.lmdb")
41    }
42
43    /// The search projection.
44    #[must_use]
45    pub fn milli_dir(&self) -> PathBuf {
46        self.root.join("memory.milli")
47    }
48
49    /// The single-writer lock.
50    #[must_use]
51    pub fn lock_path(&self) -> PathBuf {
52        self.root.join("packsetd.lock")
53    }
54
55    /// One workspace's directory.
56    #[must_use]
57    pub fn workspace_dir(&self, workspace: &str) -> PathBuf {
58        self.root.join("workspaces").join(workspace_slug(workspace))
59    }
60
61    /// The seat-wide card.
62    #[must_use]
63    pub fn user_path(&self) -> PathBuf {
64        self.root.join("USER.md")
65    }
66
67    /// One workspace's card.
68    #[must_use]
69    pub fn memory_path(&self, workspace: &str) -> PathBuf {
70        self.workspace_dir(workspace).join("MEMORY.md")
71    }
72
73    /// The append-only log, kept for readers that predate the database.
74    #[must_use]
75    pub fn atoms_path(&self, workspace: &str) -> PathBuf {
76        self.workspace_dir(workspace).join("atoms.jsonl")
77    }
78
79    /// Where an overflowing card is archived, one file per day.
80    #[must_use]
81    pub fn archive_path(&self, workspace: &str, day: &str) -> PathBuf {
82        self.workspace_dir(workspace)
83            .join("archive")
84            .join(format!("{day}.md"))
85    }
86
87    /// The active set for a workspace.
88    #[must_use]
89    pub fn pin_path(&self, workspace: &str) -> PathBuf {
90        self.workspace_dir(workspace).join("pin")
91    }
92
93    /// One set's directory inside a workspace.
94    #[must_use]
95    pub fn set_dir(&self, workspace: &str, name: &str) -> PathBuf {
96        self.workspace_dir(workspace).join("sets").join(name)
97    }
98
99    /// A set's own card.
100    #[must_use]
101    pub fn set_user_path(&self, workspace: &str, name: &str) -> PathBuf {
102        self.set_dir(workspace, name).join("USER.md")
103    }
104
105    /// A set's own workspace card.
106    #[must_use]
107    pub fn set_memory_path(&self, workspace: &str, name: &str) -> PathBuf {
108        self.set_dir(workspace, name).join("MEMORY.md")
109    }
110
111    /// A set's standing instructions.
112    #[must_use]
113    pub fn set_instructions_path(&self, workspace: &str, name: &str) -> PathBuf {
114        self.set_dir(workspace, name).join("INSTRUCTIONS.md")
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn a_workspace_name_becomes_one_directory() {
124        let home = Home::new("/tmp/pack");
125        let dir = home.workspace_dir("git:github.com/HaoZeke/vissue");
126        assert_eq!(
127            dir,
128            PathBuf::from("/tmp/pack/workspaces/git_github.com_HaoZeke_vissue")
129        );
130        // The slug is what keeps a slash in the name from nesting.
131        assert_eq!(dir.components().count(), 5);
132    }
133
134    #[test]
135    fn the_seat_card_is_above_the_workspaces() {
136        let home = Home::new("/tmp/pack");
137        assert_eq!(home.user_path(), PathBuf::from("/tmp/pack/USER.md"));
138        assert!(home
139            .memory_path("global")
140            .starts_with("/tmp/pack/workspaces"));
141    }
142
143    #[test]
144    fn a_set_lives_under_its_workspace() {
145        let home = Home::new("/tmp/pack");
146        assert_eq!(
147            home.set_user_path("global", "review"),
148            PathBuf::from("/tmp/pack/workspaces/global/sets/review/USER.md")
149        );
150    }
151}