Skip to main content

cooklang_sync_client/
models.rs

1use crate::schema::file_records;
2use diesel::prelude::*;
3use serde::{Deserialize, Serialize};
4use time::OffsetDateTime;
5
6use diesel::deserialize::FromSqlRow;
7use diesel::expression::AsExpression;
8use diesel::sql_types::Integer;
9
10#[repr(i32)]
11#[derive(Debug, Clone, Copy, AsExpression, FromSqlRow, PartialEq, Deserialize, Serialize)]
12#[diesel(sql_type = Integer)]
13pub enum FileFormat {
14    Binary = 1,
15    Text = 2,
16}
17
18#[derive(Debug)]
19pub enum IndexerUpdateEvent {
20    Updated,
21}
22
23#[derive(Queryable, Selectable, Identifiable, Debug, Clone)]
24#[diesel(table_name = file_records)]
25#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
26pub struct FileRecord {
27    pub id: i32,
28    pub jid: Option<i32>,
29    pub deleted: bool,
30    pub path: String,
31    pub size: i64,
32    pub modified_at: OffsetDateTime,
33    pub namespace_id: i32,
34}
35
36#[derive(Insertable, Debug, Clone)]
37#[diesel(table_name = file_records)]
38#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
39pub struct CreateForm {
40    pub jid: Option<i32>,
41    pub path: String,
42    pub deleted: bool,
43    pub size: i64,
44    pub modified_at: OffsetDateTime,
45    pub namespace_id: i32,
46}
47
48#[derive(Insertable, Debug, Clone)]
49#[diesel(table_name = file_records)]
50#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
51pub struct DeleteForm {
52    pub path: String,
53    pub jid: Option<i32>,
54    pub size: i64,
55    pub modified_at: OffsetDateTime,
56    pub deleted: bool,
57    pub namespace_id: i32,
58}
59
60#[derive(AsChangeset, Debug, Clone)]
61#[diesel(table_name = file_records)]
62#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
63pub struct FileRecordUpdateForm {
64    pub size: i64,
65    pub modified_at: OffsetDateTime,
66}
67
68#[derive(AsChangeset, Debug, Clone)]
69#[diesel(table_name = file_records)]
70#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
71pub struct FileRecordNonDeletedFilterForm {
72    pub deleted: bool,
73}
74
75#[derive(AsChangeset, Debug, Clone)]
76#[diesel(table_name = file_records)]
77#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
78pub struct FileRecordDeleteForm {
79    pub id: i32,
80    pub deleted: bool,
81    pub namespace_id: i32,
82}
83
84impl PartialEq<CreateForm> for FileRecord {
85    fn eq(&self, other: &CreateForm) -> bool {
86        self.path == other.path && self.size == other.size && self.modified_at == other.modified_at
87    }
88}
89
90/// Sync status enum for communicating sync state to external callers
91#[derive(Debug, Clone)]
92#[cfg_attr(feature = "ffi", derive(uniffi::Enum))]
93pub enum SyncStatus {
94    /// No sync activity is currently happening
95    Idle,
96    /// General syncing state (used when starting sync)
97    Syncing,
98    /// Indexing local files to detect changes
99    Indexing,
100    /// Downloading files from remote server
101    Downloading,
102    /// Uploading files to remote server
103    Uploading,
104    /// An error occurred during sync
105    Error { message: String },
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use time::OffsetDateTime;
112
113    #[test]
114    fn test_file_format_values() {
115        assert_eq!(FileFormat::Binary as i32, 1);
116        assert_eq!(FileFormat::Text as i32, 2);
117    }
118
119    #[test]
120    fn test_file_format_equality() {
121        assert_eq!(FileFormat::Binary, FileFormat::Binary);
122        assert_eq!(FileFormat::Text, FileFormat::Text);
123        assert_ne!(FileFormat::Binary, FileFormat::Text);
124    }
125
126    #[test]
127    fn test_file_record_create_form_equality() {
128        let now = OffsetDateTime::now_utc();
129
130        let record = FileRecord {
131            id: 1,
132            jid: Some(100),
133            deleted: false,
134            path: "test/path.txt".to_string(),
135            size: 1024,
136            modified_at: now,
137            namespace_id: 1,
138        };
139
140        let form = CreateForm {
141            jid: Some(100),
142            path: "test/path.txt".to_string(),
143            deleted: false,
144            size: 1024,
145            modified_at: now,
146            namespace_id: 1,
147        };
148
149        // Should be equal based on path, size, and modified_at
150        assert_eq!(record, form);
151    }
152
153    #[test]
154    fn test_file_record_create_form_inequality_different_path() {
155        let now = OffsetDateTime::now_utc();
156
157        let record = FileRecord {
158            id: 1,
159            jid: Some(100),
160            deleted: false,
161            path: "test/path1.txt".to_string(),
162            size: 1024,
163            modified_at: now,
164            namespace_id: 1,
165        };
166
167        let form = CreateForm {
168            jid: Some(100),
169            path: "test/path2.txt".to_string(),
170            deleted: false,
171            size: 1024,
172            modified_at: now,
173            namespace_id: 1,
174        };
175
176        // Should not be equal due to different paths
177        assert_ne!(record, form);
178    }
179
180    #[test]
181    fn test_file_record_create_form_inequality_different_size() {
182        let now = OffsetDateTime::now_utc();
183
184        let record = FileRecord {
185            id: 1,
186            jid: Some(100),
187            deleted: false,
188            path: "test/path.txt".to_string(),
189            size: 1024,
190            modified_at: now,
191            namespace_id: 1,
192        };
193
194        let form = CreateForm {
195            jid: Some(100),
196            path: "test/path.txt".to_string(),
197            deleted: false,
198            size: 2048,
199            modified_at: now,
200            namespace_id: 1,
201        };
202
203        // Should not be equal due to different sizes
204        assert_ne!(record, form);
205    }
206
207    #[test]
208    fn test_create_form_construction() {
209        let now = OffsetDateTime::now_utc();
210
211        let form = CreateForm {
212            jid: Some(42),
213            path: "recipes/test.cook".to_string(),
214            deleted: false,
215            size: 512,
216            modified_at: now,
217            namespace_id: 5,
218        };
219
220        assert_eq!(form.jid, Some(42));
221        assert_eq!(form.path, "recipes/test.cook");
222        assert_eq!(form.deleted, false);
223        assert_eq!(form.size, 512);
224        assert_eq!(form.namespace_id, 5);
225    }
226
227    #[test]
228    fn test_delete_form_construction() {
229        let now = OffsetDateTime::now_utc();
230
231        let form = DeleteForm {
232            path: "recipes/deleted.cook".to_string(),
233            jid: Some(99),
234            size: 0,
235            modified_at: now,
236            deleted: true,
237            namespace_id: 1,
238        };
239
240        assert_eq!(form.path, "recipes/deleted.cook");
241        assert_eq!(form.deleted, true);
242        assert_eq!(form.size, 0);
243    }
244
245    #[test]
246    fn test_file_record_update_form() {
247        let now = OffsetDateTime::now_utc();
248
249        let update_form = FileRecordUpdateForm {
250            size: 2048,
251            modified_at: now,
252        };
253
254        assert_eq!(update_form.size, 2048);
255        assert_eq!(update_form.modified_at, now);
256    }
257
258    #[test]
259    fn test_sync_status_variants() {
260        let idle = SyncStatus::Idle;
261        let syncing = SyncStatus::Syncing;
262        let indexing = SyncStatus::Indexing;
263        let downloading = SyncStatus::Downloading;
264        let uploading = SyncStatus::Uploading;
265        let error = SyncStatus::Error {
266            message: "Test error".to_string(),
267        };
268
269        // Just verify they can be constructed
270        assert!(matches!(idle, SyncStatus::Idle));
271        assert!(matches!(syncing, SyncStatus::Syncing));
272        assert!(matches!(indexing, SyncStatus::Indexing));
273        assert!(matches!(downloading, SyncStatus::Downloading));
274        assert!(matches!(uploading, SyncStatus::Uploading));
275        assert!(matches!(error, SyncStatus::Error { .. }));
276    }
277}