1use crate::Result;
9use crate::config::Config;
10use crate::git::RemoteManager;
11use std::time::Instant;
12use tracing::instrument;
13
14pub struct SyncService {
20 config: Config,
22}
23
24impl SyncService {
25 #[must_use]
27 pub const fn new(config: Config) -> Self {
28 Self { config }
29 }
30
31 #[must_use]
36 pub fn no_op() -> Self {
37 Self {
38 config: Config::default(),
39 }
40 }
41
42 #[must_use]
46 pub const fn is_enabled(&self) -> bool {
47 false }
49
50 #[instrument(skip(self), fields(operation = "sync.fetch"))]
58 pub fn fetch(&self) -> Result<SyncStats> {
59 let start = Instant::now();
60
61 let stats = SyncStats::default();
63
64 metrics::counter!(
65 "memory_sync_total",
66 "direction" => "fetch",
67 "domain" => "project",
68 "status" => "noop"
69 )
70 .increment(1);
71 metrics::histogram!("memory_sync_duration_ms", "direction" => "fetch")
72 .record(start.elapsed().as_secs_f64() * 1000.0);
73
74 Ok(stats)
75 }
76
77 #[instrument(skip(self), fields(operation = "sync.push"))]
85 pub fn push(&self) -> Result<SyncStats> {
86 let start = Instant::now();
87
88 let stats = SyncStats::default();
90
91 metrics::counter!(
92 "memory_sync_total",
93 "direction" => "push",
94 "domain" => "project",
95 "status" => "noop"
96 )
97 .increment(1);
98 metrics::histogram!("memory_sync_duration_ms", "direction" => "push")
99 .record(start.elapsed().as_secs_f64() * 1000.0);
100
101 Ok(stats)
102 }
103
104 #[instrument(skip(self), fields(operation = "sync.full"))]
112 pub fn sync(&self) -> Result<SyncStats> {
113 let start = Instant::now();
114
115 let stats = SyncStats::default();
117
118 metrics::counter!(
119 "memory_sync_total",
120 "direction" => "full",
121 "domain" => "project",
122 "status" => "noop"
123 )
124 .increment(1);
125 metrics::histogram!("memory_sync_duration_ms", "direction" => "full")
126 .record(start.elapsed().as_secs_f64() * 1000.0);
127
128 Ok(stats)
129 }
130
131 #[allow(clippy::unused_self)]
139 pub const fn is_available(&self) -> Result<bool> {
140 Ok(false) }
142
143 pub fn remote_name(&self) -> Result<Option<String>> {
151 let repo_path = match &self.config.repo_path {
152 Some(p) => p,
153 None => return Ok(None),
154 };
155
156 let remote = RemoteManager::new(repo_path);
157 remote.default_remote()
158 }
159
160 pub fn remote_url(&self) -> Result<Option<String>> {
168 let repo_path = match &self.config.repo_path {
169 Some(p) => p,
170 None => return Ok(None),
171 };
172
173 let remote = RemoteManager::new(repo_path);
174 let remote_name = match remote.default_remote()? {
175 Some(name) => name,
176 None => return Ok(None),
177 };
178
179 remote.get_remote_url(&remote_name)
180 }
181}
182
183impl Default for SyncService {
184 fn default() -> Self {
185 Self::new(Config::default())
186 }
187}
188
189#[derive(Debug, Clone, Default)]
191pub struct SyncStats {
192 pub pushed: usize,
194 pub pulled: usize,
196 pub conflicts: usize,
198}
199
200impl SyncStats {
201 #[must_use]
203 pub const fn is_empty(&self) -> bool {
204 self.pushed == 0 && self.pulled == 0 && self.conflicts == 0
205 }
206
207 #[must_use]
209 pub fn summary(&self) -> String {
210 if self.is_empty() {
211 "Already up to date".to_string()
212 } else {
213 format!(
214 "Pushed: {}, Pulled: {}, Conflicts: {}",
215 self.pushed, self.pulled, self.conflicts
216 )
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn test_sync_stats_empty() {
227 let stats = SyncStats::default();
228 assert!(stats.is_empty());
229 assert_eq!(stats.summary(), "Already up to date");
230 }
231
232 #[test]
233 fn test_sync_stats_summary() {
234 let stats = SyncStats {
235 pushed: 5,
236 pulled: 3,
237 conflicts: 1,
238 };
239 assert!(!stats.is_empty());
240 assert!(stats.summary().contains("Pushed: 5"));
241 assert!(stats.summary().contains("Pulled: 3"));
242 assert!(stats.summary().contains("Conflicts: 1"));
243 }
244
245 #[test]
246 fn test_sync_service_no_repo() {
247 let service = SyncService::default();
248
249 let result = service.fetch();
251 assert!(result.is_ok());
252 assert!(result.unwrap().is_empty());
253
254 let result = service.push();
255 assert!(result.is_ok());
256 assert!(result.unwrap().is_empty());
257
258 let result = service.sync();
259 assert!(result.is_ok());
260 assert!(result.unwrap().is_empty());
261 }
262
263 #[test]
264 fn test_sync_service_availability() {
265 let service = SyncService::default();
266 assert!(!service.is_available().unwrap());
267 }
268}