add a buncha types back, plus --headless
This commit is contained in:
parent
8be5d65435
commit
4ed45e52f0
6 changed files with 214 additions and 50 deletions
|
@ -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(())
|
||||
|
|
|
@ -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]))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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?
|
||||
}
|
||||
};
|
||||
|
|
76
src/main.rs
76
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<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(())
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue