From bd6efb153cb80feee7f2a5e0ad69eb5d1809c744 Mon Sep 17 00:00:00 2001 From: rustmailer Date: Wed, 8 Oct 2025 12:05:31 +0800 Subject: [PATCH] refactor: merge uid and mid into a single id field in EmailAddedToFolder and EmailEnvelopeV3 --- protos/rustmailer.proto | 22 +-- src/modules/cache/imap/sync/flow.rs | 3 +- src/modules/cache/imap/thread.rs | 12 +- src/modules/cache/imap/v2.rs | 24 +-- src/modules/cache/mod.rs | 1 + src/modules/cache/model.rs | 160 ++++++++++++++++++ .../cache/vendor/gmail/sync/envelope.rs | 49 +++++- .../cache/vendor/gmail/sync/history.rs | 10 +- src/modules/grpc/server.rs | 8 +- src/modules/grpc/service/message/from.rs | 32 ++-- src/modules/hook/events/mod.rs | 3 +- src/modules/hook/events/payload.rs | 13 +- src/modules/imap/session.rs | 30 ++-- src/modules/imap/stats.rs | 8 +- src/modules/message/list.rs | 30 ++-- src/modules/message/search/payload.rs | 38 +++-- src/modules/rest/api/message.rs | 12 +- 17 files changed, 336 insertions(+), 119 deletions(-) create mode 100644 src/modules/cache/model.rs diff --git a/protos/rustmailer.proto b/protos/rustmailer.proto index de0dd0e..bd74c4e 100644 --- a/protos/rustmailer.proto +++ b/protos/rustmailer.proto @@ -715,8 +715,10 @@ message EmailEnvelope { uint64 mailbox_id = 2; // The name of the mailbox this email is in. string mailbox_name = 3; - // The unique identifier of the message within its mailbox. - uint32 uid = 4; + // The unique ID of the message, either IMAP UID or Gmail API MID. + // - For IMAP accounts, this is the UID converted to a string. + // - For Gmail API accounts, this is the message ID returned by the API. + string id = 4; // Optional: The internal date and time of the message on the server (Unix timestamp). optional int64 internal_date = 5; // The size of the message in bytes. @@ -724,7 +726,7 @@ message EmailEnvelope { // A list of flags currently set on the message (e.g., \Seen, \Answered). repeated EnvelopeFlag flags = 7; // A hash of the message flags for quick comparison. - uint64 flags_hash = 8; + optional uint64 flags_hash = 8; // A list of blind carbon copy (BCC) recipients. repeated Addr bcc = 9; // A list of carbon copy (CC) recipients. @@ -762,17 +764,11 @@ message EmailEnvelope { // The identifier of the thread this email belongs to. // This is computed based on `in_reply_to` / `references` / `message_id`. uint64 thread_id = 26; - // The `mid` field is reserved for potential integration with other backend models. - // For instance, it can be used to store the email index or ID from external services like the Gmail API. - // This ID could be used for reference or identification purposes in scenarios where an external service - // provides an identifier for the email in question. - // This field is optional, meaning that it may be `None` if no external service identifier is available. - optional string mid = 27; // A list of labels applied to the message. - // Each element is a string representing a Gmail label ID (e.g., "INBOX", "UNREAD"). - // This field reflects the current labels associated with the email. - // Note: This field is populated only for Gmail API accounts. For other account types, it will be empty. - repeated string label_ids = 28; + // Each element is a string representing a Gmail label name (e.g., "INBOX", "UNREAD"). + // This field reflects the current labels associated with the email. + // **Note:** This field is populated only for Gmail API accounts. For other account types, it will be empty. + repeated string labels = 27; } // FetchMessageContentRequest is used to fetch specific content sections of an email message. diff --git a/src/modules/cache/imap/sync/flow.rs b/src/modules/cache/imap/sync/flow.rs index 67b23ad..b0676a4 100644 --- a/src/modules/cache/imap/sync/flow.rs +++ b/src/modules/cache/imap/sync/flow.rs @@ -829,7 +829,7 @@ async fn process_email_added_events( account_id: account.id, account_email: account.email.clone(), mailbox_name: remote.name.clone(), - uid: envelope.uid, + id: envelope.uid.to_string(), internal_date: envelope.internal_date, date: envelope.date, from: envelope.from, @@ -850,7 +850,6 @@ async fn process_email_added_events( .as_ref() .map(|atts| atts.iter().cloned().map(Attachment::from).collect()), thread_id, - mid: None, labels: vec![], }), ), diff --git a/src/modules/cache/imap/thread.rs b/src/modules/cache/imap/thread.rs index fccf482..e285269 100644 --- a/src/modules/cache/imap/thread.rs +++ b/src/modules/cache/imap/thread.rs @@ -14,6 +14,7 @@ use crate::{ account::v2::AccountV2, cache::{ imap::v2::EmailEnvelopeV3, + model::Envelope, vendor::gmail::sync::{client::GmailClient, envelope::GmailEnvelope}, }, database::{ @@ -142,7 +143,7 @@ impl EmailThread { page: u64, page_size: u64, desc: bool, - ) -> RustMailerResult> { + ) -> RustMailerResult> { let threads = paginate_secondary_scan_impl::( DB_MANAGER.envelope_db(), Some(page), @@ -172,7 +173,7 @@ impl EmailThread { current_page: threads.page, page_size: threads.page_size, total_items: threads.total_items, - items: envelopes, + items: envelopes.into_iter().map(Envelope::from).collect(), total_pages: threads.total_pages, }) } @@ -183,7 +184,7 @@ impl EmailThread { page: u64, page_size: u64, desc: bool, - ) -> RustMailerResult> { + ) -> RustMailerResult> { let threads = paginate_secondary_scan_impl::( DB_MANAGER.envelope_db(), Some(page), @@ -208,7 +209,10 @@ impl EmailThread { let results: RustMailerResult> = join_all(fetch_tasks).await.into_iter().collect(); let map = GmailClient::label_map(account.id, account.use_proxy).await?; - let envelopes = results?.into_iter().map(|e| e.into_v3(&map)).collect(); + let envelopes = results? + .into_iter() + .map(|e| e.into_envelope(&map)) + .collect(); Ok(DataPage { current_page: threads.page, page_size: threads.page_size, diff --git a/src/modules/cache/imap/v2.rs b/src/modules/cache/imap/v2.rs index 4052550..0985427 100644 --- a/src/modules/cache/imap/v2.rs +++ b/src/modules/cache/imap/v2.rs @@ -14,13 +14,16 @@ use tracing::{error, info}; use crate::{ calculate_hash, id, modules::{ - cache::imap::{ - address::AddressEntity, - envelope::{EmailEnvelope, Received}, - mailbox::EnvelopeFlag, - manager::EnvelopeFlagsManager, - minimal::MinimalEnvelope, - thread::{EmailThread, EmailThreadKey}, + cache::{ + imap::{ + address::AddressEntity, + envelope::{EmailEnvelope, Received}, + mailbox::EnvelopeFlag, + manager::EnvelopeFlagsManager, + minimal::MinimalEnvelope, + thread::{EmailThread, EmailThreadKey}, + }, + model::Envelope, }, common::Addr, database::{ @@ -281,10 +284,7 @@ impl EmailEnvelopeV3 { .await } - pub async fn get_thread( - account_id: u64, - thread_id: u64, - ) -> RustMailerResult> { + pub async fn get_thread(account_id: u64, thread_id: u64) -> RustMailerResult> { let envelopes = filter_by_secondary_key_impl::( DB_MANAGER.envelope_db(), EmailEnvelopeV3Key::thread_id, @@ -308,7 +308,7 @@ impl EmailEnvelopeV3 { } }); - Ok(result) + Ok(result.into_iter().map(Envelope::from).collect()) } pub async fn get(envelope_id: u64) -> RustMailerResult> { diff --git a/src/modules/cache/mod.rs b/src/modules/cache/mod.rs index 9c0bd8c..1e13dba 100644 --- a/src/modules/cache/mod.rs +++ b/src/modules/cache/mod.rs @@ -5,3 +5,4 @@ pub mod disk; pub mod imap; pub mod vendor; +pub mod model; \ No newline at end of file diff --git a/src/modules/cache/model.rs b/src/modules/cache/model.rs new file mode 100644 index 0000000..ec74f26 --- /dev/null +++ b/src/modules/cache/model.rs @@ -0,0 +1,160 @@ +// Copyright © 2025 rustmailer.com +// Licensed under RustMailer License Agreement v1.0 +// Unauthorized copying, modification, or distribution is prohibited. +use poem_openapi::Object; +use serde::{Deserialize, Serialize}; + +use crate::{calculate_hash, id, modules::{ + cache::imap::{envelope::Received, mailbox::EnvelopeFlag, v2::EmailEnvelopeV3}, + common::Addr, + imap::section::{EmailBodyPart, ImapAttachment}, +}}; + +#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize, Object)] +pub struct Envelope { + /// The unique ID of the message, either IMAP UID or Gmail API MID. + /// + /// - For IMAP accounts, this is the UID converted to a string. + /// - For Gmail API accounts, this is the message ID returned by the API. + pub id: String, + /// The ID of the account owning the email. + pub account_id: u64, + /// The unique identifier of the mailbox where the email is stored (e.g., `MailBox::id`). + /// Used for indexing to avoid updating indexes when mailboxes are renamed. + pub mailbox_id: u64, + /// The decoded, human-readable name of the mailbox (e.g., "INBOX", "Sent"). + pub mailbox_name: String, + /// The date and time the email was received by the server, as a Unix timestamp in milliseconds. + /// If `None`, the internal date is unavailable. + pub internal_date: Option, + /// The size of the email in bytes. + pub size: u32, + /// The flags associated with the email (e.g., `\Seen`, `\Answered`, `\Flagged`). + /// Represented as a list of `EnvelopeFlag` for standard or custom flags. + /// + /// **Note:** Available only for IMAP accounts. + pub flags: Option>, + /// A hash of the email's flags for efficient comparison or indexing. + /// + /// **Note:** Available only for IMAP accounts. + pub flags_hash: Option, + /// The blind carbon copy (BCC) recipient(s) of the email, if any. + pub bcc: Option>, + /// The carbon copy (CC) recipient(s) of the email, if any. + pub cc: Option>, + /// The date the email was sent, as a Unix timestamp in milliseconds, if available. + pub date: Option, + /// The sender's address, including name and email, if available. + pub from: Option, + /// The message ID of the email to which this email is a reply, if applicable. + pub in_reply_to: Option, + /// The actual sender's address, if different from the `from` field. + pub sender: Option, + /// The return address for undeliverable emails, if specified. + pub return_address: Option, + /// The unique message ID of the email, typically used for threading. + pub message_id: Option, + /// The subject of the email, if available. + pub subject: Option, + /// The name of the thread this email belongs to, if applicable. + pub thread_name: Option, + /// The identifier of the thread this email belongs to. + /// This is computed based on `in_reply_to` / `references` / `message_id`. + pub thread_id: u64, + /// The MIME version of the email (e.g., "1.0"), if specified. + pub mime_version: Option, + /// A list of message IDs referenced by this email, used for threading. + pub references: Option>, + /// The address(es) to which replies should be sent, if specified. + pub reply_to: Option>, + /// The primary recipient(s) of the email, if any. + pub to: Option>, + /// A list of attachments included in the email, if any. + /// + /// Each `ImapAttachment` item contains metadata including the part ID and MIME type, + /// which indicates the exact location of the attachment in the raw message structure. + /// This allows the backend to directly fetch specific attachments without retrieving + /// the entire message content. + /// + /// This is particularly useful for accounts configured with minimal sync, where full + /// message bodies are not cached locally. By including this data in the API response, + /// the client can request to download only the required attachment via a follow-up + /// API call, improving both efficiency and user experience. + /// + /// Developers do not need to understand the internal IMAP part structure — this + /// metadata provides a clean abstraction for fetching specific attachments. + /// **Note:** Available only for IMAP accounts. + pub attachments: Option>, + /// Metadata for the email's body parts (e.g., plain text, HTML), if available. + /// + /// Each `EmailBodyPart` contains detailed metadata (such as part ID, content type, + /// and charset) describing a portion of the email body. This enables precise access + /// to body content, such as plain text or HTML sections, without downloading the full + /// raw message from the server. + /// + /// This is especially helpful for lightweight clients or minimized-sync accounts that + /// do not cache full email content. The frontend can pass this metadata back to the + /// server to retrieve only the desired portion of the message (e.g., the HTML body), + /// which significantly reduces bandwidth and latency. + /// + /// By abstracting the complexity of MIME part navigation, developers can efficiently + /// retrieve specific parts of an email without handling the low-level IMAP structure. + /// **Note:** Available only for IMAP accounts. + pub body_meta: Option>, + /// Details about how the email was received, if available. + /// **Note:** Available only for IMAP accounts. + pub received: Option, + /// A list of labels applied to the message. + /// + /// Each element is a string representing a Gmail label name (e.g., "INBOX", "UNREAD"). + /// This field reflects the current labels associated with the email. + /// + /// **Note:** This field is populated only for Gmail API accounts. For other account types, it will be empty. + pub labels: Vec, +} + +impl Envelope { + pub fn compute_thread_id(&self) -> u64 { + if self.in_reply_to.is_some() && self.references.as_ref().map_or(false, |r| !r.is_empty()) { + return calculate_hash!(&self.references.as_ref().unwrap()[0]); + } + if let Some(message_id) = self.message_id.as_ref() { + return calculate_hash!(message_id); + } + id!(128) + } +} + +impl From for Envelope { + fn from(value: EmailEnvelopeV3) -> Self { + Self { + id: value.uid.to_string(), + account_id: value.account_id, + mailbox_id: value.mailbox_id, + mailbox_name: value.mailbox_name, + internal_date: value.internal_date, + size: value.size, + flags: Some(value.flags), + flags_hash: Some(value.flags_hash), + bcc: value.bcc, + cc: value.cc, + date: value.date, + from: value.from, + in_reply_to: value.in_reply_to, + sender: value.sender, + return_address: value.return_address, + message_id: value.message_id, + subject: value.subject, + thread_name: value.thread_name, + thread_id: value.thread_id, + mime_version: value.mime_version, + references: value.references, + reply_to: value.reply_to, + to: value.to, + attachments: value.attachments, + body_meta: value.body_meta, + received: value.received, + labels: value.labels, + } + } +} diff --git a/src/modules/cache/vendor/gmail/sync/envelope.rs b/src/modules/cache/vendor/gmail/sync/envelope.rs index 8977160..48b7e97 100644 --- a/src/modules/cache/vendor/gmail/sync/envelope.rs +++ b/src/modules/cache/vendor/gmail/sync/envelope.rs @@ -5,10 +5,13 @@ use crate::{ calculate_hash, id, modules::{ - cache::imap::{ - address::AddressEntity, - thread::{EmailThread, EmailThreadKey}, - v2::EmailEnvelopeV3, + cache::{ + imap::{ + address::AddressEntity, + thread::{EmailThread, EmailThreadKey}, + v2::EmailEnvelopeV3, + }, + model::Envelope, }, common::Addr, database::{ @@ -386,4 +389,42 @@ impl GmailEnvelope { labels, } } + + pub fn into_envelope(self, label_map: &AHashMap) -> Envelope { + let labels: Vec = self + .label_ids + .into_iter() + .filter_map(|id| label_map.get(&id).cloned()) + .collect(); + + Envelope { + id: self.id, + account_id: self.account_id, + mailbox_id: self.label_id, + mailbox_name: self.label_name, + internal_date: Some(self.internal_date), + size: self.size, + flags: None, + flags_hash: None, + bcc: self.bcc, + cc: self.cc, + date: self.date, + from: self.from, + in_reply_to: self.in_reply_to, + sender: self.sender, + return_address: None, + message_id: self.message_id, + subject: self.subject, + thread_name: None, + thread_id: self.thread_id, + mime_version: self.mime_version, + references: self.references, + reply_to: self.reply_to, + to: self.to, + attachments: None, + body_meta: None, + received: None, + labels, + } + } } diff --git a/src/modules/cache/vendor/gmail/sync/history.rs b/src/modules/cache/vendor/gmail/sync/history.rs index 6252152..2121fea 100644 --- a/src/modules/cache/vendor/gmail/sync/history.rs +++ b/src/modules/cache/vendor/gmail/sync/history.rs @@ -311,7 +311,7 @@ async fn dispatch_new_email_notification( let full_message = GmailClient::get_full_messages(account.id, account.use_proxy, &message.id).await?; let message_content: FullMessageContent = full_message.try_into()?; - let mut envelope = message.into_v3(&label_map); + let mut envelope = message.into_envelope(&label_map); envelope.thread_id = envelope.compute_thread_id(); EVENT_CHANNEL .queue(Event::new( @@ -323,14 +323,17 @@ async fn dispatch_new_email_notification( account_id: account.id, account_email: account.email.clone(), mailbox_name: envelope.mailbox_name.clone(), - uid: envelope.uid, + id: envelope.id, internal_date: envelope.internal_date, date: envelope.date, from: envelope.from, subject: envelope.subject, to: envelope.to, size: envelope.size, - flags: envelope.flags.into_iter().map(|f| f.to_string()).collect(), + flags: envelope + .flags + .map(|f| f.into_iter().map(|f| f.to_string()).collect()) + .unwrap_or_default(), cc: envelope.cc, bcc: envelope.bcc, in_reply_to: envelope.in_reply_to, @@ -344,7 +347,6 @@ async fn dispatch_new_email_notification( .as_ref() .map(|atts| atts.iter().cloned().map(Attachment::from).collect()), thread_id: envelope.thread_id, - mid: envelope.mid, labels: envelope.labels, }), ), diff --git a/src/modules/grpc/server.rs b/src/modules/grpc/server.rs index 86ce76c..8047137 100644 --- a/src/modules/grpc/server.rs +++ b/src/modules/grpc/server.rs @@ -13,6 +13,8 @@ use crate::modules::common::log::Tracing; use crate::modules::common::timeout::Timeout; use crate::modules::common::tls::rustls_config; use crate::modules::error::code::ErrorCode; +use crate::modules::grpc::service::hook::RustMailerEventHooksService; +use crate::modules::grpc::service::rustmailer_grpc::EventHooksServiceServer; use crate::modules::settings::cli::CompressionAlgorithm; use crate::modules::{ error::RustMailerResult, @@ -72,12 +74,16 @@ pub async fn start_grpc_server() -> RustMailerResult<()> { .add_file_descriptor_set(FILE_DESCRIPTOR_SET) .build(), ); - route = add_service!( route, AccountServiceServer, RustMailerAccountService ); + route = add_service!( + route, + EventHooksServiceServer, + RustMailerEventHooksService + ); route = add_service!( route, AutoConfigServiceServer, diff --git a/src/modules/grpc/service/message/from.rs b/src/modules/grpc/service/message/from.rs index c1e92d7..d0b93e4 100644 --- a/src/modules/grpc/service/message/from.rs +++ b/src/modules/grpc/service/message/from.rs @@ -3,10 +3,12 @@ // Unauthorized copying, modification, or distribution is prohibited. use crate::modules::{ - cache::imap::{ - envelope::Received, - mailbox::{EmailFlag, EnvelopeFlag}, - v2::EmailEnvelopeV3, + cache::{ + imap::{ + envelope::Received, + mailbox::{EmailFlag, EnvelopeFlag}, + }, + model::Envelope, }, common::Addr, grpc::service::rustmailer_grpc::{self}, @@ -132,8 +134,8 @@ impl TryFrom for FlagMessageRequest { } } -impl From> for rustmailer_grpc::CursorDataPage { - fn from(value: CursorDataPage) -> Self { +impl From> for rustmailer_grpc::CursorDataPage { + fn from(value: CursorDataPage) -> Self { Self { next_page_token: value.next_page_token, page_size: value.page_size, @@ -144,8 +146,8 @@ impl From> for rustmailer_grpc::CursorDataPage { } } -impl From> for rustmailer_grpc::PagedMessages { - fn from(value: DataPage) -> Self { +impl From> for rustmailer_grpc::PagedMessages { + fn from(value: DataPage) -> Self { Self { current_page: value.current_page, page_size: value.page_size, @@ -156,16 +158,19 @@ impl From> for rustmailer_grpc::PagedMessages { } } -impl From for rustmailer_grpc::EmailEnvelope { - fn from(value: EmailEnvelopeV3) -> Self { +impl From for rustmailer_grpc::EmailEnvelope { + fn from(value: Envelope) -> Self { Self { account_id: value.account_id, mailbox_id: value.mailbox_id, mailbox_name: value.mailbox_name, - uid: value.uid, + id: value.id, internal_date: value.internal_date, size: value.size, - flags: value.flags.into_iter().map(Into::into).collect(), + flags: value + .flags + .map(|f| f.into_iter().map(Into::into).collect()) + .unwrap_or_default(), flags_hash: value.flags_hash, bcc: value .bcc @@ -215,8 +220,7 @@ impl From for rustmailer_grpc::EmailEnvelope { .map(Into::into) .collect(), received: value.received.map(Into::into), - mid: value.mid, - label_ids: value.labels, + labels: value.labels, } } } diff --git a/src/modules/hook/events/mod.rs b/src/modules/hook/events/mod.rs index 50f62be..4532926 100644 --- a/src/modules/hook/events/mod.rs +++ b/src/modules/hook/events/mod.rs @@ -165,7 +165,7 @@ impl RustMailerEvent { account_id: id!(64), account_email: account_email.clone(), mailbox_name: "INBOX".into(), - uid: 1001, + id: "1001".to_string(), internal_date: Some(timestamp), date: Some(timestamp), size: 2048, @@ -195,7 +195,6 @@ impl RustMailerEvent { size: 1024, file_type: "application/pdf".into(), }]), - mid: None, labels: vec![] } ); diff --git a/src/modules/hook/events/payload.rs b/src/modules/hook/events/payload.rs index b1f13b8..8c1fe9d 100644 --- a/src/modules/hook/events/payload.rs +++ b/src/modules/hook/events/payload.rs @@ -18,8 +18,10 @@ pub struct EmailAddedToFolder { pub account_email: String, /// Name of the mailbox (folder) where the email was added. pub mailbox_name: String, - /// Unique identifier (UID) of the email within the mailbox. - pub uid: u32, + /// The unique ID of the message, either IMAP UID or Gmail API MID. + /// - For IMAP accounts, this is the UID converted to a string. + /// - For Gmail API accounts, this is the message ID returned by the API. + pub id: String, /// Optional internal date (in milliseconds) assigned to the email by the server. pub internal_date: Option, /// Optional date (in milliseconds) of the email, typically from the email's header. @@ -55,13 +57,6 @@ pub struct EmailAddedToFolder { pub to: Option>, /// Optional list of attachments included in the email. pub attachments: Option>, - /// The `mid` field is reserved for potential integration with other backend models. - /// For instance, it can be used to store the email index or ID from external services like the Gmail API. - /// This ID could be used for reference or identification purposes in scenarios where an external service - /// provides an identifier for the email in question. - /// - /// This field is optional, meaning that it may be `None` if no external service identifier is available. - pub mid: Option, /// A list of labels applied to the message. /// /// Each element is a string representing a Gmail label ID (e.g., "INBOX", "UNREAD"). diff --git a/src/modules/imap/session.rs b/src/modules/imap/session.rs index e14559e..b551b95 100644 --- a/src/modules/imap/session.rs +++ b/src/modules/imap/session.rs @@ -2,36 +2,36 @@ // Licensed under RustMailer License Agreement v1.0 // Unauthorized copying, modification, or distribution is prohibited. -use std::{pin::Pin, time::Duration}; +use std::pin::Pin; use tokio::io::{AsyncRead, AsyncWrite, BufWriter}; use tokio_io_timeout::TimeoutStream; pub trait SessionStream: AsyncRead + AsyncWrite + Unpin + Send + Sync + std::fmt::Debug { - /// Change the read timeout on the session stream. - fn set_read_timeout(&mut self, timeout: Option); + // Change the read timeout on the session stream. + // fn set_read_timeout(&mut self, timeout: Option); } impl SessionStream for Box { - fn set_read_timeout(&mut self, timeout: Option) { - self.as_mut().set_read_timeout(timeout); - } + // fn set_read_timeout(&mut self, timeout: Option) { + // self.as_mut().set_read_timeout(timeout); + // } } impl SessionStream for tokio_rustls::client::TlsStream { - fn set_read_timeout(&mut self, timeout: Option) { - self.get_mut().0.set_read_timeout(timeout); - } + // fn set_read_timeout(&mut self, timeout: Option) { + // self.get_mut().0.set_read_timeout(timeout); + // } } impl SessionStream for BufWriter { - fn set_read_timeout(&mut self, timeout: Option) { - self.get_mut().set_read_timeout(timeout); - } + // fn set_read_timeout(&mut self, timeout: Option) { + // self.get_mut().set_read_timeout(timeout); + // } } impl SessionStream for Pin>> { - fn set_read_timeout(&mut self, timeout: Option) { - self.as_mut().set_read_timeout_pinned(timeout); - } + // fn set_read_timeout(&mut self, timeout: Option) { + // self.as_mut().set_read_timeout_pinned(timeout); + // } } diff --git a/src/modules/imap/stats.rs b/src/modules/imap/stats.rs index 7c5eaee..c0aa9ee 100644 --- a/src/modules/imap/stats.rs +++ b/src/modules/imap/stats.rs @@ -4,7 +4,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; -use std::time::Duration; +// use std::time::Duration; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; @@ -64,9 +64,9 @@ impl AsyncWrite for StatsWrapper { } impl SessionStream for StatsWrapper { - fn set_read_timeout(&mut self, timeout: Option) { - self.inner.set_read_timeout(timeout); - } + // fn set_read_timeout(&mut self, timeout: Option) { + // self.inner.set_read_timeout(timeout); + // } } impl std::fmt::Debug for StatsWrapper { diff --git a/src/modules/message/list.rs b/src/modules/message/list.rs index d49d55e..9819b95 100644 --- a/src/modules/message/list.rs +++ b/src/modules/message/list.rs @@ -8,6 +8,7 @@ use crate::{ account::{entity::MailerType, v2::AccountV2}, cache::{ imap::{mailbox::MailBox, thread::EmailThread, v2::EmailEnvelopeV3}, + model::Envelope, vendor::gmail::sync::{ client::GmailClient, envelope::GmailEnvelope, labels::GmailLabels, }, @@ -29,7 +30,7 @@ pub async fn list_messages_in_mailbox( page_size: u64, remote: bool, desc: bool, -) -> RustMailerResult> { +) -> RustMailerResult> { let account = AccountV2::check_account_active(account_id, false).await?; if page_size == 0 { return Err(raise_error!( @@ -73,7 +74,7 @@ async fn fetch_remote_messages( next_page_token: Option<&str>, page_size: u64, desc: bool, -) -> RustMailerResult> { +) -> RustMailerResult> { match account.mailer_type { MailerType::ImapSmtp => { let page = decode_page_token(next_page_token)?; @@ -167,15 +168,15 @@ async fn fetch_remote_messages( }) .await?; - let envelopes: Vec = batch_messages + let envelopes: Vec = batch_messages .into_iter() .map(|m| { let mut envelope: GmailEnvelope = m.try_into()?; envelope.account_id = account_id; envelope.label_name = mailbox_name.into(); - Ok(envelope.into_v3(&label_map)) + Ok(envelope.into_envelope(&label_map)) }) - .collect::>>()?; + .collect::>>()?; let total_pages = (total as f64 / page_size as f64).ceil() as u64; @@ -194,11 +195,11 @@ async fn process_fetches( fetches: Vec, account_id: u64, mailbox_name: &str, -) -> RustMailerResult> { +) -> RustMailerResult> { let mut envelopes = Vec::with_capacity(fetches.len()); for fetch in fetches { let envelope = extract_envelope(&fetch, account_id, mailbox_name)?; - envelopes.push(envelope); + envelopes.push(envelope.into()); } Ok(envelopes) } @@ -209,7 +210,7 @@ async fn fetch_local_messages( next_page_token: Option<&str>, page_size: u64, desc: bool, -) -> RustMailerResult> { +) -> RustMailerResult> { let page = decode_page_token(next_page_token)?; match account.mailer_type { MailerType::ImapSmtp => { @@ -251,7 +252,7 @@ async fn fetch_local_messages( page_size, total_items, Some(total_pages), - items, + items.into_iter().map(Envelope::from).collect(), )) } } @@ -289,7 +290,7 @@ async fn fetch_local_messages( page_size, total_items, Some(total_pages), - items.into_iter().map(|e| e.into_v3(&map)).collect(), + items.into_iter().map(|e| e.into_envelope(&map)).collect(), )) } } @@ -302,7 +303,7 @@ pub async fn list_threads_in_mailbox( page: u64, page_size: u64, desc: bool, -) -> RustMailerResult> { +) -> RustMailerResult> { let account = AccountV2::check_account_active(account_id, false).await?; validate_pagination_params(page, page_size)?; if account.minimal_sync() { @@ -346,7 +347,7 @@ pub async fn list_threads_in_mailbox( pub async fn get_thread_messages( account_id: u64, thread_id: u64, -) -> RustMailerResult> { +) -> RustMailerResult> { let account = AccountV2::check_account_active(account_id, false).await?; if account.minimal_sync() { return Err(raise_error!( @@ -365,7 +366,10 @@ pub async fn get_thread_messages( MailerType::GmailApi => { let envelopes = GmailEnvelope::get_thread(account_id, thread_id).await?; let map = GmailClient::label_map(account_id, account.use_proxy).await?; - Ok(envelopes.into_iter().map(|e| e.into_v3(&map)).collect()) + Ok(envelopes + .into_iter() + .map(|e| e.into_envelope(&map)) + .collect()) } } } diff --git a/src/modules/message/search/payload.rs b/src/modules/message/search/payload.rs index 8ab4974..4133a51 100644 --- a/src/modules/message/search/payload.rs +++ b/src/modules/message/search/payload.rs @@ -7,6 +7,7 @@ use crate::modules::account::entity::MailerType; use crate::modules::cache::imap::address::AddressEntity; use crate::modules::cache::imap::sync::flow::generate_uid_sequence_hashset; use crate::modules::cache::imap::v2::EmailEnvelopeV3; +use crate::modules::cache::model::Envelope; use crate::modules::cache::vendor::gmail::sync::client::GmailClient; use crate::modules::cache::vendor::gmail::sync::envelope::GmailEnvelope; use crate::modules::common::decode_page_token; @@ -485,7 +486,7 @@ impl MessageSearchRequest { next_page_token: Option<&str>, page_size: u64, desc: bool, - ) -> RustMailerResult> { + ) -> RustMailerResult> { let account = AccountV2::check_account_active(account_id, false).await?; match account.mailer_type { MailerType::ImapSmtp => { @@ -504,7 +505,7 @@ impl MessageSearchRequest { account: &AccountV2, next_page_token: Option<&str>, page_size: u64, - ) -> RustMailerResult> { + ) -> RustMailerResult> { if page_size == 0 { return Err(raise_error!( "page_size must be greater than 0.".into(), @@ -571,14 +572,14 @@ impl MessageSearchRequest { }) .await?; - let envelopes: Vec = batch_messages + let envelopes: Vec = batch_messages .into_iter() .map(|m| { let mut envelope: GmailEnvelope = m.try_into()?; envelope.account_id = account_id; - Ok(envelope.into_v3(&label_map)) + Ok(envelope.into_envelope(&label_map)) }) - .collect::>>()?; + .collect::>>()?; let total_pages = (total as f64 / page_size as f64).ceil() as u64; @@ -597,7 +598,7 @@ impl MessageSearchRequest { next_page_token: Option<&str>, page_size: u64, desc: bool, - ) -> RustMailerResult> { + ) -> RustMailerResult> { // Validate page and page_size if page_size == 0 { return Err(raise_error!( @@ -657,7 +658,7 @@ impl MessageSearchRequest { let mut envelopes = Vec::new(); for fetch in fetches { let envelope = extract_envelope(&fetch, account.id, mailbox)?; - envelopes.push(envelope); + envelopes.push(envelope.into()); } let next_page_token = if page == total_pages { @@ -717,7 +718,7 @@ impl MessageSearchRequest { let mut envelopes = Vec::new(); for fetch in fetches { let envelope = extract_envelope(&fetch, account.id, mailbox)?; - envelopes.push(envelope); + envelopes.push(envelope.into()); } let next_page_token = if page == total_pages { None @@ -754,7 +755,7 @@ impl UnifiedSearchRequest { page: u64, page_size: u64, desc: bool, - ) -> RustMailerResult> { + ) -> RustMailerResult> { if page == 0 || page_size == 0 { return Err(raise_error!( "'page' and 'page_size' must be greater than 0.".into(), @@ -803,12 +804,17 @@ impl UnifiedSearchRequest { for (id, account_id, _) in result.items { let account = AccountV2::get(account_id).await?; let envelope = match account.mailer_type { - MailerType::ImapSmtp => EmailEnvelopeV3::get(id).await?.ok_or_else(|| { - raise_error!( - format!("Failed to get EmailEnvelope for hash {id} in search operation"), - ErrorCode::InternalError - ) - })?, + MailerType::ImapSmtp => EmailEnvelopeV3::get(id) + .await? + .ok_or_else(|| { + raise_error!( + format!( + "Failed to get EmailEnvelope for hash {id} in search operation" + ), + ErrorCode::InternalError + ) + })? + .into(), MailerType::GmailApi => { let label_map = GmailClient::label_map(account_id, account.use_proxy).await?; let envelope = GmailEnvelope::get(id).await?.ok_or_else(|| { @@ -819,7 +825,7 @@ impl UnifiedSearchRequest { ErrorCode::InternalError ) })?; - envelope.into_v3(&label_map) + envelope.into_envelope(&label_map) } }; items.push(envelope); diff --git a/src/modules/rest/api/message.rs b/src/modules/rest/api/message.rs index 8a2747c..8aaf7c3 100644 --- a/src/modules/rest/api/message.rs +++ b/src/modules/rest/api/message.rs @@ -3,7 +3,7 @@ // Unauthorized copying, modification, or distribution is prohibited. use crate::current_datetime; -use crate::modules::cache::imap::v2::EmailEnvelopeV3; +use crate::modules::cache::model::Envelope; use crate::modules::common::auth::ClientContext; use crate::modules::message::append::AppendReplyToDraftRequest; use crate::modules::message::attachment::{retrieve_email_attachment, AttachmentRequest}; @@ -135,7 +135,7 @@ impl MessageApi { /// lists messages in descending order; otherwise, ascending. internal date desc: Query>, context: ClientContext, - ) -> ApiResult>> { + ) -> ApiResult>> { let remote = remote.0.unwrap_or(false); let desc = desc.0.unwrap_or(false); let account_id = account_id.0; @@ -175,7 +175,7 @@ impl MessageApi { /// lists messages in descending order; otherwise, ascending. internal date desc: Query>, context: ClientContext, - ) -> ApiResult>> { + ) -> ApiResult>> { let desc = desc.0.unwrap_or(false); let account_id = account_id.0; context.require_account_access(account_id)?; @@ -199,7 +199,7 @@ impl MessageApi { // Thread ID thread_id: Query, context: ClientContext, - ) -> ApiResult>> { + ) -> ApiResult>> { let account_id = account_id.0; context.require_account_access(account_id)?; @@ -319,7 +319,7 @@ impl MessageApi { /// specifying the search criteria (e.g., keywords, flags). payload: Json, context: ClientContext, - ) -> ApiResult>> { + ) -> ApiResult>> { let request = payload.0; let desc = desc.0.unwrap_or(false); let account_id = account_id.0; @@ -351,7 +351,7 @@ impl MessageApi { payload: Json, /// Request context (includes authentication and permissions). context: ClientContext, - ) -> ApiResult>> { + ) -> ApiResult>> { let mut request = payload.0; let desc = desc.0.unwrap_or(false);