1use crate::Result;
3use indexmap::{IndexMap, IndexSet};
4use serde::{Deserialize, Serialize};
5use sos_core::{
6 commit::{CommitHash, CommitState, Comparison},
7 AccountId, SecretId, VaultId,
8};
9use sos_core::{
10 device::DevicePublicKey,
11 events::{
12 patch::{
13 AccountDiff, AccountPatch, DeviceDiff, DevicePatch, FolderDiff,
14 FolderPatch,
15 },
16 AccountEvent, DeviceEvent, WriteEvent,
17 },
18};
19use sos_vault::Summary;
20use std::collections::HashMap;
21
22#[cfg(feature = "files")]
23use sos_core::{
24 events::{
25 patch::{FileDiff, FilePatch},
26 FileEvent,
27 },
28 ExternalFile, ExternalFileName, SecretPath,
29};
30
31#[derive(Debug, Serialize, Deserialize)]
36#[serde(rename_all = "camelCase")]
37pub struct DebugTree {
38 pub account_id: AccountId,
40 pub folders: IndexSet<Summary>,
42 pub status: SyncStatus,
44 pub events: DebugEventLogs,
46}
47
48#[derive(Debug, Default, Serialize, Deserialize)]
50#[serde(default)]
51pub struct DebugEventLogs {
52 pub identity: DebugEvents,
54 pub account: DebugEvents,
56 pub device: DebugEvents,
58 #[cfg(feature = "files")]
60 pub file: DebugEvents,
61 pub folders: HashMap<VaultId, DebugEvents>,
63}
64
65#[derive(Debug, Default, Serialize, Deserialize)]
67#[serde(default)]
68pub struct DebugEvents {
69 #[serde(skip_serializing_if = "Vec::is_empty")]
71 pub leaves: Vec<CommitHash>,
72 pub length: usize,
74 #[serde(skip_serializing_if = "Option::is_none")]
76 pub root: Option<CommitHash>,
77}
78
79#[derive(Debug, Default, Clone, PartialEq, Eq)]
81pub struct SyncPacket {
82 pub status: SyncStatus,
84 pub diff: SyncDiff,
86 pub compare: Option<SyncCompare>,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq)]
92pub enum MaybeDiff<T> {
93 Diff(T),
95 Compare(Option<CommitState>),
99}
100
101#[derive(Default, Debug, Clone, PartialEq, Eq)]
103pub struct SyncDiff {
104 pub identity: Option<MaybeDiff<FolderDiff>>,
106 pub account: Option<MaybeDiff<AccountDiff>>,
108 pub device: Option<MaybeDiff<DeviceDiff>>,
110 #[cfg(feature = "files")]
112 pub files: Option<MaybeDiff<FileDiff>>,
113 pub folders: IndexMap<VaultId, MaybeDiff<FolderDiff>>,
115}
116
117#[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)]
121pub struct SyncStatus {
122 pub root: CommitHash,
124 pub identity: CommitState,
126 pub account: CommitState,
128 pub device: CommitState,
130 #[cfg(feature = "files")]
132 pub files: Option<CommitState>,
133 pub folders: IndexMap<VaultId, CommitState>,
135}
136
137#[derive(Debug, Default, PartialEq, Eq)]
139pub struct CreateSet {
140 pub identity: FolderPatch,
142 pub account: AccountPatch,
144 pub device: DevicePatch,
146 #[cfg(feature = "files")]
148 pub files: FilePatch,
149 pub folders: HashMap<VaultId, FolderPatch>,
151}
152
153#[derive(Debug, Default, Clone, PartialEq, Eq)]
161pub struct UpdateSet {
162 pub identity: Option<FolderDiff>,
164 pub account: Option<AccountDiff>,
166 pub device: Option<DeviceDiff>,
168 #[cfg(feature = "files")]
170 pub files: Option<FileDiff>,
171 pub folders: HashMap<VaultId, FolderDiff>,
173}
174
175#[derive(Debug, Default, Clone, PartialEq, Eq)]
177pub struct MergeOutcome {
178 pub changes: u64,
183
184 pub tracked: TrackedChanges,
200
201 #[doc(hidden)]
207 #[cfg(feature = "files")]
208 pub external_files: IndexSet<ExternalFile>,
209}
210
211#[derive(Debug, Default, Clone, PartialEq, Eq)]
213pub struct TrackedChanges {
214 pub identity: IndexSet<TrackedFolderChange>,
216
217 pub device: IndexSet<TrackedDeviceChange>,
219
220 pub account: IndexSet<TrackedAccountChange>,
222
223 #[cfg(feature = "files")]
225 pub files: IndexSet<TrackedFileChange>,
226
227 pub folders: HashMap<VaultId, IndexSet<TrackedFolderChange>>,
229}
230
231impl TrackedChanges {
232 pub fn add_tracked_folder_changes(
235 &mut self,
236 folder_id: &VaultId,
237 changes: IndexSet<TrackedFolderChange>,
238 ) {
239 if !changes.is_empty() {
240 self.folders.insert(*folder_id, changes);
241 }
242 }
243
244 pub async fn new_folder_records(
246 value: &FolderPatch,
247 ) -> Result<IndexSet<TrackedFolderChange>> {
248 let events = value.into_events::<WriteEvent>().await?;
249 Self::new_folder_events(events).await
250 }
251
252 pub async fn new_folder_events(
255 events: Vec<WriteEvent>,
256 ) -> Result<IndexSet<TrackedFolderChange>> {
257 let mut changes = IndexSet::new();
258 for event in events {
259 match event {
260 WriteEvent::CreateSecret(secret_id, _) => {
261 changes.insert(TrackedFolderChange::Created(secret_id));
262 }
263 WriteEvent::UpdateSecret(secret_id, _) => {
264 changes.insert(TrackedFolderChange::Updated(secret_id));
265 }
266 WriteEvent::DeleteSecret(secret_id) => {
267 let created = TrackedFolderChange::Created(secret_id);
268 let updated = TrackedFolderChange::Updated(secret_id);
269 let had_created = changes.shift_remove(&created);
270 changes.shift_remove(&updated);
271 if !had_created {
272 changes
273 .insert(TrackedFolderChange::Deleted(secret_id));
274 }
275 }
276 _ => {}
277 }
278 }
279 Ok(changes)
280 }
281
282 pub async fn new_account_records(
284 value: &AccountPatch,
285 ) -> Result<IndexSet<TrackedAccountChange>> {
286 let events = value.into_events::<AccountEvent>().await?;
287 Self::new_account_events(events).await
288 }
289
290 pub async fn new_account_events(
293 events: Vec<AccountEvent>,
294 ) -> Result<IndexSet<TrackedAccountChange>> {
295 let mut changes = IndexSet::new();
296 for event in events {
297 match event {
298 AccountEvent::CreateFolder(folder_id, _) => {
299 changes.insert(TrackedAccountChange::FolderCreated(
300 folder_id,
301 ));
302 }
303 AccountEvent::RenameFolder(folder_id, _)
304 | AccountEvent::UpdateFolder(folder_id, _) => {
305 changes.insert(TrackedAccountChange::FolderUpdated(
306 folder_id,
307 ));
308 }
309 AccountEvent::DeleteFolder(folder_id) => {
310 let created =
311 TrackedAccountChange::FolderCreated(folder_id);
312 let updated =
313 TrackedAccountChange::FolderUpdated(folder_id);
314 let had_created = changes.shift_remove(&created);
315 changes.shift_remove(&updated);
316
317 if !had_created {
318 changes.insert(TrackedAccountChange::FolderDeleted(
319 folder_id,
320 ));
321 }
322 }
323 _ => {}
324 }
325 }
326 Ok(changes)
327 }
328
329 pub async fn new_device_records(
331 value: &DevicePatch,
332 ) -> Result<IndexSet<TrackedDeviceChange>> {
333 let events = value.into_events::<DeviceEvent>().await?;
334 Self::new_device_events(events).await
335 }
336
337 pub async fn new_device_events(
340 events: Vec<DeviceEvent>,
341 ) -> Result<IndexSet<TrackedDeviceChange>> {
342 let mut changes = IndexSet::new();
343 for event in events {
344 match event {
345 DeviceEvent::Trust(device) => {
346 changes.insert(TrackedDeviceChange::Trusted(
347 device.public_key().to_owned(),
348 ));
349 }
350 DeviceEvent::Revoke(public_key) => {
351 let trusted = TrackedDeviceChange::Trusted(public_key);
352 let had_trusted = changes.shift_remove(&trusted);
353 if !had_trusted {
354 changes
355 .insert(TrackedDeviceChange::Revoked(public_key));
356 }
357 }
358 _ => {}
359 }
360 }
361 Ok(changes)
362 }
363
364 #[cfg(feature = "files")]
366 pub async fn new_file_records(
367 value: &FilePatch,
368 ) -> Result<IndexSet<TrackedFileChange>> {
369 let events = value.into_events::<FileEvent>().await?;
370 Self::new_file_events(events).await
371 }
372
373 #[cfg(feature = "files")]
376 pub async fn new_file_events(
377 events: Vec<FileEvent>,
378 ) -> Result<IndexSet<TrackedFileChange>> {
379 let mut changes = IndexSet::new();
380 for event in events {
381 match event {
382 FileEvent::CreateFile(owner, name) => {
383 changes.insert(TrackedFileChange::Created(owner, name));
384 }
385 FileEvent::MoveFile { name, from, dest } => {
386 changes.insert(TrackedFileChange::Moved {
387 name,
388 from,
389 dest,
390 });
391 }
392 FileEvent::DeleteFile(owner, name) => {
393 let created = TrackedFileChange::Created(owner, name);
394 let had_created = changes.shift_remove(&created);
395
396 let moved = changes.iter().find_map(|event| {
397 if let TrackedFileChange::Moved {
398 name: moved_name,
399 dest,
400 from,
401 } = event
402 {
403 if moved_name == &name && dest == &owner {
404 return Some(TrackedFileChange::Moved {
405 name: *moved_name,
406 from: *from,
407 dest: *dest,
408 });
409 }
410 }
411 None
412 });
413 if let Some(moved) = moved {
414 changes.shift_remove(&moved);
415 }
416
417 if !had_created {
418 changes
419 .insert(TrackedFileChange::Deleted(owner, name));
420 }
421 }
422 _ => {}
423 }
424 }
425 Ok(changes)
426 }
427}
428
429#[derive(Debug, Clone, Hash, PartialEq, Eq)]
431pub enum TrackedDeviceChange {
432 Trusted(DevicePublicKey),
434 Revoked(DevicePublicKey),
436}
437
438#[derive(Debug, Clone, Hash, PartialEq, Eq)]
440pub enum TrackedAccountChange {
441 FolderCreated(VaultId),
443 FolderUpdated(VaultId),
445 FolderDeleted(VaultId),
447}
448
449#[cfg(feature = "files")]
451#[derive(Debug, Clone, Hash, PartialEq, Eq)]
452pub enum TrackedFileChange {
453 Created(SecretPath, ExternalFileName),
455 Moved {
457 name: ExternalFileName,
459 from: SecretPath,
461 dest: SecretPath,
463 },
464 Deleted(SecretPath, ExternalFileName),
466}
467
468#[derive(Debug, Clone, Hash, PartialEq, Eq)]
470pub enum TrackedFolderChange {
471 Created(SecretId),
473 Updated(SecretId),
475 Deleted(SecretId),
477}
478
479#[derive(Debug, Default, Clone, Eq, PartialEq)]
490pub struct SyncCompare {
491 pub identity: Option<Comparison>,
493 pub account: Option<Comparison>,
495 pub device: Option<Comparison>,
497 #[cfg(feature = "files")]
499 pub files: Option<Comparison>,
500 pub folders: IndexMap<VaultId, Comparison>,
502}
503
504impl SyncCompare {
505 pub fn maybe_conflict(&self) -> MaybeConflict {
507 MaybeConflict {
508 identity: self
509 .identity
510 .as_ref()
511 .map(|c| matches!(c, Comparison::Unknown))
512 .unwrap_or(false),
513 account: self
514 .account
515 .as_ref()
516 .map(|c| matches!(c, Comparison::Unknown))
517 .unwrap_or(false),
518 device: self
519 .device
520 .as_ref()
521 .map(|c| matches!(c, Comparison::Unknown))
522 .unwrap_or(false),
523 #[cfg(feature = "files")]
524 files: self
525 .files
526 .as_ref()
527 .map(|c| matches!(c, Comparison::Unknown))
528 .unwrap_or(false),
529 folders: self
530 .folders
531 .iter()
532 .map(|(k, v)| (*k, matches!(v, Comparison::Unknown)))
533 .collect(),
534 }
535 }
536}
537
538#[derive(Debug, Default, Eq, PartialEq)]
540pub struct MaybeConflict {
541 pub identity: bool,
543 pub account: bool,
545 pub device: bool,
547 #[cfg(feature = "files")]
549 pub files: bool,
550 pub folders: IndexMap<VaultId, bool>,
552}
553
554impl MaybeConflict {
555 pub fn has_conflicts(&self) -> bool {
557 let mut has_conflicts = self.identity || self.account || self.device;
558
559 #[cfg(feature = "files")]
560 {
561 has_conflicts = has_conflicts || self.files;
562 }
563
564 for (_, value) in &self.folders {
565 has_conflicts = has_conflicts || *value;
566 if has_conflicts {
567 break;
568 }
569 }
570
571 has_conflicts
572 }
573}