sqry_core/workspace/
registry.rs1use std::collections::BTreeMap;
4use std::fs::{self, File};
5use std::path::{Path, PathBuf};
6use std::time::SystemTime;
7
8use serde::{Deserialize, Serialize};
9
10use super::error::{WorkspaceError, WorkspaceResult};
11use super::serde_time;
12
13pub const WORKSPACE_REGISTRY_VERSION: u32 = 1;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct WorkspaceRegistry {
19 pub metadata: WorkspaceMetadata,
21 #[serde(default)]
23 pub repositories: Vec<WorkspaceRepository>,
24}
25
26impl WorkspaceRegistry {
27 #[must_use]
29 pub fn new(workspace_name: Option<String>) -> Self {
30 Self {
31 metadata: WorkspaceMetadata::new(workspace_name),
32 repositories: Vec::new(),
33 }
34 }
35
36 pub fn load(path: &Path) -> WorkspaceResult<Self> {
42 let file = File::open(path).map_err(|err| WorkspaceError::io(path, err))?;
43 let mut registry: WorkspaceRegistry =
44 serde_json::from_reader(file).map_err(WorkspaceError::Serialization)?;
45
46 if registry.metadata.version != WORKSPACE_REGISTRY_VERSION {
47 return Err(WorkspaceError::UnsupportedVersion {
48 found: registry.metadata.version,
49 expected: WORKSPACE_REGISTRY_VERSION,
50 });
51 }
52
53 registry.sort_repositories();
54 Ok(registry)
55 }
56
57 pub fn save(&mut self, path: &Path) -> WorkspaceResult<()> {
63 if let Some(parent) = path.parent() {
64 fs::create_dir_all(parent).map_err(|err| WorkspaceError::io(parent, err))?;
65 }
66
67 self.sort_repositories();
68 self.metadata.touch_updated();
69
70 let file = File::create(path).map_err(|err| WorkspaceError::io(path, err))?;
71 serde_json::to_writer_pretty(file, self).map_err(WorkspaceError::Serialization)
72 }
73
74 pub fn upsert_repo(&mut self, repo: WorkspaceRepository) -> WorkspaceResult<()> {
80 let id = repo.id.clone();
81
82 if let Some(existing) = self
83 .repositories
84 .iter_mut()
85 .find(|existing| existing.id == id)
86 {
87 *existing = repo;
88 } else {
89 self.repositories.push(repo);
90 }
91
92 self.metadata.touch_updated();
93 Ok(())
94 }
95
96 pub fn remove_repo(&mut self, repo_id: &WorkspaceRepoId) -> bool {
98 let len_before = self.repositories.len();
99 self.repositories.retain(|repo| repo.id != *repo_id);
100 let removed = self.repositories.len() != len_before;
101
102 if removed {
103 self.metadata.touch_updated();
104 }
105
106 removed
107 }
108
109 fn sort_repositories(&mut self) {
110 self.repositories.sort_by(|a, b| a.id.cmp(&b.id));
111 }
112
113 #[must_use]
115 pub fn as_map(&self) -> BTreeMap<&WorkspaceRepoId, &WorkspaceRepository> {
116 self.repositories
117 .iter()
118 .map(|repo| (&repo.id, repo))
119 .collect()
120 }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct WorkspaceMetadata {
126 pub version: u32,
128 pub workspace_name: Option<String>,
130 #[serde(default)]
132 pub default_discovery_mode: Option<String>,
133 #[serde(with = "serde_time")]
135 pub created_at: SystemTime,
136 #[serde(with = "serde_time")]
138 pub updated_at: SystemTime,
139}
140
141impl WorkspaceMetadata {
142 fn new(workspace_name: Option<String>) -> Self {
143 let now = SystemTime::now();
144 Self {
145 version: WORKSPACE_REGISTRY_VERSION,
146 workspace_name,
147 default_discovery_mode: None,
148 created_at: now,
149 updated_at: now,
150 }
151 }
152
153 fn touch_updated(&mut self) {
154 self.updated_at = SystemTime::now();
155 }
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
160pub struct WorkspaceRepoId(String);
161
162impl WorkspaceRepoId {
163 pub fn new(relative: impl AsRef<Path>) -> WorkspaceRepoId {
165 let path = relative.as_ref();
166
167 let normalized = if path.components().count() == 0 {
168 ".".to_string()
169 } else {
170 path.components()
171 .map(|component| component.as_os_str().to_string_lossy())
172 .collect::<Vec<_>>()
173 .join("/")
174 };
175
176 WorkspaceRepoId(normalized)
177 }
178
179 #[must_use]
181 pub fn as_str(&self) -> &str {
182 &self.0
183 }
184}
185
186impl std::fmt::Display for WorkspaceRepoId {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 write!(f, "{}", self.0)
189 }
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194pub struct WorkspaceRepository {
195 pub id: WorkspaceRepoId,
197 pub name: String,
199 pub root: PathBuf,
201 pub index_path: PathBuf,
203 #[serde(with = "serde_time::option")]
205 pub last_indexed_at: Option<SystemTime>,
206 pub symbol_count: Option<u64>,
208 pub primary_language: Option<String>,
210}
211
212impl WorkspaceRepository {
213 #[must_use]
215 pub fn new(
216 id: WorkspaceRepoId,
217 name: String,
218 root: PathBuf,
219 index_path: PathBuf,
220 last_indexed_at: Option<SystemTime>,
221 ) -> Self {
222 Self {
223 id,
224 name,
225 root,
226 index_path,
227 last_indexed_at,
228 symbol_count: None,
229 primary_language: None,
230 }
231 }
232}