Skip to main content

boarddown_sync/
conflict.rs

1use boarddown_core::{Board, Error, Task, ConflictResolution, TaskOp};
2
3use super::Conflict;
4
5#[derive(Debug, Clone, PartialEq)]
6pub enum Resolution {
7    Local,
8    Remote,
9    Manual { local: Task, remote: Task },
10}
11
12pub struct ConflictResolver {
13    strategy: ConflictResolution,
14}
15
16impl ConflictResolver {
17    pub fn new(strategy: ConflictResolution) -> Self {
18        Self { strategy }
19    }
20
21    pub fn resolve(&self, conflict: &Conflict, board: &mut Board) -> Result<Resolution, Error> {
22        match self.strategy {
23            ConflictResolution::LastWriteWins => {
24                Ok(Resolution::Remote)
25            }
26            ConflictResolution::Local => {
27                Ok(Resolution::Local)
28            }
29            ConflictResolution::Remote => {
30                Ok(Resolution::Remote)
31            }
32            ConflictResolution::Manual => {
33                let local_task = board.tasks.get(&conflict.task_id).cloned();
34                let remote_task = self.apply_operation_to_mock_board(&conflict.remote);
35                match (local_task, remote_task) {
36                    (Some(local), Some(remote)) => Ok(Resolution::Manual { local, remote }),
37                    (Some(local), None) => Ok(Resolution::Local),
38                    (None, Some(remote)) => Ok(Resolution::Remote),
39                    (None, None) => Ok(Resolution::Remote),
40                }
41            }
42        }
43    }
44
45    fn apply_operation_to_mock_board(&self, op: &TaskOp) -> Option<Task> {
46        let mut task = Task::builder()
47            .id(op.task_id.clone())
48            .title("temp")
49            .build()
50            .ok()?;
51        
52        match &op.operation {
53            boarddown_core::Operation::SetTitle { old: _, new } => {
54                task.title = new.clone();
55            }
56            boarddown_core::Operation::SetStatus { old: _, new } => {
57                task.status = *new;
58            }
59            _ => return None,
60        }
61        
62        Some(task)
63    }
64
65    pub fn resolve_task(&self, conflict: &Conflict) -> Result<Task, Error> {
66        match self.strategy {
67            ConflictResolution::LastWriteWins | ConflictResolution::Remote => {
68                self.apply_operation_to_mock_board(&conflict.remote)
69                    .ok_or_else(|| Error::Conflict("Cannot resolve task".to_string()))
70            }
71            ConflictResolution::Local => {
72                self.apply_operation_to_mock_board(&conflict.local)
73                    .ok_or_else(|| Error::Conflict("Cannot resolve task".to_string()))
74            }
75            ConflictResolution::Manual => {
76                self.apply_operation_to_mock_board(&conflict.remote)
77                    .ok_or_else(|| Error::Conflict("Cannot resolve task".to_string()))
78            }
79        }
80    }
81
82    pub fn detect_conflicts(&self, local_ops: &[TaskOp], remote_ops: &[TaskOp]) -> Result<Vec<Conflict>, Error> {
83        let mut conflicts = Vec::new();
84        
85        for local_op in local_ops {
86            for remote_op in remote_ops {
87                if local_op.task_id == remote_op.task_id && local_op.timestamp != remote_op.timestamp {
88                    if self.operations_conflict(&local_op.operation, &remote_op.operation) {
89                        conflicts.push(Conflict {
90                            task_id: local_op.task_id.clone(),
91                            local: local_op.clone(),
92                            remote: remote_op.clone(),
93                        });
94                    }
95                }
96            }
97        }
98        
99        Ok(conflicts)
100    }
101
102    fn operations_conflict(&self, op1: &boarddown_core::Operation, op2: &boarddown_core::Operation) -> bool {
103        use boarddown_core::Operation;
104        
105        match (op1, op2) {
106            (Operation::SetTitle { .. }, Operation::SetTitle { .. }) => true,
107            (Operation::SetStatus { .. }, Operation::SetStatus { .. }) => true,
108            (Operation::SetMetadata { .. }, Operation::SetMetadata { .. }) => true,
109            (Operation::Delete, _) | (_, Operation::Delete) => true,
110            _ => false,
111        }
112    }
113}
114
115impl Default for ConflictResolver {
116    fn default() -> Self {
117        Self::new(ConflictResolution::LastWriteWins)
118    }
119}