Skip to main content

brume_daemon_proto/
lib.rs

1//! Definition of the protocol needed to communicate with the daemon
2
3use std::{collections::HashMap, fmt::Display};
4
5use brume::concrete::{
6    FSBackend, FsInstanceDescription, Named, local::LocalDir, nextcloud::NextcloudFs,
7};
8
9use brume::synchro::FullSyncStatus;
10use serde::{Deserialize, Serialize};
11use uuid::Uuid;
12
13pub use brume::concrete::{local::LocalDirCreationInfo, nextcloud::NextcloudFsCreationInfo};
14pub use brume::synchro::SynchroSide;
15pub use brume::vfs::virtual_path::{VirtualPath, VirtualPathBuf};
16
17/// Name of the socket where the clients should connect
18pub const BRUME_SOCK_NAME: &str = "brume.socket";
19
20/// An id that uniquely identify a pair of synchronized FS
21#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
22pub struct SynchroId(Uuid);
23
24impl Default for SynchroId {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl From<Uuid> for SynchroId {
31    fn from(value: Uuid) -> Self {
32        Self(value)
33    }
34}
35
36impl SynchroId {
37    pub fn new() -> Self {
38        Self(Uuid::new_v4())
39    }
40
41    pub fn id(&self) -> Uuid {
42        self.0
43    }
44
45    pub fn as_bytes(&self) -> &[u8] {
46        self.0.as_bytes()
47    }
48
49    pub fn short(&self) -> u32 {
50        self.0.as_fields().0
51    }
52}
53
54/// A reference to a filesystem in the SynchroList handled by the brume daemon.
55#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
56pub struct AnyFsRef {
57    id: Uuid,
58    description: AnyFsDescription,
59}
60
61impl Display for AnyFsRef {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        write!(f, "{}", self.description)
64    }
65}
66
67impl From<AnyFsCreationInfo> for AnyFsRef {
68    fn from(value: AnyFsCreationInfo) -> Self {
69        Self {
70            id: Uuid::new_v4(),
71            description: value.into(),
72        }
73    }
74}
75
76impl AnyFsRef {
77    pub fn new(id: Uuid, description: AnyFsDescription) -> Self {
78        Self { id, description }
79    }
80
81    pub fn description(&self) -> &AnyFsDescription {
82        &self.description
83    }
84
85    pub fn name(&self) -> &str {
86        self.description.name()
87    }
88
89    pub fn id(&self) -> Uuid {
90        self.id
91    }
92}
93
94/// Computed status of the synchro, based on synchronization events
95///
96/// This status is mostly gotten from the [`FullSyncStatus`] returned by [`full_sync`]. When
97/// [`full_sync`] is running, the status is set to [`Self::SyncInProgress`]
98///
99/// [`full_sync`]: brume::synchro::Synchro::full_sync
100#[derive(Default, Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
101pub enum SynchroStatus {
102    /// No node in any FS is in Conflict or Error state
103    #[default]
104    Ok,
105    /// At least one node is in Conflict state, but no node is in Error state
106    Conflict,
107    /// At least one node is in Error state
108    Error,
109    /// There is some inconsistency in one of the Vfs, likely coming from a bug in brume.
110    /// User should re-sync the faulty vfs from scratch
111    Desync,
112    /// A synchronization is in progress
113    SyncInProgress,
114}
115
116impl Display for SynchroStatus {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        write!(f, "{:?}", self)
119    }
120}
121
122impl TryFrom<&str> for SynchroStatus {
123    type Error = ();
124
125    fn try_from(value: &str) -> Result<Self, <SynchroStatus as TryFrom<&str>>::Error> {
126        match value {
127            "Ok" => Ok(Self::Ok),
128            "Conflict" => Ok(Self::Conflict),
129            "Error" => Ok(Self::Error),
130            "Desync" => Ok(Self::Desync),
131            "SyncInProgress" => Ok(Self::SyncInProgress),
132            _ => Err(()),
133        }
134    }
135}
136
137impl From<FullSyncStatus> for SynchroStatus {
138    fn from(value: FullSyncStatus) -> Self {
139        match value {
140            FullSyncStatus::Ok => Self::Ok,
141            FullSyncStatus::Conflict => Self::Conflict,
142            FullSyncStatus::Error => Self::Error,
143            FullSyncStatus::Desync => Self::Desync,
144        }
145    }
146}
147
148impl SynchroStatus {
149    /// Returns true if the status allows further synchronization
150    pub fn is_synchronizable(self) -> bool {
151        !matches!(self, SynchroStatus::Desync | SynchroStatus::SyncInProgress)
152    }
153}
154
155/// User controlled state of the synchro
156#[derive(Default, PartialEq, Eq, Copy, Clone, Debug, Serialize, Deserialize)]
157pub enum SynchroState {
158    #[default]
159    Running,
160    Paused,
161}
162
163impl Display for SynchroState {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        write!(f, "{:?}", self)
166    }
167}
168
169impl TryFrom<&str> for SynchroState {
170    type Error = ();
171
172    fn try_from(value: &str) -> Result<Self, Self::Error> {
173        match value {
174            "Running" => Ok(Self::Running),
175            "Paused" => Ok(Self::Paused),
176            _ => Err(()),
177        }
178    }
179}
180
181/// A [`Synchro`] where the [`Backend`] filesystems are only known at runtime.
182///
183/// This type represents only an index and needs a valid SynchroList, to actually be used. It
184/// must not be used after the Synchro it points to has been removed from the SynchroList.
185///
186/// [`Backend`]: FSBackend
187/// [`Synchro`]: brume::synchro::Synchro
188#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
189pub struct AnySynchroRef {
190    local: AnyFsRef,
191    remote: AnyFsRef,
192    /// the status of the synchro is automatically updated, for example in case of error or
193    /// conflict
194    status: SynchroStatus,
195    /// the state is defined by the user, for example running or paused
196    state: SynchroState,
197    name: String,
198}
199
200impl Display for AnySynchroRef {
201    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202        write!(f, "synchro between {} and {}", self.local, self.remote)
203    }
204}
205
206impl AnySynchroRef {
207    pub fn new(local_ref: AnyFsRef, remote_ref: AnyFsRef, name: String) -> Self {
208        AnySynchroRef {
209            local: local_ref,
210            remote: remote_ref,
211            status: SynchroStatus::default(),
212            state: SynchroState::default(),
213            name,
214        }
215    }
216
217    /// Returns the local counterpart of the synchro
218    pub fn local(&self) -> &AnyFsRef {
219        &self.local
220    }
221
222    /// Returns the remote counterpart of the synchro
223    pub fn remote(&self) -> &AnyFsRef {
224        &self.remote
225    }
226
227    /// Returns the name of the synchro
228    ///
229    /// This name is only "relatively" unique, meaning that names can be reused. However, at a
230    /// specific point in time and for a specific SynchroList, there should be no collision.
231    pub fn name(&self) -> &str {
232        &self.name
233    }
234
235    /// Returns the status of this synchro
236    pub fn status(&self) -> SynchroStatus {
237        self.status
238    }
239
240    /// Updates the status of this synchro
241    pub fn set_status(&mut self, status: SynchroStatus) {
242        self.status = status
243    }
244
245    /// Returns the state of this synchro
246    pub fn state(&self) -> SynchroState {
247        self.state
248    }
249
250    /// Updates the state of this synchro
251    pub fn set_state(&mut self, state: SynchroState) {
252        self.state = state
253    }
254}
255
256/// The information needed to create a FS that can be synchronized.
257#[derive(Clone, Debug, Serialize, Deserialize)]
258pub enum AnyFsCreationInfo {
259    LocalDir(<LocalDir as FSBackend>::CreationInfo),
260    Nextcloud(<NextcloudFs as FSBackend>::CreationInfo),
261}
262
263impl AnyFsCreationInfo {
264    pub async fn validate(&self) -> Result<(), String> {
265        match self {
266            AnyFsCreationInfo::LocalDir(info) => LocalDir::validate(info)
267                .await
268                .map_err(|_| "Invalid directory for synchronization".to_string()),
269            AnyFsCreationInfo::Nextcloud(info) => NextcloudFs::validate(info).await.map_err(|e| {
270                let msg = if let Some(msg) = e.protocol_error_message() {
271                    msg
272                } else {
273                    e.to_string()
274                };
275                format!("Failed to connect to Nextcloud server: {msg}")
276            }),
277        }
278    }
279}
280
281/// The information needed to create a new synchro between filesystems
282#[derive(Debug, Clone)]
283pub struct AnySynchroCreationInfo {
284    local: AnyFsCreationInfo,
285    remote: AnyFsCreationInfo,
286    name: Option<String>,
287}
288
289impl AnySynchroCreationInfo {
290    pub fn new(local: AnyFsCreationInfo, remote: AnyFsCreationInfo, name: Option<String>) -> Self {
291        Self {
292            local,
293            remote,
294            name,
295        }
296    }
297
298    pub fn local(&self) -> &AnyFsCreationInfo {
299        &self.local
300    }
301
302    pub fn remote(&self) -> &AnyFsCreationInfo {
303        &self.remote
304    }
305
306    pub fn name(&self) -> Option<&str> {
307        self.name.as_deref()
308    }
309}
310
311/// The information needed to describe a FS that can be synchronized.
312///
313/// This is used for display and to avoid duplicate synchros.
314#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
315pub enum AnyFsDescription {
316    LocalDir(<LocalDir as FSBackend>::Description),
317    Nextcloud(<NextcloudFs as FSBackend>::Description),
318}
319
320impl AnyFsDescription {
321    pub fn name(&self) -> &str {
322        match self {
323            AnyFsDescription::LocalDir(desc) => desc.name(),
324            AnyFsDescription::Nextcloud(desc) => desc.name(),
325        }
326    }
327
328    pub fn type_name(&self) -> &str {
329        match self {
330            AnyFsDescription::LocalDir(_) => LocalDir::TYPE_NAME,
331            AnyFsDescription::Nextcloud(_) => NextcloudFs::TYPE_NAME,
332        }
333    }
334}
335
336impl Display for AnyFsDescription {
337    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
338        match self {
339            AnyFsDescription::LocalDir(local) => local.fmt(f),
340            AnyFsDescription::Nextcloud(nextcloud) => nextcloud.fmt(f),
341        }
342    }
343}
344
345impl From<AnyFsCreationInfo> for AnyFsDescription {
346    fn from(value: AnyFsCreationInfo) -> Self {
347        match value {
348            AnyFsCreationInfo::LocalDir(dir) => Self::LocalDir(dir.into()),
349            AnyFsCreationInfo::Nextcloud(nextcloud) => Self::Nextcloud(nextcloud.into()),
350        }
351    }
352}
353
354#[tarpc::service]
355pub trait BrumeService {
356    /// Creates a new synchronization between a "remote" and a "local" fs
357    async fn new_synchro(
358        local: AnyFsCreationInfo,
359        remote: AnyFsCreationInfo,
360        name: Option<String>,
361    ) -> Result<(), String>;
362
363    /// Lists all the existing synchronizations registered in the daemon
364    async fn list_synchros() -> HashMap<SynchroId, AnySynchroRef>;
365
366    /// Deletes a synchronization
367    async fn delete_synchro(id: SynchroId) -> Result<(), String>;
368
369    /// Pauses a synchronization
370    async fn pause_synchro(id: SynchroId) -> Result<(), String>;
371
372    /// Resumes a synchronization
373    async fn resume_synchro(id: SynchroId) -> Result<(), String>;
374
375    /// Resolves a conflict
376    async fn resolve_conflict(
377        id: SynchroId,
378        path: VirtualPathBuf,
379        side: SynchroSide,
380    ) -> Result<(), String>;
381}