syncthing_rs/types/
events.rs

1//! Event types, as defined [here](https://docs.syncthing.net/dev/events.html)
2use std::collections::HashMap;
3
4use serde::{Deserialize, Serialize};
5
6use super::config::{
7    Defaults, DeviceConfiguration, FolderConfiguration, GuiConfiguration, LDAPConfiguration,
8    NewDeviceConfiguration, ObservedDevice,
9};
10
11/// Represents an [Event](https://docs.syncthing.net/dev/events.html)
12#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
13#[serde(rename_all = "camelCase")]
14pub struct Event {
15    pub id: u64,
16    #[serde(rename = "globalID")]
17    pub global_id: u64,
18    pub time: chrono::DateTime<chrono::Utc>,
19    #[serde(flatten)]
20    pub ty: EventType,
21}
22
23#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
24#[serde(tag = "type", content = "data")]
25pub enum EventType {
26    ClusterConfigReceived {
27        device: String,
28    },
29    #[serde(rename_all = "camelCase")]
30    ConfigSaved {
31        version: u64,
32        folders: Vec<FolderConfiguration>,
33        devices: Vec<DeviceConfiguration>,
34        gui: GuiConfiguration,
35        ldap: LDAPConfiguration,
36        remote_ignored_devices: Vec<ObservedDevice>,
37        defaults: Box<Defaults>,
38    },
39    #[serde(rename_all = "camelCase")]
40    DeviceConnected {
41        addr: String,
42        id: String,
43        device_name: String,
44        client_name: String,
45        client_version: String,
46        #[serde(rename = "type")]
47        ty: ConnectionType,
48    },
49    DeviceDisconnected {
50        error: String,
51        id: String,
52    },
53    DeviceDiscovered {
54        addrs: Vec<String>,
55        device: String,
56    },
57    DevicePaused {
58        device: String,
59    },
60    DeviceRejected {}, // Deprecated
61    DeviceResumed {
62        device: String,
63    },
64    DownloadProgress {
65        #[serde(flatten)]
66        folders: HashMap<String, HashMap<String, FileDownloadProgress>>,
67    },
68    Failure(String),
69    #[serde(rename_all = "camelCase")]
70    FolderCompletion {
71        completion: f64,
72        device: String,
73        folder: String,
74        global_bytes: u64,
75        global_items: u64,
76        need_bytes: u64,
77        need_deletes: u64,
78        need_items: u64,
79        remote_state: String,
80        sequence: u64,
81    },
82    FolderErrors {
83        errors: Vec<FolderError>,
84        folder: String,
85    },
86    FolderPaused {
87        id: String,
88        label: String,
89    },
90    FolderRejected {}, // Deprecated
91    FolderResumed {
92        id: String,
93        label: String,
94    },
95    FolderScanProgress {
96        total: u64,
97        rate: u64,
98        current: u64,
99        folder: String,
100    },
101    FolderSummary {
102        folder: String,
103        summary: FolderSummary,
104    },
105    FolderWatchStateChanged {
106        folder: String,
107        from: String,
108        to: String,
109    },
110    ItemFinished {
111        item: String,
112        folder: String,
113        error: Option<String>,
114        #[serde(rename = "type")]
115        ty: String,
116        action: String,
117    },
118    ItemStarted {
119        item: String,
120        folder: String,
121        #[serde(rename = "type")]
122        ty: String,
123        action: String,
124    },
125    ListenAddressesChanged {
126        address: ListenAddressChanged,
127        wan: Option<Vec<ListenAddressChanged>>,
128        lan: Option<Vec<ListenAddressChanged>>,
129    },
130    LocalChangeDetected {
131        action: String,
132        folder: String,
133        #[serde(rename = "folderID")]
134        folder_id: String,
135        label: String,
136        path: String,
137        #[serde(rename = "type")]
138        ty: String,
139    },
140    LocalIndexUpdated {
141        folder: String,
142        items: u64,
143        filenames: Vec<String>,
144        sequence: u64,
145    },
146    #[serde(rename_all = "camelCase")]
147    LoginAttempt {
148        remote_address: String,
149        username: String,
150        success: bool,
151        proxy: Option<String>,
152    },
153    PendingDevicesChanged {
154        added: Option<Vec<AddedPendingDeviceChanged>>,
155        removed: Option<Vec<RemovedPendingDeviceChanged>>,
156    },
157    PendingFoldersChanged {
158        added: Option<Vec<AddedPendingFolderChanged>>,
159        removed: Option<Vec<RemovedPendingFolderChanged>>,
160    },
161    #[serde(rename_all = "camelCase")]
162    RemoteChangeDetected {
163        #[serde(rename = "type")]
164        ty: String,
165        action: String,
166        folder: String,
167        #[serde(rename = "folderID")]
168        folder_id: String,
169        path: String,
170        label: String,
171        modified_by: String,
172    },
173    RemoteDownloadProgress {
174        state: HashMap<String, u64>,
175        device: String,
176        folder: String,
177    },
178    RemoteIndexUpdated {
179        device: String,
180        folder: String,
181        items: u64,
182    },
183    Starting {
184        home: String,
185    },
186    StartupComplete {
187        #[serde(rename = "myID")]
188        my_id: String,
189    },
190    StateChanged {
191        folder: String,
192        from: String,
193        duration: Option<f64>,
194        to: String,
195    },
196}
197
198#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
199pub enum ConnectionType {
200    #[serde(rename = "tcp-client")]
201    TCPClient,
202    #[serde(rename = "tcp-server")]
203    TCPServer,
204    #[serde(rename = "relay-client")]
205    RelayClient,
206    #[serde(rename = "relay-server")]
207    RelayServer,
208    #[serde(rename = "quic-server")]
209    QuicServer,
210}
211
212#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
213pub struct FolderError {
214    pub error: String,
215    pub path: String,
216}
217
218#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
219#[serde(rename_all = "camelCase")]
220pub struct FolderSummary {
221    pub errors: i64,
222    pub global_files: i64,
223    pub global_directories: i64,
224    pub global_symlinks: i64,
225    pub global_deleted: i64,
226    pub global_bytes: i64,
227    pub global_total_items: i64,
228    pub local_files: i64,
229    pub local_directories: i64,
230    pub local_symlinks: i64,
231    pub local_deleted: i64,
232    pub local_bytes: i64,
233    pub local_total_items: i64,
234    pub need_files: i64,
235    pub need_directories: i64,
236    pub need_symlinks: i64,
237    pub need_deletes: i64,
238    pub need_bytes: i64,
239    pub need_total_items: i64,
240    pub receive_only_changed_files: i64,
241    pub receive_only_changed_directories: i64,
242    pub receive_only_changed_symlinks: i64,
243    pub receive_only_changed_deletes: i64,
244    pub receive_only_changed_bytes: i64,
245    pub receive_only_total_items: i64,
246    pub in_sync_files: i64,
247    pub in_sync_bytes: i64,
248    pub state: String,
249    pub state_changed: chrono::DateTime<chrono::Utc>,
250    pub error: String,
251    pub sequence: i64,
252    pub remote_sequence: HashMap<String, i64>,
253    pub ignore_patterns: bool,
254    pub watch_error: String,
255}
256
257#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
258#[serde(rename_all = "PascalCase")]
259pub struct ListenAddressChanged {
260    pub fragment: String,
261    pub raw_query: String,
262    pub scheme: String,
263    pub path: String,
264    pub user: Option<String>,
265    pub force_query: bool,
266    pub host: String,
267    pub opaque: String,
268}
269
270#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
271#[serde(rename_all = "camelCase")]
272pub struct FileDownloadProgress {
273    pub total: u64,
274    pub pulling: u64,
275    pub copied_from_original: u64,
276    pub reused: u64,
277    pub copied_from_elsewhere: u64,
278    pub pulled: u64,
279    pub bytes_total: u64,
280    pub bytes_done: u64,
281}
282
283/// Information provided by the API if there is a new pending device
284/// in a [`PendingDeviceChanged`](https://docs.syncthing.net/events/pendingdeviceschanged.html)
285/// event.
286#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
287pub struct AddedPendingDeviceChanged {
288    pub address: std::net::SocketAddr,
289    #[serde(rename = "deviceID")]
290    pub device_id: String,
291    pub name: String,
292}
293
294/// Information provided by the API if there is a pending device removed
295/// in a [`PendingDeviceChanged`](https://docs.syncthing.net/events/pendingdeviceschanged.html)
296/// event.
297#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone)]
298pub struct RemovedPendingDeviceChanged {
299    #[serde(rename = "deviceID")]
300    pub device_id: String,
301}
302
303/// Information provided by the API if there is a new pending folder
304/// in a [`PendingFoldersChanged`](https://docs.syncthing.net/events/pendingfolderschanged.html)
305/// event.
306#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq, Eq)]
307#[serde(rename_all = "camelCase")]
308pub struct AddedPendingFolderChanged {
309    #[serde(rename = "deviceID")]
310    pub device_id: String,
311    #[serde(rename = "folderID")]
312    pub folder_id: String,
313    pub folder_label: String,
314    pub receive_encrypted: bool,
315    pub remote_encrypted: bool,
316}
317
318/// Information provided by the API if there is a pending folder removed
319/// in a [`PendingFoldersChanged`](https://docs.syncthing.net/events/pendingfolderschanged.html)
320/// event.
321#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Eq, Clone)]
322pub struct RemovedPendingFolderChanged {
323    /// A removed entry without `device_id`, means that the folder is
324    /// no longer pending on any device.
325    #[serde(rename = "deviceID")]
326    pub device_id: Option<String>,
327    #[serde(rename = "folderID")]
328    pub folder_id: String,
329}
330
331impl From<AddedPendingDeviceChanged> for NewDeviceConfiguration {
332    fn from(value: AddedPendingDeviceChanged) -> Self {
333        Self::new(value.device_id).name(value.name)
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use std::net::{IpAddr, Ipv4Addr};
340
341    use super::*;
342
343    #[test]
344    fn test_new_device() {
345        let added = AddedPendingDeviceChanged {
346            address: std::net::SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8384),
347            device_id: "foo".to_string(),
348            name: "bar".to_string(),
349        };
350
351        let new: NewDeviceConfiguration = added.into();
352
353        assert_eq!(new.get_device_id(), "foo");
354        assert_eq!(new.get_name(), &Some("bar".to_string()));
355    }
356}