cooklang_sync_client/
models.rs

1use crate::schema::file_records;
2use diesel::prelude::*;
3use serde::{Deserialize, Serialize};
4use time::OffsetDateTime;
5
6use diesel::{AsExpression, FromSqlRow};
7
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, uniffi::Enum)]
92pub enum SyncStatus {
93    /// No sync activity is currently happening
94    Idle,
95    /// General syncing state (used when starting sync)
96    Syncing,
97    /// Indexing local files to detect changes
98    Indexing,
99    /// Downloading files from remote server
100    Downloading,
101    /// Uploading files to remote server
102    Uploading,
103    /// An error occurred during sync
104    Error { message: String },
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110    use time::OffsetDateTime;
111
112    #[test]
113    fn test_file_format_values() {
114        assert_eq!(FileFormat::Binary as i32, 1);
115        assert_eq!(FileFormat::Text as i32, 2);
116    }
117
118    #[test]
119    fn test_file_format_equality() {
120        assert_eq!(FileFormat::Binary, FileFormat::Binary);
121        assert_eq!(FileFormat::Text, FileFormat::Text);
122        assert_ne!(FileFormat::Binary, FileFormat::Text);
123    }
124
125    #[test]
126    fn test_file_record_create_form_equality() {
127        let now = OffsetDateTime::now_utc();
128
129        let record = FileRecord {
130            id: 1,
131            jid: Some(100),
132            deleted: false,
133            path: "test/path.txt".to_string(),
134            size: 1024,
135            modified_at: now,
136            namespace_id: 1,
137        };
138
139        let form = CreateForm {
140            jid: Some(100),
141            path: "test/path.txt".to_string(),
142            deleted: false,
143            size: 1024,
144            modified_at: now,
145            namespace_id: 1,
146        };
147
148        // Should be equal based on path, size, and modified_at
149        assert_eq!(record, form);
150    }
151
152    #[test]
153    fn test_file_record_create_form_inequality_different_path() {
154        let now = OffsetDateTime::now_utc();
155
156        let record = FileRecord {
157            id: 1,
158            jid: Some(100),
159            deleted: false,
160            path: "test/path1.txt".to_string(),
161            size: 1024,
162            modified_at: now,
163            namespace_id: 1,
164        };
165
166        let form = CreateForm {
167            jid: Some(100),
168            path: "test/path2.txt".to_string(),
169            deleted: false,
170            size: 1024,
171            modified_at: now,
172            namespace_id: 1,
173        };
174
175        // Should not be equal due to different paths
176        assert_ne!(record, form);
177    }
178
179    #[test]
180    fn test_file_record_create_form_inequality_different_size() {
181        let now = OffsetDateTime::now_utc();
182
183        let record = FileRecord {
184            id: 1,
185            jid: Some(100),
186            deleted: false,
187            path: "test/path.txt".to_string(),
188            size: 1024,
189            modified_at: now,
190            namespace_id: 1,
191        };
192
193        let form = CreateForm {
194            jid: Some(100),
195            path: "test/path.txt".to_string(),
196            deleted: false,
197            size: 2048,
198            modified_at: now,
199            namespace_id: 1,
200        };
201
202        // Should not be equal due to different sizes
203        assert_ne!(record, form);
204    }
205
206    #[test]
207    fn test_create_form_construction() {
208        let now = OffsetDateTime::now_utc();
209
210        let form = CreateForm {
211            jid: Some(42),
212            path: "recipes/test.cook".to_string(),
213            deleted: false,
214            size: 512,
215            modified_at: now,
216            namespace_id: 5,
217        };
218
219        assert_eq!(form.jid, Some(42));
220        assert_eq!(form.path, "recipes/test.cook");
221        assert_eq!(form.deleted, false);
222        assert_eq!(form.size, 512);
223        assert_eq!(form.namespace_id, 5);
224    }
225
226    #[test]
227    fn test_delete_form_construction() {
228        let now = OffsetDateTime::now_utc();
229
230        let form = DeleteForm {
231            path: "recipes/deleted.cook".to_string(),
232            jid: Some(99),
233            size: 0,
234            modified_at: now,
235            deleted: true,
236            namespace_id: 1,
237        };
238
239        assert_eq!(form.path, "recipes/deleted.cook");
240        assert_eq!(form.deleted, true);
241        assert_eq!(form.size, 0);
242    }
243
244    #[test]
245    fn test_file_record_update_form() {
246        let now = OffsetDateTime::now_utc();
247
248        let update_form = FileRecordUpdateForm {
249            size: 2048,
250            modified_at: now,
251        };
252
253        assert_eq!(update_form.size, 2048);
254        assert_eq!(update_form.modified_at, now);
255    }
256
257    #[test]
258    fn test_sync_status_variants() {
259        let idle = SyncStatus::Idle;
260        let syncing = SyncStatus::Syncing;
261        let indexing = SyncStatus::Indexing;
262        let downloading = SyncStatus::Downloading;
263        let uploading = SyncStatus::Uploading;
264        let error = SyncStatus::Error {
265            message: "Test error".to_string(),
266        };
267
268        // Just verify they can be constructed
269        assert!(matches!(idle, SyncStatus::Idle));
270        assert!(matches!(syncing, SyncStatus::Syncing));
271        assert!(matches!(indexing, SyncStatus::Indexing));
272        assert!(matches!(downloading, SyncStatus::Downloading));
273        assert!(matches!(uploading, SyncStatus::Uploading));
274        assert!(matches!(error, SyncStatus::Error { .. }));
275    }
276}