sync: mailcore 0.2.1-beta
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use crate::types::{FolderInfo, MailError, MailServerConfig};
|
||||
use crate::types::{AuthCredential, FolderInfo, MailError, MailServerConfig};
|
||||
use flutter_rust_bridge::frb;
|
||||
|
||||
use mail_parser::MimeHeaders;
|
||||
@@ -13,7 +13,19 @@ lazy_static! {
|
||||
}
|
||||
|
||||
pub fn init_logger() {
|
||||
tracing_subscriber::fmt::init();
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
use tracing_subscriber::prelude::*;
|
||||
if let Ok(android_layer) = tracing_android::layer("KoraxRust") {
|
||||
let _ = tracing_subscriber::registry()
|
||||
.with(android_layer)
|
||||
.try_init();
|
||||
}
|
||||
}
|
||||
#[cfg(not(target_os = "android"))]
|
||||
{
|
||||
tracing_subscriber::fmt::init();
|
||||
}
|
||||
|
||||
// Initialize Rustls with 'ring' as the default provider to prevent crashes
|
||||
// when multiple providers (ring, aws-lc-rs) are present in the dependency graph.
|
||||
@@ -21,6 +33,170 @@ pub fn init_logger() {
|
||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||
}
|
||||
|
||||
/// Step-by-step IMAP connection diagnostic. Returns a multi-line string describing
|
||||
/// each phase (TCP → TLS → greeting → XOAUTH2 → NOOP) with timing and outcome.
|
||||
/// Use this from Flutter to identify exactly where the hang is on Android.
|
||||
pub async fn diagnose_imap_connection(host: String, port: u32, email: String) -> String {
|
||||
use std::sync::Arc;
|
||||
use std::time::{Duration, Instant};
|
||||
use tokio::io::AsyncReadExt;
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_rustls::TlsConnector;
|
||||
|
||||
let mut out = String::new();
|
||||
let mut root_cert_store = rustls::RootCertStore::empty();
|
||||
root_cert_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
|
||||
|
||||
let tls_config = match rustls::ClientConfig::builder_with_provider(Arc::new(
|
||||
rustls::crypto::ring::default_provider(),
|
||||
))
|
||||
.with_safe_default_protocol_versions()
|
||||
.map_err(|e| e.to_string())
|
||||
{
|
||||
Ok(b) => b.with_root_certificates(root_cert_store).with_no_client_auth(),
|
||||
Err(e) => {
|
||||
out.push_str(&format!("TLS config: FAILED ({})\n", e));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
let addr = format!("{}:{}", host, port);
|
||||
|
||||
// Phase 1: TCP connect
|
||||
let t0 = Instant::now();
|
||||
let stream = match tokio::time::timeout(Duration::from_secs(10), TcpStream::connect(&addr))
|
||||
.await
|
||||
{
|
||||
Err(_) => {
|
||||
out.push_str(&format!("TCP: TIMEOUT after 10s\n"));
|
||||
return out;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
out.push_str(&format!("TCP: FAILED ({}) in {:.1}s\n", e, t0.elapsed().as_secs_f32()));
|
||||
return out;
|
||||
}
|
||||
Ok(Ok(s)) => {
|
||||
out.push_str(&format!("TCP: OK in {:.1}s\n", t0.elapsed().as_secs_f32()));
|
||||
s
|
||||
}
|
||||
};
|
||||
|
||||
// Phase 2: TLS handshake
|
||||
let connector = TlsConnector::from(Arc::new(tls_config));
|
||||
let server_name = match rustls_pki_types::ServerName::try_from(host.clone())
|
||||
.map_err(|_| format!("Invalid DNS name: {}", host))
|
||||
.and_then(|n| Ok(n.to_owned()))
|
||||
{
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
out.push_str(&format!("TLS name: FAILED ({})\n", e));
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
let t1 = Instant::now();
|
||||
let mut tls_stream = match tokio::time::timeout(
|
||||
Duration::from_secs(10),
|
||||
connector.connect(server_name, stream),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(_) => {
|
||||
out.push_str(&format!("TLS: TIMEOUT after 10s\n"));
|
||||
return out;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
out.push_str(&format!("TLS: FAILED ({}) in {:.1}s\n", e, t1.elapsed().as_secs_f32()));
|
||||
return out;
|
||||
}
|
||||
Ok(Ok(s)) => {
|
||||
out.push_str(&format!("TLS: OK in {:.1}s\n", t1.elapsed().as_secs_f32()));
|
||||
s
|
||||
}
|
||||
};
|
||||
|
||||
// Phase 3: Read IMAP server greeting (first line)
|
||||
let mut greeting = vec![0u8; 512];
|
||||
let t2 = Instant::now();
|
||||
match tokio::time::timeout(Duration::from_secs(10), tls_stream.read(&mut greeting)).await {
|
||||
Err(_) => {
|
||||
out.push_str(&format!("Greeting: TIMEOUT after 10s\n"));
|
||||
return out;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
out.push_str(&format!("Greeting: READ FAILED ({}) in {:.1}s\n", e, t2.elapsed().as_secs_f32()));
|
||||
return out;
|
||||
}
|
||||
Ok(Ok(n)) => {
|
||||
let line = String::from_utf8_lossy(&greeting[..n.min(120)]).to_string();
|
||||
out.push_str(&format!(
|
||||
"Greeting: OK in {:.1}s — {:?}\n",
|
||||
t2.elapsed().as_secs_f32(),
|
||||
line.trim()
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Phase 4: Token fetch
|
||||
let t3 = Instant::now();
|
||||
let token = match tokio::time::timeout(
|
||||
Duration::from_secs(10),
|
||||
crate::oauth::get_valid_token(&email),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Err(_) => {
|
||||
out.push_str(&format!("OAuth2 token: TIMEOUT after 10s\n"));
|
||||
return out;
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
out.push_str(&format!("OAuth2 token: FAILED ({}) in {:.1}s\n", e, t3.elapsed().as_secs_f32()));
|
||||
return out;
|
||||
}
|
||||
Ok(Ok(t)) => {
|
||||
out.push_str(&format!(
|
||||
"OAuth2 token: OK in {:.1}s (len={})\n",
|
||||
t3.elapsed().as_secs_f32(),
|
||||
t.len()
|
||||
));
|
||||
t
|
||||
}
|
||||
};
|
||||
|
||||
// Phase 5: XOAUTH2 authenticate (raw IMAP)
|
||||
use tokio::io::AsyncWriteExt;
|
||||
let auth_str = format!("user={}\x01auth=Bearer {}\x01\x01", email, token);
|
||||
use base64::Engine as _;
|
||||
let encoded = base64::engine::general_purpose::STANDARD.encode(auth_str.as_bytes());
|
||||
let cmd = format!("A001 AUTHENTICATE XOAUTH2 {}\r\n", encoded);
|
||||
|
||||
let t4 = Instant::now();
|
||||
if let Err(e) = tokio::time::timeout(Duration::from_secs(5), tls_stream.write_all(cmd.as_bytes())).await {
|
||||
out.push_str(&format!("AUTHENTICATE write: {:?}\n", e));
|
||||
return out;
|
||||
}
|
||||
|
||||
let mut resp = vec![0u8; 2048];
|
||||
match tokio::time::timeout(Duration::from_secs(10), tls_stream.read(&mut resp)).await {
|
||||
Err(_) => {
|
||||
out.push_str(&format!("AUTHENTICATE response: TIMEOUT after 10s in {:.1}s\n", t4.elapsed().as_secs_f32()));
|
||||
}
|
||||
Ok(Err(e)) => {
|
||||
out.push_str(&format!("AUTHENTICATE response: FAILED ({}) in {:.1}s\n", e, t4.elapsed().as_secs_f32()));
|
||||
}
|
||||
Ok(Ok(n)) => {
|
||||
let line = String::from_utf8_lossy(&resp[..n.min(200)]).to_string();
|
||||
out.push_str(&format!(
|
||||
"AUTHENTICATE response in {:.1}s: {:?}\n",
|
||||
t4.elapsed().as_secs_f32(),
|
||||
line.trim()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
pub async fn discover_server(email: String) -> Result<MailServerConfig, MailError> {
|
||||
crate::dns::discover_mail_server(&email)
|
||||
.await
|
||||
@@ -31,12 +207,12 @@ pub async fn discover_server(email: String) -> Result<MailServerConfig, MailErro
|
||||
pub async fn connect_and_list_folders(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
show_all_folders: bool,
|
||||
storage_path: String,
|
||||
) -> Result<Vec<FolderInfo>, MailError> {
|
||||
// This call will create/cache the session in the manager
|
||||
let client_arc = crate::session_manager::get_client(config, email, password).await?;
|
||||
let client_arc = crate::session_manager::get_client(config, email, credential).await?;
|
||||
|
||||
let mut client = client_arc.lock().await;
|
||||
let mut folders = client
|
||||
@@ -67,7 +243,7 @@ pub fn get_total_unread_count(storage_path: String) -> i32 {
|
||||
pub async fn get_messages(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
) -> Result<Vec<crate::types::MessageHeader>, MailError> {
|
||||
let mailbox = if mailbox.to_uppercase() == "INBOX" {
|
||||
@@ -76,7 +252,7 @@ pub async fn get_messages(
|
||||
mailbox
|
||||
};
|
||||
|
||||
let client_arc = crate::session_manager::get_client(config, email, password).await?;
|
||||
let client_arc = crate::session_manager::get_client(config, email, credential).await?;
|
||||
|
||||
let mut client = client_arc.lock().await;
|
||||
client
|
||||
@@ -92,7 +268,7 @@ pub async fn get_messages(
|
||||
pub async fn get_message_body(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
uid: u32,
|
||||
storage_path: String,
|
||||
@@ -117,9 +293,9 @@ pub async fn get_message_body(
|
||||
body
|
||||
})
|
||||
} else {
|
||||
let mut client = crate::imap::ImapClient::new(config.clone(), email.clone());
|
||||
let mut client = crate::imap::ImapClient::new(config.clone(), email.clone(), credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -161,7 +337,7 @@ pub async fn get_message_body(
|
||||
pub async fn set_message_flag(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
uid: u32,
|
||||
flag: Option<String>,
|
||||
@@ -176,9 +352,9 @@ pub async fn set_message_flag(
|
||||
}
|
||||
|
||||
// Dedicated connection — never blocked by sync_mailbox holding the shared session lock.
|
||||
let mut client = crate::imap::ImapClient::new(config, email);
|
||||
let mut client = crate::imap::ImapClient::new(config, email, credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -231,11 +407,11 @@ pub fn get_contact_by_email(
|
||||
pub async fn append_to_sent(
|
||||
config: &MailServerConfig,
|
||||
email: &str,
|
||||
password: &str,
|
||||
credential: AuthCredential,
|
||||
message_bytes: &[u8],
|
||||
) {
|
||||
let mut client = crate::imap::ImapClient::new(config.clone(), email.to_string());
|
||||
if let Err(e) = client.connect(password).await {
|
||||
let mut client = crate::imap::ImapClient::new(config.clone(), email.to_string(), credential);
|
||||
if let Err(e) = client.connect().await {
|
||||
tracing::warn!("append_to_sent: IMAP connect failed: {}", e);
|
||||
return;
|
||||
}
|
||||
@@ -258,7 +434,7 @@ pub async fn append_to_sent(
|
||||
pub async fn send_email(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
to: String,
|
||||
subject: String,
|
||||
body: String,
|
||||
@@ -272,19 +448,19 @@ pub async fn send_email(
|
||||
return Err(MailError::Generic("PGP encryption not supported in mailcore. Use mailmore.".to_string()));
|
||||
}
|
||||
|
||||
let client = crate::smtp::SmtpClient::new(config.clone(), email.clone());
|
||||
let message_bytes = client
|
||||
.send_mail(&password, &to, &subject, &body, attachments)
|
||||
let smtp_client = crate::smtp::SmtpClient::new(config.clone(), email.clone(), credential.clone());
|
||||
let message_bytes = smtp_client
|
||||
.send_mail(&to, &subject, &body, attachments)
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
append_to_sent(&config, &email, &password, &message_bytes).await;
|
||||
append_to_sent(&config, &email, credential, &message_bytes).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn prefetch_bodies(
|
||||
config: crate::types::MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
uids: Vec<u32>,
|
||||
storage_path: String,
|
||||
@@ -309,9 +485,9 @@ pub async fn prefetch_bodies(
|
||||
}
|
||||
|
||||
// Use a DEDICATED connection so prefetch never blocks user-initiated fetches
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone());
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone(), credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
client
|
||||
@@ -561,7 +737,7 @@ fn detect_pgp_status(parsed: &mail_parser::Message) -> (crate::types::PgpStatus,
|
||||
pub async fn sync_mailbox(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
storage_path: String,
|
||||
cache_days: i64,
|
||||
@@ -583,7 +759,7 @@ pub async fn sync_mailbox(
|
||||
// Step 1: Run sync on the shared session.
|
||||
let sync_result = {
|
||||
let client_arc =
|
||||
crate::session_manager::get_client(config.clone(), email.clone(), password.clone())
|
||||
crate::session_manager::get_client(config.clone(), email.clone(), credential.clone())
|
||||
.await?;
|
||||
let mut client = client_arc.lock().await;
|
||||
client
|
||||
@@ -595,8 +771,8 @@ pub async fn sync_mailbox(
|
||||
// Step 2: FLAG refresh on a dedicated connection.
|
||||
let uids = sync_result.uids_for_flag_refresh;
|
||||
if !uids.is_empty() {
|
||||
let mut dedicated = crate::imap::ImapClient::new(config, email);
|
||||
match dedicated.connect(&password).await {
|
||||
let mut dedicated = crate::imap::ImapClient::new(config, email, credential);
|
||||
match dedicated.connect().await {
|
||||
Ok(()) => {
|
||||
if let Err(e) = dedicated.refresh_flags(&mailbox, &uids, &storage).await {
|
||||
tracing::warn!("FLAG refresh via dedicated connection failed: {}", e);
|
||||
@@ -618,7 +794,7 @@ pub async fn sync_mailbox(
|
||||
pub async fn listen_for_updates(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
) -> Result<tokio::sync::mpsc::Receiver<bool>, MailError> {
|
||||
let mailbox = if mailbox.to_uppercase() == "INBOX" {
|
||||
@@ -627,9 +803,9 @@ pub async fn listen_for_updates(
|
||||
mailbox
|
||||
};
|
||||
|
||||
let mut client = crate::imap::ImapClient::new(config.clone(), email.clone());
|
||||
let mut client = crate::imap::ImapClient::new(config.clone(), email.clone(), credential.clone());
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
client
|
||||
@@ -637,7 +813,6 @@ pub async fn listen_for_updates(
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
let password_clone = password.clone();
|
||||
let mailbox_clone = mailbox.clone();
|
||||
let email_clone = email.clone();
|
||||
|
||||
@@ -674,7 +849,7 @@ pub async fn listen_for_updates(
|
||||
Err(e) => {
|
||||
tracing::error!("IDLE error for {}: {}", email_clone, e);
|
||||
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
|
||||
if let Err(re_err) = client.reconnect(&password_clone).await {
|
||||
if let Err(re_err) = client.reconnect().await {
|
||||
tracing::error!("IDLE reconnect failed: {}", re_err);
|
||||
break;
|
||||
}
|
||||
@@ -754,12 +929,12 @@ pub fn delete_draft(path: String, id: i64) -> Result<(), MailError> {
|
||||
pub async fn sync_drafts(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
storage_path: String,
|
||||
) -> Result<(), MailError> {
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone());
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone(), credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -869,7 +1044,7 @@ pub async fn sync_drafts(
|
||||
pub async fn move_message(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
from_mailbox: String,
|
||||
to_mailbox: String,
|
||||
uid: u32,
|
||||
@@ -885,9 +1060,9 @@ pub async fn move_message(
|
||||
} else {
|
||||
to_mailbox
|
||||
};
|
||||
let mut client = crate::imap::ImapClient::new(config, email);
|
||||
let mut client = crate::imap::ImapClient::new(config, email, credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -913,7 +1088,7 @@ pub async fn move_message(
|
||||
pub async fn move_messages(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
from_mailbox: String,
|
||||
to_mailbox: String,
|
||||
uids: Vec<u32>,
|
||||
@@ -933,9 +1108,9 @@ pub async fn move_messages(
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut client = crate::imap::ImapClient::new(config, email);
|
||||
let mut client = crate::imap::ImapClient::new(config, email, credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -961,7 +1136,7 @@ pub async fn move_messages(
|
||||
pub async fn delete_messages_permanently(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
uids: Vec<u32>,
|
||||
storage_path: String,
|
||||
@@ -969,9 +1144,9 @@ pub async fn delete_messages_permanently(
|
||||
if uids.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
let mut client = crate::imap::ImapClient::new(config, email);
|
||||
let mut client = crate::imap::ImapClient::new(config, email, credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -1009,7 +1184,7 @@ pub fn mark_as_read_local(
|
||||
pub async fn mark_as_read(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
uid: u32,
|
||||
storage_path: String,
|
||||
@@ -1020,9 +1195,9 @@ pub async fn mark_as_read(
|
||||
mailbox
|
||||
};
|
||||
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone());
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone(), credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -1045,7 +1220,7 @@ pub async fn mark_as_read(
|
||||
pub async fn mark_as_unread(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
uid: u32,
|
||||
storage_path: String,
|
||||
@@ -1056,9 +1231,9 @@ pub async fn mark_as_unread(
|
||||
mailbox
|
||||
};
|
||||
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone());
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone(), credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -1081,7 +1256,7 @@ pub async fn mark_as_unread(
|
||||
pub async fn mark_all_as_read(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
storage_path: String,
|
||||
) -> Result<(), MailError> {
|
||||
@@ -1091,9 +1266,9 @@ pub async fn mark_all_as_read(
|
||||
mailbox
|
||||
};
|
||||
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone());
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone(), credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -1135,7 +1310,7 @@ pub fn prepare_delete_all_messages(
|
||||
pub async fn delete_all_messages(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
storage_path: String,
|
||||
wipe_drafts: bool,
|
||||
@@ -1146,9 +1321,9 @@ pub async fn delete_all_messages(
|
||||
mailbox
|
||||
};
|
||||
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone());
|
||||
let mut client = crate::imap::ImapClient::new(config, email.clone(), credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
|
||||
@@ -1177,7 +1352,7 @@ pub async fn delete_all_messages(
|
||||
pub async fn resume_pending_wipes(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
storage_path: String,
|
||||
) -> Result<(), MailError> {
|
||||
let storage = crate::storage::Storage::new(&storage_path)
|
||||
@@ -1189,7 +1364,7 @@ pub async fn resume_pending_wipes(
|
||||
let _ = delete_all_messages(
|
||||
config.clone(),
|
||||
email.clone(),
|
||||
password.clone(),
|
||||
credential.clone(),
|
||||
folder,
|
||||
storage_path.clone(),
|
||||
wd,
|
||||
@@ -1202,7 +1377,7 @@ pub async fn resume_pending_wipes(
|
||||
pub async fn create_folder(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
folder_name: String,
|
||||
) -> Result<(), MailError> {
|
||||
let folder_name = if folder_name.to_uppercase() == "INBOX" {
|
||||
@@ -1210,9 +1385,9 @@ pub async fn create_folder(
|
||||
} else {
|
||||
folder_name
|
||||
};
|
||||
let mut client = crate::imap::ImapClient::new(config, email);
|
||||
let mut client = crate::imap::ImapClient::new(config, email, credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
let result = client.create_folder(&folder_name).await.map_err(|e| {
|
||||
@@ -1225,7 +1400,7 @@ pub async fn create_folder(
|
||||
pub async fn delete_folder(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
folder_name: String,
|
||||
storage_path: String,
|
||||
) -> Result<(), MailError> {
|
||||
@@ -1234,9 +1409,9 @@ pub async fn delete_folder(
|
||||
} else {
|
||||
folder_name
|
||||
};
|
||||
let mut client = crate::imap::ImapClient::new(config, email);
|
||||
let mut client = crate::imap::ImapClient::new(config, email, credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
let result = client.delete_folder(&folder_name).await.map_err(|e| {
|
||||
@@ -1270,7 +1445,7 @@ pub fn delete_credential(account: String, service: String) -> Result<(), MailErr
|
||||
pub async fn load_older_messages(
|
||||
config: MailServerConfig,
|
||||
email: String,
|
||||
password: String,
|
||||
credential: AuthCredential,
|
||||
mailbox: String,
|
||||
oldest_uid: u32,
|
||||
batch_size: u32,
|
||||
@@ -1284,9 +1459,9 @@ pub async fn load_older_messages(
|
||||
|
||||
let local_uids = storage.get_uids(&mailbox).unwrap_or_default();
|
||||
|
||||
let mut client = crate::imap::ImapClient::new(config, email);
|
||||
let mut client = crate::imap::ImapClient::new(config, email, credential);
|
||||
client
|
||||
.connect(&password)
|
||||
.connect()
|
||||
.await
|
||||
.map_err(|e| MailError::Network(e.to_string()))?;
|
||||
client
|
||||
@@ -1337,11 +1512,61 @@ pub fn get_folder_stats(
|
||||
) -> Result<crate::types::FolderStats, MailError> {
|
||||
let storage = crate::storage::Storage::new(&storage_path)
|
||||
.map_err(|e| MailError::Generic(e.to_string()))?;
|
||||
let (unread, server_total) = storage
|
||||
let (unread, server_total, local_total) = storage
|
||||
.get_folder_stats(&email, &mailbox)
|
||||
.map_err(|e| MailError::Generic(e.to_string()))?;
|
||||
Ok(crate::types::FolderStats {
|
||||
unread,
|
||||
server_total,
|
||||
local_total,
|
||||
})
|
||||
}
|
||||
|
||||
/// Populate the in-memory OAuth2 token store from Flutter secure storage.
|
||||
/// Call at startup for every OAuth2 account with the persisted refresh token.
|
||||
/// Rust will perform the first token refresh automatically on the next IMAP connect.
|
||||
pub fn init_oauth2_session(email: String, refresh_token: String) {
|
||||
crate::oauth::init_session(&email, &refresh_token);
|
||||
}
|
||||
|
||||
/// Remove all OAuth2 tokens for an account from the in-memory store.
|
||||
/// Call when removing an OAuth2 account.
|
||||
pub fn delete_oauth2_tokens(email: String) -> Result<(), MailError> {
|
||||
crate::oauth::delete_tokens(&email);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Provide OAuth2 client credentials. Must be called once at startup before
|
||||
/// any Google sign-in or token refresh. Lives in the private mailmore layer.
|
||||
pub fn init_google_credentials(client_id: String, client_secret: String) {
|
||||
crate::oauth::init_credentials(&client_id, &client_secret);
|
||||
}
|
||||
|
||||
/// Provide Desktop/mobile PKCE client credentials for browser-based OAuth flow.
|
||||
pub fn init_google_desktop_credentials(client_id: String, client_secret: String) {
|
||||
crate::oauth::init_desktop_credentials(&client_id, &client_secret);
|
||||
}
|
||||
|
||||
/// Exchange a Google server-side authorization code for access + refresh tokens.
|
||||
/// Returns (access_token, refresh_token) — the caller must persist the refresh
|
||||
/// token via Flutter secure storage for use across app restarts.
|
||||
pub async fn exchange_google_auth_code(
|
||||
email: String,
|
||||
server_auth_code: String,
|
||||
) -> Result<Vec<String>, MailError> {
|
||||
let (access, refresh) =
|
||||
crate::oauth::exchange_auth_code(&email, &server_auth_code).await?;
|
||||
Ok(vec![access, refresh])
|
||||
}
|
||||
|
||||
/// Exchange an authorization code from the desktop PKCE flow.
|
||||
/// Returns (email, access_token, refresh_token).
|
||||
pub async fn exchange_google_auth_code_desktop(
|
||||
code: String,
|
||||
redirect_uri: String,
|
||||
code_verifier: String,
|
||||
) -> Result<Vec<String>, MailError> {
|
||||
let (email, access, refresh) =
|
||||
crate::oauth::exchange_auth_code_desktop(&code, &redirect_uri, &code_verifier).await?;
|
||||
Ok(vec![email, access, refresh])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user