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<()> { 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?; let resp = self.execute(cmd).await?;
debug!("list response: {:?}", resp); debug!("list response: {:?}", resp);
Ok(()) Ok(())

View file

@ -196,8 +196,12 @@ fn build_mailbox_data(pair: Pair<Rule>) -> MailboxData {
let mut pairs = pair.into_inner(); let mut pairs = pair.into_inner();
let pair = pairs.next().unwrap(); let pair = pairs.next().unwrap();
let (flags, delimiter, name) = build_mailbox_list(pair); let (flags, delimiter, name) = build_mailbox_list(pair);
MailboxData::List { flags, delimiter, name } MailboxData::List {
}, flags,
delimiter,
name,
}
}
_ => unreachable!("{:#?}", pair), _ => unreachable!("{:#?}", pair),
} }
} }
@ -322,12 +326,12 @@ mod tests {
}) })
); );
// assert_eq!( assert_eq!(
// parse_response(concat!( 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#"* 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", "\r\n",
// )), )),
// Ok(Response::Fetch(12, vec![])) Ok(Response::Fetch(12, vec![AttributeValue]))
// ); );
} }
} }

View file

@ -60,7 +60,149 @@ pub enum UidSetMember {
} }
#[derive(Clone, Debug, PartialEq, Eq)] #[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)] #[derive(Clone, Debug, PartialEq, Eq)]
pub enum MailboxData { pub enum MailboxData {

View file

@ -3,7 +3,10 @@
use anyhow::Result; use anyhow::Result;
use futures::{future::FutureExt, stream::StreamExt}; use futures::{future::FutureExt, stream::StreamExt};
use panorama_imap::{ use panorama_imap::{
client::{ClientBuilder, ClientConfig, auth::{self, Auth}}, client::{
auth::{self, Auth},
ClientBuilder, ClientConfig,
},
command::Command as ImapCommand, command::Command as ImapCommand,
}; };
use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle}; use tokio::{sync::mpsc::UnboundedReceiver, task::JoinHandle};

View file

@ -1,15 +1,14 @@
#[macro_use] #[macro_use]
extern crate log; extern crate log;
use std::fs::OpenOptions; use std::path::{Path, PathBuf};
use std::path::PathBuf;
use anyhow::Result; use anyhow::Result;
use fern::colors::{Color, ColoredLevelConfig}; 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 panorama::{config::spawn_config_watcher_system, mail, report_err, ui};
use structopt::StructOpt; use structopt::StructOpt;
use tokio::sync::mpsc; use tokio::{runtime::Runtime, sync::mpsc};
use xdg::BaseDirectories; use xdg::BaseDirectories;
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
@ -18,36 +17,25 @@ struct Opt {
/// The path to the log file. By default, does not log. /// The path to the log file. By default, does not log.
#[structopt(long = "log-file")] #[structopt(long = "log-file")]
log_file: Option<PathBuf>, log_file: Option<PathBuf>,
/// Run this application headlessly
#[structopt(long = "headless")]
headless: bool,
} }
#[tokio::main(flavor = "multi_thread")] fn main() -> Result<()> {
async fn main() -> Result<()> {
let now = chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]");
// parse command line arguments into options struct // parse command line arguments into options struct
let opt = Opt::from_args(); let opt = Opt::from_args();
setup_logger(opt.log_file.as_ref())?;
let colors = ColoredLevelConfig::new() let rt = Runtime::new().unwrap();
.info(Color::Blue) rt.block_on(run(opt)).unwrap();
.debug(Color::BrightBlack)
.warn(Color::Yellow) Ok(())
.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()?;
// #[tokio::main(flavor = "multi_thread")]
async fn run(opt: Opt) -> Result<()> {
let _xdg = BaseDirectories::new()?; let _xdg = BaseDirectories::new()?;
let (_config_thread, config_update) = spawn_config_watcher_system()?; let (_config_thread, config_update) = spawn_config_watcher_system()?;
@ -64,8 +52,10 @@ async fn main() -> Result<()> {
.await; .await;
}); });
if !opt.headless {
let stdout = std::io::stdout(); let stdout = std::io::stdout();
tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err)); tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));
}
exit_rx.recv().await; exit_rx.recv().await;
@ -75,3 +65,29 @@ async fn main() -> Result<()> {
std::process::exit(0); std::process::exit(0);
// Ok(()) // 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; use self::table::Table;
const FRAME: Duration = Duration::from_millis(20); const FRAME_DURATION: Duration = Duration::from_millis(20);
/// X Y W H /// X Y W H
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
@ -28,10 +28,6 @@ pub struct Rect(u16, u16, u16, u16);
/// UI entrypoint. /// UI entrypoint.
pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> { 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)?; execute!(w, cursor::Hide, terminal::EnterAlternateScreen)?;
terminal::enable_raw_mode()?; terminal::enable_raw_mode()?;
@ -57,10 +53,10 @@ pub async fn run_ui(mut w: impl Write + Debug, exit: ExitSender) -> Result<()> {
w.flush()?; w.flush()?;
// approx 60fps // 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 // 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()?; let event = event::read()?;
table.update(&event); table.update(&event);