Add TLS support and limit inbox fetch to last 50 messages
Support both plain and TLS IMAP connections via an ImapSession enum, enabling use with Gmail and other TLS-only servers. Limit fetch range to the most recent 50 messages to avoid hanging on large mailboxes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
7bb8aaec32
commit
cc7eeba7f8
3 changed files with 82 additions and 17 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -524,6 +524,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"crossterm",
|
||||
"imap",
|
||||
"native-tls",
|
||||
"ratatui",
|
||||
"serde",
|
||||
"toml",
|
||||
|
|
|
|||
|
|
@ -7,5 +7,6 @@ edition = "2024"
|
|||
ratatui = "0.30"
|
||||
crossterm = "0.29"
|
||||
imap = "2.4"
|
||||
native-tls = "0.2"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
toml = "1.0"
|
||||
97
src/lib.rs
97
src/lib.rs
|
|
@ -3,6 +3,7 @@ use std::net::TcpStream;
|
|||
use std::time::{Duration, Instant};
|
||||
use crossterm::event;
|
||||
use crossterm::event::{Event, KeyCode};
|
||||
use native_tls::TlsStream;
|
||||
use ratatui::backend::CrosstermBackend;
|
||||
use ratatui::layout::{Constraint, Direction, Layout};
|
||||
use ratatui::prelude::{Color, Modifier, Style};
|
||||
|
|
@ -20,28 +21,57 @@ struct Email {
|
|||
date: String,
|
||||
}
|
||||
|
||||
type ImapSession = imap::Session<TcpStream>;
|
||||
enum ImapSession {
|
||||
Plain(imap::Session<TcpStream>),
|
||||
Tls(imap::Session<TlsStream<TcpStream>>),
|
||||
}
|
||||
|
||||
impl ImapSession {
|
||||
fn noop(&mut self) -> imap::error::Result<()> {
|
||||
match self {
|
||||
Self::Plain(s) => s.noop(),
|
||||
Self::Tls(s) => s.noop(),
|
||||
}
|
||||
}
|
||||
|
||||
fn logout(&mut self) -> imap::error::Result<()> {
|
||||
match self {
|
||||
Self::Plain(s) => s.logout(),
|
||||
Self::Tls(s) => s.logout(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn connect(config: &Config) -> Result<ImapSession, String> {
|
||||
let imap_cfg = &config.imap;
|
||||
let stream =
|
||||
TcpStream::connect((&*imap_cfg.host, imap_cfg.port)).map_err(|e| e.to_string())?;
|
||||
let client = imap::Client::new(stream);
|
||||
let session = client
|
||||
.login(&imap_cfg.username, &imap_cfg.password)
|
||||
.map_err(|(e, _)| e.to_string())?;
|
||||
Ok(session)
|
||||
if imap_cfg.use_tls {
|
||||
let tls = native_tls::TlsConnector::builder()
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
let client = imap::connect(
|
||||
(&*imap_cfg.host, imap_cfg.port),
|
||||
&imap_cfg.host,
|
||||
&tls,
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let session = client
|
||||
.login(&imap_cfg.username, &imap_cfg.password)
|
||||
.map_err(|(e, _)| e.to_string())?;
|
||||
Ok(ImapSession::Tls(session))
|
||||
} else {
|
||||
let stream =
|
||||
TcpStream::connect((&*imap_cfg.host, imap_cfg.port)).map_err(|e| e.to_string())?;
|
||||
let client = imap::Client::new(stream);
|
||||
let session = client
|
||||
.login(&imap_cfg.username, &imap_cfg.password)
|
||||
.map_err(|(e, _)| e.to_string())?;
|
||||
Ok(ImapSession::Plain(session))
|
||||
}
|
||||
}
|
||||
|
||||
fn fetch_inbox(session: &mut ImapSession) -> Result<Vec<Email>, String> {
|
||||
session.select("INBOX").map_err(|e| e.to_string())?;
|
||||
|
||||
let messages = session
|
||||
.fetch("1:*", "BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]")
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
fn parse_emails(fetches: &[imap::types::Fetch]) -> Vec<Email> {
|
||||
let mut emails = Vec::new();
|
||||
for message in messages.iter() {
|
||||
for message in fetches {
|
||||
if let Some(body) = message.header() {
|
||||
let header = String::from_utf8_lossy(body);
|
||||
let mut subject = String::new();
|
||||
|
|
@ -61,8 +91,41 @@ fn fetch_inbox(session: &mut ImapSession) -> Result<Vec<Email>, String> {
|
|||
emails.push(Email { subject, from, date });
|
||||
}
|
||||
}
|
||||
emails
|
||||
}
|
||||
|
||||
Ok(emails)
|
||||
const MAX_FETCH: u32 = 50;
|
||||
|
||||
fn fetch_range(exists: u32) -> String {
|
||||
let start = exists.saturating_sub(MAX_FETCH - 1).max(1);
|
||||
format!("{}:{}", start, exists)
|
||||
}
|
||||
|
||||
fn fetch_inbox(session: &mut ImapSession) -> Result<Vec<Email>, String> {
|
||||
match session {
|
||||
ImapSession::Plain(s) => {
|
||||
let mailbox = s.select("INBOX").map_err(|e| e.to_string())?;
|
||||
if mailbox.exists == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let range = fetch_range(mailbox.exists);
|
||||
let messages = s
|
||||
.fetch(range, "BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]")
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(parse_emails(&messages))
|
||||
}
|
||||
ImapSession::Tls(s) => {
|
||||
let mailbox = s.select("INBOX").map_err(|e| e.to_string())?;
|
||||
if mailbox.exists == 0 {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
let range = fetch_range(mailbox.exists);
|
||||
let messages = s
|
||||
.fetch(range, "BODY.PEEK[HEADER.FIELDS (SUBJECT FROM DATE)]")
|
||||
.map_err(|e| e.to_string())?;
|
||||
Ok(parse_emails(&messages))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Refresh inbox using NOOP + fetch. Reconnects on error.
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue