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<()> {
|
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(())
|
||||||
|
|
|
@ -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]))
|
||||||
// );
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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};
|
||||||
|
@ -91,7 +94,7 @@ async fn imap_main(acct: MailAccountConfig) -> Result<()> {
|
||||||
// check if the authentication method is supported
|
// check if the authentication method is supported
|
||||||
let mut authed = match acct.imap.auth {
|
let mut authed = match acct.imap.auth {
|
||||||
ImapAuth::Plain { username, password } => {
|
ImapAuth::Plain { username, password } => {
|
||||||
let auth = auth::Plain {username, password};
|
let auth = auth::Plain { username, password };
|
||||||
auth.perform_auth(unauth).await?
|
auth.perform_auth(unauth).await?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
76
src/main.rs
76
src/main.rs
|
@ -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)
|
|
||||||
.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()?;
|
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[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;
|
||||||
});
|
});
|
||||||
|
|
||||||
let stdout = std::io::stdout();
|
if !opt.headless {
|
||||||
tokio::spawn(ui::run_ui(stdout, exit_tx).unwrap_or_else(report_err));
|
let stdout = std::io::stdout();
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue