From 4ed45e52f04d3fc84539eed009aa2b9e952b441d Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Mon, 22 Feb 2021 22:30:20 -0600 Subject: [PATCH] add a buncha types back, plus --headless --- imap/src/client/mod.rs | 5 +- imap/src/parser/mod.rs | 22 +++--- imap/src/response/mod.rs | 144 ++++++++++++++++++++++++++++++++++++++- src/mail/mod.rs | 7 +- src/main.rs | 76 +++++++++++++-------- src/ui/mod.rs | 10 +-- 6 files changed, 214 insertions(+), 50 deletions(-) diff --git a/imap/src/client/mod.rs b/imap/src/client/mod.rs index fb6fd7d..5aada44 100644 --- a/imap/src/client/mod.rs +++ b/imap/src/client/mod.rs @@ -147,7 +147,10 @@ impl ClientAuthenticated { } pub async fn list(&mut self) -> Result<()> { - let cmd = Command::List { reference: "".to_owned(), mailbox: "*".to_owned() }; + let cmd = Command::List { + reference: "".to_owned(), + mailbox: "*".to_owned(), + }; let resp = self.execute(cmd).await?; debug!("list response: {:?}", resp); Ok(()) diff --git a/imap/src/parser/mod.rs b/imap/src/parser/mod.rs index 3e84ba4..c017c1e 100644 --- a/imap/src/parser/mod.rs +++ b/imap/src/parser/mod.rs @@ -196,8 +196,12 @@ fn build_mailbox_data(pair: Pair) -> MailboxData { let mut pairs = pair.into_inner(); let pair = pairs.next().unwrap(); let (flags, delimiter, name) = build_mailbox_list(pair); - MailboxData::List { flags, delimiter, name } - }, + MailboxData::List { + flags, + delimiter, + name, + } + } _ => unreachable!("{:#?}", pair), } } @@ -322,12 +326,12 @@ mod tests { }) ); - // assert_eq!( - // parse_response(concat!( - // r#"* 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" "IMAP4rev1 WG mtg summary and minutes" (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) ((NIL NIL "imap" "cac.washington.edu")) ((NIL NIL "minutes" "CNRI.Reston.VA.US") ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL "") BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 92))"#, - // "\r\n", - // )), - // Ok(Response::Fetch(12, vec![])) - // ); + assert_eq!( + parse_response(concat!( + r#"* 12 FETCH (FLAGS (\Seen) INTERNALDATE "17-Jul-1996 02:44:25 -0700" RFC822.SIZE 4286 ENVELOPE ("Wed, 17 Jul 1996 02:23:25 -0700 (PDT)" "IMAP4rev1 WG mtg summary and minutes" (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) (("Terry Gray" NIL "gray" "cac.washington.edu")) ((NIL NIL "imap" "cac.washington.edu")) ((NIL NIL "minutes" "CNRI.Reston.VA.US") ("John Klensin" NIL "KLENSIN" "MIT.EDU")) NIL NIL "") BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 92))"#, + "\r\n", + )), + Ok(Response::Fetch(12, vec![AttributeValue])) + ); } } diff --git a/imap/src/response/mod.rs b/imap/src/response/mod.rs index cfda6c8..ad25c9f 100644 --- a/imap/src/response/mod.rs +++ b/imap/src/response/mod.rs @@ -60,7 +60,149 @@ pub enum UidSetMember { } #[derive(Clone, Debug, PartialEq, Eq)] -pub enum AttributeValue {} +pub enum AttributeValue { + BodySection { + section: Option, + index: Option, + data: Option, + }, + BodyStructure(BodyStructure), + Envelope(Box), + Flags(Vec), + InternalDate(String), + ModSeq(u64), // RFC 4551, section 3.3.2 + Rfc822(Option), + Rfc822Header(Option), + Rfc822Size(u32), + Rfc822Text(Option), + Uid(u32), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BodyStructure { + Basic { + common: BodyContentCommon, + other: BodyContentSinglePart, + extension: Option, + }, + Text { + common: BodyContentCommon, + other: BodyContentSinglePart, + lines: u32, + extension: Option, + }, + Message { + common: BodyContentCommon, + other: BodyContentSinglePart, + envelope: Envelope, + body: Box, + lines: u32, + extension: Option, + }, + Multipart { + common: BodyContentCommon, + bodies: Vec, + extension: Option, + }, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BodyContentSinglePart { + pub id: Option, + pub md5: Option, + pub description: Option, + pub transfer_encoding: ContentEncoding, + pub octets: u32, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct BodyContentCommon { + pub ty: ContentType, + pub disposition: Option, + pub language: Option>, + pub location: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ContentType { + pub ty: String, + pub subtype: String, + pub params: BodyParams, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ContentDisposition { + pub ty: String, + pub params: BodyParams, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum ContentEncoding { + SevenBit, + EightBit, + Binary, + Base64, + QuotedPrintable, + Other(String), +} + +pub type BodyParams = Option>; + +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum BodyExtension { + Num(u32), + Str(Option), + List(Vec), +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Envelope { + pub date: Option, + pub subject: Option, + pub from: Option>, + pub sender: Option>, + pub reply_to: Option>, + pub to: Option>, + pub cc: Option>, + pub bcc: Option>, + pub in_reply_to: Option, + pub message_id: Option, +} + +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Address { + pub name: Option, + pub adl: Option, + pub mailbox: Option, + pub host: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[non_exhaustive] +pub enum Attribute { + Body, + Envelope, + Flags, + InternalDate, + ModSeq, // RFC 4551, section 3.3.2 + Rfc822, + Rfc822Size, + Rfc822Text, + Uid, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum MessageSection { + Header, + Mime, + Text, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum SectionPath { + Full(MessageSection), + Part(Vec, Option), +} #[derive(Clone, Debug, PartialEq, Eq)] pub enum MailboxData { diff --git a/src/mail/mod.rs b/src/mail/mod.rs index 9c3c40c..a34d135 100644 --- a/src/mail/mod.rs +++ b/src/mail/mod.rs @@ -3,7 +3,10 @@ use anyhow::Result; use futures::{future::FutureExt, stream::StreamExt}; use panorama_imap::{ - client::{ClientBuilder, ClientConfig, auth::{self, Auth}}, + client::{ + auth::{self, Auth}, + ClientBuilder, ClientConfig, + }, command::Command as ImapCommand, }; use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle}; @@ -91,7 +94,7 @@ async fn imap_main(acct: MailAccountConfig) -> Result<()> { // check if the authentication method is supported let mut authed = match acct.imap.auth { ImapAuth::Plain { username, password } => { - let auth = auth::Plain {username, password}; + let auth = auth::Plain { username, password }; auth.perform_auth(unauth).await? } }; diff --git a/src/main.rs b/src/main.rs index 19eecf3..d419b6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,14 @@ #[macro_use] extern crate log; -use std::fs::OpenOptions; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use anyhow::Result; use fern::colors::{Color, ColoredLevelConfig}; -use futures::future::TryFutureExt; +use futures::future::{FutureExt, TryFutureExt}; use panorama::{config::spawn_config_watcher_system, mail, report_err, ui}; use structopt::StructOpt; -use tokio::sync::mpsc; +use tokio::{runtime::Runtime, sync::mpsc}; use xdg::BaseDirectories; #[derive(Debug, StructOpt)] @@ -18,36 +17,25 @@ struct Opt { /// The path to the log file. By default, does not log. #[structopt(long = "log-file")] log_file: Option, + + /// Run this application headlessly + #[structopt(long = "headless")] + headless: bool, } -#[tokio::main(flavor = "multi_thread")] -async fn main() -> Result<()> { - let now = chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"); - +fn main() -> Result<()> { // parse command line arguments into options struct let opt = Opt::from_args(); + setup_logger(opt.log_file.as_ref())?; - let colors = ColoredLevelConfig::new() - .info(Color::Blue) - .debug(Color::BrightBlack) - .warn(Color::Yellow) - .error(Color::Red); - let mut logger = fern::Dispatch::new() - .format(move |out, message, record| { - out.finish(format_args!( - "{}[{}][{}] {}", - now, - record.target(), - colors.color(record.level()), - message - )) - }) - .level(log::LevelFilter::Debug); - if let Some(log_file) = opt.log_file { - logger = logger.chain(fern::log_file(log_file)?); - } - logger.apply()?; + let rt = Runtime::new().unwrap(); + rt.block_on(run(opt)).unwrap(); + Ok(()) +} + +// #[tokio::main(flavor = "multi_thread")] +async fn run(opt: Opt) -> Result<()> { let _xdg = BaseDirectories::new()?; let (_config_thread, config_update) = spawn_config_watcher_system()?; @@ -64,8 +52,10 @@ async fn main() -> Result<()> { .await; }); - let stdout = std::io::stdout(); - tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err)); + if !opt.headless { + let stdout = std::io::stdout(); + tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err)); + } exit_rx.recv().await; @@ -75,3 +65,29 @@ async fn main() -> Result<()> { std::process::exit(0); // Ok(()) } + +fn setup_logger(log_file: Option>) -> Result<()> { + let now = chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"); + let colors = ColoredLevelConfig::new() + .info(Color::Blue) + .debug(Color::BrightBlack) + .warn(Color::Yellow) + .error(Color::Red); + let mut logger = fern::Dispatch::new() + .format(move |out, message, record| { + out.finish(format_args!( + "{}[{}][{}] {}", + now, + record.target(), + colors.color(record.level()), + message + )) + }) + .level(log::LevelFilter::Debug); + if let Some(log_file) = log_file { + logger = logger.chain(fern::log_file(log_file)?); + } + logger.apply()?; + + Ok(()) +} diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 27915e8..58005c3 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -20,7 +20,7 @@ use crate::ExitSender; use self::table::Table; -const FRAME: Duration = Duration::from_millis(20); +const FRAME_DURATION: Duration = Duration::from_millis(20); /// X Y W H #[derive(Copy, Clone)] @@ -28,10 +28,6 @@ pub struct Rect(u16, u16, u16, u16); /// UI entrypoint. pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> { - loop { - tokio::time::sleep(Duration::from_secs(4000)).await; - } - execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?; terminal::enable_raw_mode()?; @@ -57,10 +53,10 @@ pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> { w.flush()?; // approx 60fps - time::sleep(FRAME).await; + time::sleep(FRAME_DURATION).await; // check to see if there's even an event this frame. otherwise, just keep going - if event::poll(FRAME)? { + if event::poll(FRAME_DURATION)? { let event = event::read()?; table.update(&event);