add a buncha types back, plus --headless

This commit is contained in:
Michael Zhang 2021-02-22 22:30:20 -06:00
parent 8be5d65435
commit 4ed45e52f0
Signed by: michael
GPG key ID: BDA47A31A3C8EE6B
6 changed files with 214 additions and 50 deletions

View file

@ -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(())

View file

@ -196,8 +196,12 @@ fn build_mailbox_data(pair: Pair<Rule>) -> 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 "<B27397-0100000@cac.washington.edu>") 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 "<B27397-0100000@cac.washington.edu>") BODY ("TEXT" "PLAIN" ("CHARSET" "US-ASCII") NIL NIL "7BIT" 3028 92))"#,
"\r\n",
)),
Ok(Response::Fetch(12, vec![AttributeValue]))
);
}
}

View file

@ -60,7 +60,149 @@ pub enum UidSetMember {
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AttributeValue {}
pub enum AttributeValue {
BodySection {
section: Option<SectionPath>,
index: Option<u32>,
data: Option<String>,
},
BodyStructure(BodyStructure),
Envelope(Box<Envelope>),
Flags(Vec<String>),
InternalDate(String),
ModSeq(u64), // RFC 4551, section 3.3.2
Rfc822(Option<String>),
Rfc822Header(Option<String>),
Rfc822Size(u32),
Rfc822Text(Option<String>),
Uid(u32),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BodyStructure {
Basic {
common: BodyContentCommon,
other: BodyContentSinglePart,
extension: Option<BodyExtension>,
},
Text {
common: BodyContentCommon,
other: BodyContentSinglePart,
lines: u32,
extension: Option<BodyExtension>,
},
Message {
common: BodyContentCommon,
other: BodyContentSinglePart,
envelope: Envelope,
body: Box<BodyStructure>,
lines: u32,
extension: Option<BodyExtension>,
},
Multipart {
common: BodyContentCommon,
bodies: Vec<BodyStructure>,
extension: Option<BodyExtension>,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BodyContentSinglePart {
pub id: Option<String>,
pub md5: Option<String>,
pub description: Option<String>,
pub transfer_encoding: ContentEncoding,
pub octets: u32,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BodyContentCommon {
pub ty: ContentType,
pub disposition: Option<ContentDisposition>,
pub language: Option<Vec<String>>,
pub location: Option<String>,
}
#[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<Vec<(String, String)>>;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BodyExtension {
Num(u32),
Str(Option<String>),
List(Vec<BodyExtension>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Envelope {
pub date: Option<String>,
pub subject: Option<String>,
pub from: Option<Vec<Address>>,
pub sender: Option<Vec<Address>>,
pub reply_to: Option<Vec<Address>>,
pub to: Option<Vec<Address>>,
pub cc: Option<Vec<Address>>,
pub bcc: Option<Vec<Address>>,
pub in_reply_to: Option<String>,
pub message_id: Option<String>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Address {
pub name: Option<String>,
pub adl: Option<String>,
pub mailbox: Option<String>,
pub host: Option<String>,
}
#[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<u32>, Option<MessageSection>),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum MailboxData {

View file

@ -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?
}
};

View file

@ -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<PathBuf>,
/// 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<impl AsRef<Path>>) -> 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(())
}

View file

@ -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);