Skip to main content

boarddown_sync/
lib.rs

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}