Skip to main content

kimun_notes/settings/
workspace_config.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::path::PathBuf;
5
6#[derive(Debug, Clone)]
7pub enum WorkspaceConfigError {
8    DuplicateWorkspace {
9        name: String,
10        existing_path: PathBuf,
11    },
12}
13
14impl std::fmt::Display for WorkspaceConfigError {
15    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16        match self {
17            WorkspaceConfigError::DuplicateWorkspace {
18                name,
19                existing_path,
20            } => {
21                write!(
22                    f,
23                    "Workspace '{}' already exists at {:?}",
24                    name, existing_path
25                )
26            }
27        }
28    }
29}
30
31impl std::error::Error for WorkspaceConfigError {}
32
33#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
34pub struct GlobalConfig {
35    pub current_workspace: String,
36}
37
38#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
39pub struct WorkspaceEntry {
40    pub path: PathBuf,
41    pub last_paths: Vec<String>,
42    pub created: DateTime<Utc>,
43    #[serde(default)]
44    pub quick_note_path: Option<String>,
45    #[serde(default)]
46    pub inbox_path: Option<String>,
47    /// Absolute resolved path for runtime use. Not serialized — `path` is
48    /// written to disk as the user configured it (relative, ~/..., or absolute).
49    #[serde(skip)]
50    pub resolved_path: Option<PathBuf>,
51}
52
53impl WorkspaceEntry {
54    /// Returns the resolved absolute path if available, otherwise the original path.
55    pub fn effective_path(&self) -> &PathBuf {
56        self.resolved_path.as_ref().unwrap_or(&self.path)
57    }
58
59    pub fn effective_quick_note_path(&self) -> String {
60        self.quick_note_path
61            .clone()
62            .unwrap_or_else(|| kimun_core::nfs::VaultPath::root().to_string())
63    }
64
65    pub fn effective_inbox_path(&self) -> String {
66        self.inbox_path
67            .clone()
68            .unwrap_or_else(|| kimun_core::DEFAULT_INBOX_PATH.to_string())
69    }
70}
71
72#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
73pub struct WorkspaceConfig {
74    pub global: GlobalConfig,
75    pub workspaces: HashMap<String, WorkspaceEntry>,
76}
77
78impl WorkspaceConfig {
79    pub fn new_empty() -> Self {
80        Self {
81            global: GlobalConfig {
82                current_workspace: String::new(),
83            },
84            workspaces: HashMap::new(),
85        }
86    }
87
88    pub fn add_workspace(
89        &mut self,
90        name: String,
91        path: PathBuf,
92    ) -> Result<(), WorkspaceConfigError> {
93        if self.workspaces.contains_key(&name) {
94            return Err(WorkspaceConfigError::DuplicateWorkspace {
95                name: name.clone(),
96                existing_path: self.workspaces[&name].path.clone(),
97            });
98        }
99
100        let entry = WorkspaceEntry {
101            path,
102            last_paths: Vec::new(),
103            created: Utc::now(),
104            quick_note_path: None,
105            inbox_path: None,
106            resolved_path: None,
107        };
108
109        self.workspaces.insert(name.clone(), entry);
110
111        // Set as current if it's the first workspace
112        if self.workspaces.len() == 1 {
113            self.global.current_workspace = name.clone();
114        }
115
116        Ok(())
117    }
118
119    pub fn get_current_workspace(&self) -> Option<&WorkspaceEntry> {
120        self.workspaces.get(&self.global.current_workspace)
121    }
122
123    pub fn get_workspace(&self, name: &str) -> Option<&WorkspaceEntry> {
124        self.workspaces.get(name)
125    }
126
127    pub fn from_phase1_migration(workspace_dir: PathBuf, last_paths: Vec<String>) -> Self {
128        let mut config = Self::new_empty();
129
130        let entry = WorkspaceEntry {
131            path: workspace_dir,
132            last_paths,
133            created: Utc::now(),
134            quick_note_path: None,
135            inbox_path: None,
136            resolved_path: None,
137        };
138
139        config.workspaces.insert("default".to_string(), entry);
140        config.global.current_workspace = "default".to_string();
141
142        config
143    }
144}