1mod crdt;
2mod ot;
3mod conflict;
4
5pub use crdt::CrdtEngine;
6pub use ot::OtEngine;
7pub use conflict::ConflictResolver;
8pub use conflict::Resolution;
9
10use boarddown_core::{BoardId, Changeset, ClientId, Error, Storage, Version, TaskOp};
11
12pub struct SyncEngine {
13 client_id: ClientId,
14 storage: Option<Box<dyn Storage>>,
15 remote_url: Option<String>,
16}
17
18impl SyncEngine {
19 pub fn new() -> Self {
20 Self {
21 client_id: ClientId::new(),
22 storage: None,
23 remote_url: None,
24 }
25 }
26
27 pub fn with_storage(mut self, storage: Box<dyn Storage>) -> Self {
28 self.storage = Some(storage);
29 self
30 }
31
32 pub fn with_remote(mut self, url: &str) -> Self {
33 self.remote_url = Some(url.to_string());
34 self
35 }
36
37 pub fn client_id(&self) -> &ClientId {
38 &self.client_id
39 }
40
41 pub async fn sync(&self, board_id: &BoardId) -> Result<SyncResult, Error> {
42 if self.storage.is_none() {
43 return Err(Error::Sync("No storage configured".to_string()));
44 }
45
46 let storage = self.storage.as_ref().unwrap();
47
48 let local_board = storage.load_board(board_id).await?;
49
50 let local_version = Version::initial();
51
52 let remote_changeset = storage.get_changeset(board_id, local_version).await?;
53
54 let mut merged_board = local_board.clone();
55
56 remote_changeset.apply(&mut merged_board)?;
57
58 storage.save_board(&merged_board).await?;
59
60 Ok(SyncResult {
61 pushed: 0,
62 pulled: remote_changeset.operations.len(),
63 conflicts: vec![],
64 })
65 }
66
67 pub async fn push(&self, board_id: &BoardId, changes: &[TaskOp]) -> Result<usize, Error> {
68 if self.storage.is_none() {
69 return Err(Error::Sync("No storage configured".to_string()));
70 }
71
72 let storage = self.storage.as_ref().unwrap();
73
74 let mut board = storage.load_board(board_id).await?;
75
76 for op in changes {
77 let mut changeset = Changeset::new(board_id.clone(), Version::initial());
78 changeset.add_operation(op.clone());
79 changeset.apply(&mut board)?;
80 }
81
82 storage.save_board(&board).await?;
83
84 Ok(changes.len())
85 }
86
87 pub async fn pull(&self, board_id: &BoardId, since: Version) -> Result<Changeset, Error> {
88 if self.storage.is_none() {
89 return Err(Error::Sync("No storage configured".to_string()));
90 }
91
92 let storage = self.storage.as_ref().unwrap();
93 storage.get_changeset(board_id, since).await
94 }
95
96 pub async fn get_local_version(&self, board_id: &BoardId) -> Result<Version, Error> {
97 if self.storage.is_none() {
98 return Err(Error::Sync("No storage configured".to_string()));
99 }
100
101 let storage = self.storage.as_ref().unwrap();
102 let board = storage.load_board(board_id).await?;
103
104 Ok(Version(board.tasks.len() as u64))
105 }
106}
107
108impl Default for SyncEngine {
109 fn default() -> Self {
110 Self::new()
111 }
112}
113
114#[derive(Debug, Clone)]
115pub struct SyncResult {
116 pub pushed: usize,
117 pub pulled: usize,
118 pub conflicts: Vec<Conflict>,
119}
120
121#[derive(Debug, Clone)]
122pub struct Conflict {
123 pub task_id: boarddown_core::TaskId,
124 pub local: boarddown_core::TaskOp,
125 pub remote: boarddown_core::TaskOp,
126}