1use mxr_core::id::*;
2use mxr_core::types::*;
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct IpcMessage {
7 pub id: u64,
8 pub payload: IpcPayload,
9}
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12#[serde(tag = "type")]
13#[allow(clippy::large_enum_variant)]
14pub enum IpcPayload {
15 Request(Request),
16 Response(Response),
17 Event(DaemonEvent),
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(tag = "cmd")]
22pub enum Request {
23 ListEnvelopes {
24 label_id: Option<LabelId>,
25 account_id: Option<AccountId>,
26 limit: u32,
27 offset: u32,
28 },
29 ListEnvelopesByIds {
30 message_ids: Vec<MessageId>,
31 },
32 GetEnvelope {
33 message_id: MessageId,
34 },
35 GetBody {
36 message_id: MessageId,
37 },
38 DownloadAttachment {
39 message_id: MessageId,
40 attachment_id: AttachmentId,
41 },
42 OpenAttachment {
43 message_id: MessageId,
44 attachment_id: AttachmentId,
45 },
46 ListBodies {
47 message_ids: Vec<MessageId>,
48 },
49 GetThread {
50 thread_id: ThreadId,
51 },
52 ListLabels {
53 account_id: Option<AccountId>,
54 },
55 CreateLabel {
56 name: String,
57 color: Option<String>,
58 account_id: Option<AccountId>,
59 },
60 DeleteLabel {
61 name: String,
62 account_id: Option<AccountId>,
63 },
64 RenameLabel {
65 old: String,
66 new: String,
67 account_id: Option<AccountId>,
68 },
69 ListRules,
70 ListAccounts,
71 ListAccountsConfig,
72 AuthorizeAccountConfig {
73 account: AccountConfigData,
74 reauthorize: bool,
75 },
76 UpsertAccountConfig {
77 account: AccountConfigData,
78 },
79 SetDefaultAccount {
80 key: String,
81 },
82 TestAccountConfig {
83 account: AccountConfigData,
84 },
85 GetRule {
86 rule: String,
87 },
88 GetRuleForm {
89 rule: String,
90 },
91 UpsertRule {
92 rule: serde_json::Value,
93 },
94 UpsertRuleForm {
95 existing_rule: Option<String>,
96 name: String,
97 condition: String,
98 action: String,
99 priority: i32,
100 enabled: bool,
101 },
102 DeleteRule {
103 rule: String,
104 },
105 DryRunRules {
106 rule: Option<String>,
107 all: bool,
108 after: Option<String>,
109 },
110 ListEvents {
111 limit: u32,
112 level: Option<String>,
113 category: Option<String>,
114 },
115 GetLogs {
116 limit: u32,
117 level: Option<String>,
118 },
119 GetDoctorReport,
120 GenerateBugReport {
121 verbose: bool,
122 full_logs: bool,
123 since: Option<String>,
124 },
125 ListRuleHistory {
126 rule: Option<String>,
127 limit: u32,
128 },
129 Search {
130 query: String,
131 limit: u32,
132 mode: Option<SearchMode>,
133 explain: bool,
134 },
135 SyncNow {
136 account_id: Option<AccountId>,
137 },
138 GetSyncStatus {
139 account_id: AccountId,
140 },
141 SetFlags {
142 message_id: MessageId,
143 flags: MessageFlags,
144 },
145 Count {
146 query: String,
147 mode: Option<SearchMode>,
148 },
149 GetHeaders {
150 message_id: MessageId,
151 },
152 ListSavedSearches,
153 ListSubscriptions {
154 limit: u32,
155 },
156 GetSemanticStatus,
157 EnableSemantic {
158 enabled: bool,
159 },
160 InstallSemanticProfile {
161 profile: SemanticProfile,
162 },
163 UseSemanticProfile {
164 profile: SemanticProfile,
165 },
166 ReindexSemantic,
167 CreateSavedSearch {
168 name: String,
169 query: String,
170 search_mode: SearchMode,
171 },
172 DeleteSavedSearch {
173 name: String,
174 },
175 RunSavedSearch {
176 name: String,
177 limit: u32,
178 },
179 Mutation(MutationCommand),
181 Unsubscribe {
182 message_id: MessageId,
183 },
184 Snooze {
185 message_id: MessageId,
186 wake_at: chrono::DateTime<chrono::Utc>,
187 },
188 Unsnooze {
189 message_id: MessageId,
190 },
191 ListSnoozed,
192 PrepareReply {
194 message_id: MessageId,
195 reply_all: bool,
196 },
197 PrepareForward {
198 message_id: MessageId,
199 },
200 SendDraft {
201 draft: Draft,
202 },
203 SaveDraftToServer {
205 draft: Draft,
206 },
207 ListDrafts,
208
209 ExportThread {
211 thread_id: ThreadId,
212 format: ExportFormat,
213 },
214 ExportSearch {
215 query: String,
216 format: ExportFormat,
217 },
218
219 GetStatus,
220 Ping,
221 Shutdown,
222}
223
224#[derive(Debug, Clone, Serialize, Deserialize)]
226#[serde(tag = "mutation")]
227pub enum MutationCommand {
228 Archive {
229 message_ids: Vec<MessageId>,
230 },
231 Trash {
232 message_ids: Vec<MessageId>,
233 },
234 Spam {
235 message_ids: Vec<MessageId>,
236 },
237 Star {
238 message_ids: Vec<MessageId>,
239 starred: bool,
240 },
241 SetRead {
242 message_ids: Vec<MessageId>,
243 read: bool,
244 },
245 ModifyLabels {
246 message_ids: Vec<MessageId>,
247 add: Vec<String>,
248 remove: Vec<String>,
249 },
250 Move {
251 message_ids: Vec<MessageId>,
252 target_label: String,
253 },
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct ReplyContext {
259 pub in_reply_to: String,
260 pub references: Vec<String>,
261 pub reply_to: String,
262 pub cc: String,
263 pub subject: String,
264 pub from: String,
265 pub thread_context: String,
266}
267
268#[derive(Debug, Clone, Serialize, Deserialize)]
270pub struct ForwardContext {
271 pub subject: String,
272 pub from: String,
273 pub forwarded_content: String,
274}
275
276#[derive(Debug, Clone, Serialize, Deserialize)]
277#[serde(tag = "status")]
278#[allow(clippy::large_enum_variant)]
279pub enum Response {
280 Ok { data: ResponseData },
281 Error { message: String },
282}
283
284#[derive(Debug, Clone, Serialize, Deserialize)]
285#[serde(tag = "kind")]
286#[allow(clippy::large_enum_variant)]
287pub enum ResponseData {
288 Envelopes {
289 envelopes: Vec<Envelope>,
290 },
291 Envelope {
292 envelope: Envelope,
293 },
294 Body {
295 body: MessageBody,
296 },
297 AttachmentFile {
298 file: AttachmentFile,
299 },
300 Bodies {
301 bodies: Vec<MessageBody>,
302 },
303 Thread {
304 thread: Thread,
305 messages: Vec<Envelope>,
306 },
307 Labels {
308 labels: Vec<Label>,
309 },
310 Label {
311 label: Label,
312 },
313 Rules {
314 rules: Vec<serde_json::Value>,
315 },
316 RuleData {
317 rule: serde_json::Value,
318 },
319 Accounts {
320 accounts: Vec<AccountSummaryData>,
321 },
322 AccountsConfig {
323 accounts: Vec<AccountConfigData>,
324 },
325 AccountOperation {
326 result: AccountOperationResult,
327 },
328 RuleFormData {
329 form: RuleFormData,
330 },
331 RuleDryRun {
332 results: Vec<serde_json::Value>,
333 },
334 EventLogEntries {
335 entries: Vec<EventLogEntry>,
336 },
337 LogLines {
338 lines: Vec<String>,
339 },
340 DoctorReport {
341 report: DoctorReport,
342 },
343 BugReport {
344 content: String,
345 },
346 RuleHistory {
347 entries: Vec<serde_json::Value>,
348 },
349 SearchResults {
350 results: Vec<SearchResultItem>,
351 },
352 SyncStatus {
353 sync: AccountSyncStatus,
354 },
355 Count {
356 count: u32,
357 },
358 Headers {
359 headers: Vec<(String, String)>,
360 },
361 SavedSearches {
362 searches: Vec<mxr_core::types::SavedSearch>,
363 },
364 Subscriptions {
365 subscriptions: Vec<mxr_core::types::SubscriptionSummary>,
366 },
367 SemanticStatus {
368 snapshot: SemanticStatusSnapshot,
369 },
370 SavedSearchData {
371 search: mxr_core::types::SavedSearch,
372 },
373 Status {
374 uptime_secs: u64,
375 accounts: Vec<String>,
376 total_messages: u32,
377 daemon_pid: Option<u32>,
378 sync_statuses: Vec<AccountSyncStatus>,
379 },
380 ReplyContext {
381 context: ReplyContext,
382 },
383 ForwardContext {
384 context: ForwardContext,
385 },
386 Drafts {
387 drafts: Vec<Draft>,
388 },
389 SnoozedMessages {
390 snoozed: Vec<Snoozed>,
391 },
392 ExportResult {
393 content: String,
394 },
395 Pong,
396 Ack,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
400pub struct SearchResultItem {
401 pub message_id: MessageId,
402 pub account_id: AccountId,
403 pub thread_id: ThreadId,
404 pub score: f32,
405 pub mode: SearchMode,
406}
407
408#[derive(Debug, Clone, Serialize, Deserialize)]
409pub struct AttachmentFile {
410 pub attachment_id: AttachmentId,
411 pub filename: String,
412 pub path: String,
413}
414
415#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct EventLogEntry {
417 pub timestamp: i64,
418 pub level: String,
419 pub category: String,
420 pub account_id: Option<AccountId>,
421 pub message_id: Option<String>,
422 pub rule_id: Option<String>,
423 pub summary: String,
424 pub details: Option<String>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
428pub struct AccountSyncStatus {
429 pub account_id: AccountId,
430 pub account_name: String,
431 pub last_attempt_at: Option<String>,
432 pub last_success_at: Option<String>,
433 pub last_error: Option<String>,
434 pub failure_class: Option<String>,
435 pub consecutive_failures: u32,
436 pub backoff_until: Option<String>,
437 pub sync_in_progress: bool,
438 pub current_cursor_summary: Option<String>,
439 pub last_synced_count: u32,
440 pub healthy: bool,
441}
442
443#[derive(Debug, Clone, Serialize, Deserialize)]
444pub struct DoctorReport {
445 pub healthy: bool,
446 pub data_dir_exists: bool,
447 pub database_exists: bool,
448 pub index_exists: bool,
449 pub socket_exists: bool,
450 pub socket_reachable: bool,
451 pub stale_socket: bool,
452 pub daemon_running: bool,
453 pub daemon_pid: Option<u32>,
454 pub index_lock_held: bool,
455 pub index_lock_error: Option<String>,
456 pub database_path: String,
457 pub database_size_bytes: u64,
458 pub index_path: String,
459 pub index_size_bytes: u64,
460 pub log_path: String,
461 pub log_size_bytes: u64,
462 pub sync_statuses: Vec<AccountSyncStatus>,
463 pub recent_sync_events: Vec<EventLogEntry>,
464 pub recent_error_logs: Vec<String>,
465 pub recommended_next_steps: Vec<String>,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
469pub struct RuleFormData {
470 pub id: Option<String>,
471 pub name: String,
472 pub condition: String,
473 pub action: String,
474 pub priority: i32,
475 pub enabled: bool,
476}
477
478#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct AccountConfigData {
480 pub key: String,
481 pub name: String,
482 pub email: String,
483 pub sync: Option<AccountSyncConfigData>,
484 pub send: Option<AccountSendConfigData>,
485 pub is_default: bool,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
489#[serde(rename_all = "snake_case")]
490pub enum AccountSourceData {
491 Runtime,
492 Config,
493 Both,
494}
495
496#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
497#[serde(rename_all = "snake_case")]
498pub enum AccountEditModeData {
499 Full,
500 RuntimeOnly,
501}
502
503#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct AccountSummaryData {
505 pub account_id: AccountId,
506 pub key: Option<String>,
507 pub name: String,
508 pub email: String,
509 pub provider_kind: String,
510 pub sync_kind: Option<String>,
511 pub send_kind: Option<String>,
512 pub enabled: bool,
513 pub is_default: bool,
514 pub source: AccountSourceData,
515 pub editable: AccountEditModeData,
516 pub sync: Option<AccountSyncConfigData>,
517 pub send: Option<AccountSendConfigData>,
518}
519
520#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
521#[serde(rename_all = "snake_case")]
522pub enum GmailCredentialSourceData {
523 #[default]
524 Bundled,
525 Custom,
526}
527
528#[derive(Debug, Clone, Serialize, Deserialize)]
529#[serde(tag = "type", rename_all = "snake_case")]
530pub enum AccountSyncConfigData {
531 Gmail {
532 #[serde(default)]
533 credential_source: GmailCredentialSourceData,
534 client_id: String,
535 client_secret: Option<String>,
536 token_ref: String,
537 },
538 Imap {
539 host: String,
540 port: u16,
541 username: String,
542 password_ref: String,
543 password: Option<String>,
544 use_tls: bool,
545 },
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize)]
549pub struct AccountOperationStep {
550 pub ok: bool,
551 pub detail: String,
552}
553
554#[derive(Debug, Clone, Serialize, Deserialize)]
555pub struct AccountOperationResult {
556 pub ok: bool,
557 pub summary: String,
558 pub save: Option<AccountOperationStep>,
559 pub auth: Option<AccountOperationStep>,
560 pub sync: Option<AccountOperationStep>,
561 pub send: Option<AccountOperationStep>,
562}
563
564#[derive(Debug, Clone, Serialize, Deserialize)]
565#[serde(tag = "type", rename_all = "snake_case")]
566pub enum AccountSendConfigData {
567 Gmail,
568 Smtp {
569 host: String,
570 port: u16,
571 username: String,
572 password_ref: String,
573 password: Option<String>,
574 use_tls: bool,
575 },
576}
577
578#[derive(Debug, Clone, Serialize, Deserialize)]
579#[serde(tag = "event")]
580pub enum DaemonEvent {
581 SyncCompleted {
582 account_id: AccountId,
583 messages_synced: u32,
584 },
585 SyncError {
586 account_id: AccountId,
587 error: String,
588 },
589 NewMessages {
590 envelopes: Vec<Envelope>,
591 },
592 MessageUnsnoozed {
593 message_id: MessageId,
594 },
595 LabelCountsUpdated {
596 counts: Vec<LabelCount>,
597 },
598}
599
600#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct LabelCount {
602 pub label_id: LabelId,
603 pub unread_count: u32,
604 pub total_count: u32,
605}