updates
This commit is contained in:
parent
7153b30a43
commit
7cd69bd6a8
15 changed files with 169 additions and 53 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -212,7 +212,7 @@ dependencies = [
|
||||||
"git2",
|
"git2",
|
||||||
"git2-curl",
|
"git2-curl",
|
||||||
"glob",
|
"glob",
|
||||||
"hex 0.4.2",
|
"hex 0.4.3",
|
||||||
"home",
|
"home",
|
||||||
"humantime",
|
"humantime",
|
||||||
"ignore",
|
"ignore",
|
||||||
|
@ -949,9 +949,9 @@ checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35"
|
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "home"
|
name = "home"
|
||||||
|
@ -1425,6 +1425,7 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"assert_matches",
|
"assert_matches",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"bytes",
|
||||||
"derive_builder",
|
"derive_builder",
|
||||||
"futures",
|
"futures",
|
||||||
"log",
|
"log",
|
||||||
|
@ -1434,6 +1435,7 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-rustls",
|
"tokio-rustls",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
|
"tokio-util",
|
||||||
"webpki-roots",
|
"webpki-roots",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1481,15 +1483,14 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest"
|
name = "pest"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
source = "git+https://github.com/iptq/pest?rev=838adc941cc182e2c2d807e245a1e4c3e8594036#838adc941cc182e2c2d807e245a1e4c3e8594036"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"log",
|
||||||
"ucd-trie",
|
"ucd-trie",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_derive"
|
name = "pest_derive"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
source = "git+https://github.com/iptq/pest?rev=838adc941cc182e2c2d807e245a1e4c3e8594036#838adc941cc182e2c2d807e245a1e4c3e8594036"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_generator",
|
"pest_generator",
|
||||||
|
@ -1498,7 +1499,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_generator"
|
name = "pest_generator"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
source = "git+https://github.com/iptq/pest?rev=838adc941cc182e2c2d807e245a1e4c3e8594036#838adc941cc182e2c2d807e245a1e4c3e8594036"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
|
@ -1510,7 +1510,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pest_meta"
|
name = "pest_meta"
|
||||||
version = "2.1.3"
|
version = "2.1.3"
|
||||||
source = "git+https://github.com/iptq/pest?rev=838adc941cc182e2c2d807e245a1e4c3e8594036#838adc941cc182e2c2d807e245a1e4c3e8594036"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cargo",
|
"cargo",
|
||||||
"maplit",
|
"maplit",
|
||||||
|
|
|
@ -14,15 +14,19 @@ maintenance = { status = "passively-maintained" }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
async-trait = "0.1.42"
|
async-trait = "0.1.42"
|
||||||
|
bytes = { version = "1.0.1" }
|
||||||
derive_builder = "0.9.0"
|
derive_builder = "0.9.0"
|
||||||
futures = "0.3.12"
|
futures = "0.3.12"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
parking_lot = "0.11.1"
|
parking_lot = "0.11.1"
|
||||||
pest = { git = "https://github.com/iptq/pest", rev = "838adc941cc182e2c2d807e245a1e4c3e8594036" }
|
# pest = { path = "../../pest/pest" }
|
||||||
pest_derive = { git = "https://github.com/iptq/pest", rev = "838adc941cc182e2c2d807e245a1e4c3e8594036" }
|
# pest_derive = { path = "../../pest/derive" }
|
||||||
|
pest = { git = "https://github.com/iptq/pest", rev = "6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99" }
|
||||||
|
pest_derive = { git = "https://github.com/iptq/pest", rev = "6a4d3a3d10e42a3ee605ca979d0fcdac97a83a99" }
|
||||||
tokio = { version = "1.1.1", features = ["full"] }
|
tokio = { version = "1.1.1", features = ["full"] }
|
||||||
tokio-rustls = "0.22.0"
|
tokio-rustls = "0.22.0"
|
||||||
tokio-stream = "0.1.3"
|
tokio-stream = "0.1.3"
|
||||||
|
tokio-util = { version = "0.6.3" }
|
||||||
webpki-roots = "0.21.0"
|
webpki-roots = "0.21.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
|
|
@ -4,10 +4,9 @@ use std::task::{Context, Poll};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::{
|
use futures::{
|
||||||
future::{self, Either, FutureExt, TryFutureExt},
|
future::{self, FutureExt, TryFutureExt},
|
||||||
stream::{Peekable, Stream, StreamExt},
|
stream::{Stream, StreamExt},
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
|
||||||
use tokio::{
|
use tokio::{
|
||||||
io::{
|
io::{
|
||||||
self, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, ReadHalf, WriteHalf,
|
self, AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, ReadHalf, WriteHalf,
|
||||||
|
@ -21,8 +20,9 @@ use tokio::{
|
||||||
use tokio_rustls::{
|
use tokio_rustls::{
|
||||||
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
||||||
};
|
};
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
use tokio_util::codec::FramedRead;
|
||||||
|
|
||||||
|
use crate::codec::ImapCodec;
|
||||||
use crate::command::Command;
|
use crate::command::Command;
|
||||||
use crate::parser::{parse_capability, parse_response};
|
use crate::parser::{parse_capability, parse_response};
|
||||||
use crate::response::{Response, ResponseDone};
|
use crate::response::{Response, ResponseDone};
|
||||||
|
@ -189,16 +189,20 @@ async fn listen<C>(
|
||||||
where
|
where
|
||||||
C: AsyncRead + Unpin,
|
C: AsyncRead + Unpin,
|
||||||
{
|
{
|
||||||
let mut reader = BufReader::new(conn);
|
let codec = ImapCodec::default();
|
||||||
|
let mut framed = FramedRead::new(conn, codec);
|
||||||
|
// let mut reader = BufReader::new(conn);
|
||||||
let mut greeting_tx = Some(greeting_tx);
|
let mut greeting_tx = Some(greeting_tx);
|
||||||
let mut curr_cmd: Option<Command2> = None;
|
let mut curr_cmd: Option<Command2> = None;
|
||||||
let mut exit_rx = exit_rx.map_err(|_| ()).shared();
|
let mut exit_rx = exit_rx.map_err(|_| ()).shared();
|
||||||
// let mut exit_fut = Some(exit_rx.fuse());
|
// let mut exit_fut = Some(exit_rx.fuse());
|
||||||
// let mut fut1 = None;
|
// let mut fut1 = None;
|
||||||
|
let mut cache = String::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let mut next_line = String::new();
|
// let mut next_line = String::new();
|
||||||
let read_fut = reader.read_line(&mut next_line).fuse();
|
// let read_fut = reader.read_line(&mut next_line).fuse();
|
||||||
|
let read_fut = framed.next().fuse();
|
||||||
pin_mut!(read_fut);
|
pin_mut!(read_fut);
|
||||||
|
|
||||||
// only listen for a new command if there isn't one already
|
// only listen for a new command if there isn't one already
|
||||||
|
@ -222,10 +226,24 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
len = read_fut => {
|
resp = read_fut => {
|
||||||
trace!("read line {:?}", next_line);
|
// trace!("read line {:?}", next_line);
|
||||||
// res should not be None here
|
// res should not be None here
|
||||||
let resp = parse_response(next_line)?;
|
// cache += &next_line;
|
||||||
|
// let resp = match parse_response(&cache) {
|
||||||
|
// Ok(v) => {
|
||||||
|
// cache.clear();
|
||||||
|
// v
|
||||||
|
// }
|
||||||
|
// Err(e) => {
|
||||||
|
// error!("parse error: {}", e);
|
||||||
|
// continue;
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
let resp = match resp {
|
||||||
|
Some(Ok(v)) => v,
|
||||||
|
a => { error!("failed: {:?}", a); bail!("fuck"); },
|
||||||
|
};
|
||||||
|
|
||||||
// if this is the very first response, then it's a greeting
|
// if this is the very first response, then it's a greeting
|
||||||
if let Some(greeting_tx) = greeting_tx.take() {
|
if let Some(greeting_tx) = greeting_tx.take() {
|
||||||
|
@ -244,6 +262,6 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let conn = reader.into_inner();
|
let conn = framed.into_inner();
|
||||||
Ok(conn)
|
Ok(conn)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,10 +43,9 @@ use tokio::net::TcpStream;
|
||||||
use tokio_rustls::{
|
use tokio_rustls::{
|
||||||
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
client::TlsStream, rustls::ClientConfig as RustlsConfig, webpki::DNSNameRef, TlsConnector,
|
||||||
};
|
};
|
||||||
use tokio_stream::wrappers::UnboundedReceiverStream;
|
|
||||||
|
|
||||||
use crate::command::{Command, FetchItems, SearchCriteria};
|
use crate::command::{Command, FetchItems, SearchCriteria};
|
||||||
use crate::response::{MailboxData, Response, ResponseData, ResponseDone};
|
use crate::response::{AttributeValue, MailboxData, Response, ResponseData, ResponseDone};
|
||||||
|
|
||||||
pub use self::inner::{Client, ResponseStream};
|
pub use self::inner::{Client, ResponseStream};
|
||||||
|
|
||||||
|
@ -200,7 +199,7 @@ impl ClientAuthenticated {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the UID FETCH command
|
/// Runs the UID FETCH command
|
||||||
pub async fn uid_fetch(&mut self, uids: &[u32]) -> Result<()> {
|
pub async fn uid_fetch(&mut self, uids: &[u32]) -> Result<Vec<(u32, Vec<AttributeValue>)>> {
|
||||||
let cmd = Command::UidFetch {
|
let cmd = Command::UidFetch {
|
||||||
uids: uids.to_vec(),
|
uids: uids.to_vec(),
|
||||||
items: FetchItems::All,
|
items: FetchItems::All,
|
||||||
|
@ -208,11 +207,13 @@ impl ClientAuthenticated {
|
||||||
debug!("uid fetch: {}", cmd);
|
debug!("uid fetch: {}", cmd);
|
||||||
let stream = self.execute(cmd).await?;
|
let stream = self.execute(cmd).await?;
|
||||||
let (done, data) = stream.wait().await?;
|
let (done, data) = stream.wait().await?;
|
||||||
debug!("done: {:?} {:?}", done, data);
|
Ok(data
|
||||||
for resp in data {
|
.into_iter()
|
||||||
debug!("uid fetch: {:?}", resp);
|
.filter_map(|resp| match resp {
|
||||||
}
|
Response::Fetch(n, attrs) => Some((n, attrs)),
|
||||||
todo!()
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs the IDLE command
|
/// Runs the IDLE command
|
||||||
|
|
30
imap/src/codec.rs
Normal file
30
imap/src/codec.rs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
use bytes::{Buf, BytesMut};
|
||||||
|
use tokio_util::codec::Decoder;
|
||||||
|
|
||||||
|
use crate::parser::parse_streamed_response;
|
||||||
|
use crate::response::Response;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ImapCodec;
|
||||||
|
|
||||||
|
impl Decoder for ImapCodec {
|
||||||
|
type Item = Response;
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
|
||||||
|
let s = std::str::from_utf8(src)?;
|
||||||
|
debug!("-------------------------");
|
||||||
|
debug!("s: {:?}", s);
|
||||||
|
match parse_streamed_response(s) {
|
||||||
|
Ok((resp, len)) => {
|
||||||
|
src.advance(len);
|
||||||
|
return Ok(Some(resp));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("failed to parse: {:?} {}", e, e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,7 @@ extern crate log;
|
||||||
extern crate pest_derive;
|
extern crate pest_derive;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
|
pub mod codec;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
pub mod parser;
|
pub mod parser;
|
||||||
pub mod response;
|
pub mod response;
|
||||||
|
|
|
@ -1,31 +1,27 @@
|
||||||
use pest::{ParseResult as PestResult, ParserState};
|
use pest::{iterators::QueueableToken, ParseResult as PestResult, ParserState};
|
||||||
|
|
||||||
use super::Rule;
|
use super::Rule;
|
||||||
|
|
||||||
type PSR<'a> = Box<ParserState<'a, Rule>>;
|
type PSR<'a> = Box<ParserState<'a, Rule>>;
|
||||||
|
|
||||||
/// This is a hack around the literal syntax to allow us to parse characters statefully.
|
/// This is a hack around the literal syntax to allow us to parse characters statefully.
|
||||||
pub(crate) fn literal_internal(state: PSR) -> PestResult<PSR> {
|
pub(crate) fn literal_internal(mut state: PSR) -> PestResult<PSR> {
|
||||||
|
// debug!("STATE: {:?}", state);
|
||||||
use pest::Atomicity;
|
use pest::Atomicity;
|
||||||
|
|
||||||
// yoinked from the generated code
|
// yoinked from the generated code
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(non_snake_case, unused_variables)]
|
#[allow(non_snake_case, unused_variables)]
|
||||||
pub fn digit(state: PSR) -> PestResult<PSR> {
|
pub fn digit(state: PSR) -> PestResult<PSR> {
|
||||||
state.rule(Rule::digit, |state| {
|
state.match_range('\u{30}'..'\u{39}')
|
||||||
state.atomic(Atomicity::Atomic, |state| {
|
|
||||||
state.match_range('\u{30}'..'\u{39}')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(non_snake_case, unused_variables)]
|
#[allow(non_snake_case, unused_variables)]
|
||||||
pub fn number(state: PSR) -> PestResult<PSR> {
|
pub fn number(state: PSR) -> PestResult<PSR> {
|
||||||
state.rule(Rule::number, |state| {
|
state.rule(Rule::number, |state| {
|
||||||
state.atomic(Atomicity::Atomic, |state| {
|
state.sequence(|state| {
|
||||||
state.sequence(|state| {
|
debug!("number::atomic::sequence");
|
||||||
digit(state).and_then(|state| state.repeat(|state| digit(state)))
|
digit(state).and_then(|state| state.repeat(digit))
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -41,18 +37,49 @@ pub(crate) fn literal_internal(state: PSR) -> PestResult<PSR> {
|
||||||
#[inline]
|
#[inline]
|
||||||
#[allow(non_snake_case, unused_variables)]
|
#[allow(non_snake_case, unused_variables)]
|
||||||
pub fn crlf(state: PSR) -> PestResult<PSR> {
|
pub fn crlf(state: PSR) -> PestResult<PSR> {
|
||||||
|
debug!(
|
||||||
|
"running rule 'crlf' {:?}",
|
||||||
|
state.queue().iter().rev().take(10).collect::<Vec<_>>()
|
||||||
|
);
|
||||||
state.sequence(|state| state.match_string("\r")?.match_string("\n"))
|
state.sequence(|state| state.match_string("\r")?.match_string("\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
let state = state.match_string("{").and_then(number)?;
|
let state: PSR = state.match_string("{").and_then(number)?;
|
||||||
let num_chars = {
|
let num_chars = {
|
||||||
let mut queue = state.queue().iter().rev();
|
let queue = state.queue();
|
||||||
println!("QUEUE: {:?}", queue);
|
let (start_idx, end_pos) = queue
|
||||||
let end = queue.next().unwrap();
|
.iter()
|
||||||
let start = queue.next().unwrap();
|
.rev()
|
||||||
|
.find_map(|p| match p {
|
||||||
|
QueueableToken::End {
|
||||||
|
start_token_index: start,
|
||||||
|
rule: Rule::number,
|
||||||
|
input_pos: pos,
|
||||||
|
} => Some((*start, *pos)),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let start_pos = match queue[start_idx] {
|
||||||
|
QueueableToken::Start { input_pos: pos, .. } => pos,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
debug!("start_pos: {}, end_pos: {}", start_pos, end_pos);
|
||||||
|
|
||||||
let inp = state.position().get_str();
|
let inp = state.position().get_str();
|
||||||
let seg = &inp[start.input_pos()..end.input_pos()];
|
let seg = &inp[start_pos..end_pos];
|
||||||
seg.parse::<usize>().unwrap()
|
match seg.parse::<usize>() {
|
||||||
|
Ok(v) => {
|
||||||
|
debug!("got length: {}", v);
|
||||||
|
v
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!(
|
||||||
|
"failed to parse int from {}..{} {:?}: {}",
|
||||||
|
start_pos, end_pos, seg, e
|
||||||
|
);
|
||||||
|
return Err(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
state
|
state
|
||||||
|
|
|
@ -25,6 +25,15 @@ pub fn parse_capability(s: impl AsRef<str>) -> ParseResult<Capability> {
|
||||||
Ok(build_capability(pair))
|
Ok(build_capability(pair))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_streamed_response(s: impl AsRef<str>) -> ParseResult<(Response, usize)> {
|
||||||
|
let mut pairs = Rfc3501::parse(Rule::streamed_response, s.as_ref())?;
|
||||||
|
let pair = unwrap1(pairs.next().unwrap());
|
||||||
|
let span = pair.as_span();
|
||||||
|
let range = span.end() - span.start();
|
||||||
|
let response = build_response(pair);
|
||||||
|
Ok((response, range))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn parse_response(s: impl AsRef<str>) -> ParseResult<Response> {
|
pub fn parse_response(s: impl AsRef<str>) -> ParseResult<Response> {
|
||||||
let mut pairs = Rfc3501::parse(Rule::response, s.as_ref())?;
|
let mut pairs = Rfc3501::parse(Rule::response, s.as_ref())?;
|
||||||
let pair = pairs.next().unwrap();
|
let pair = pairs.next().unwrap();
|
||||||
|
@ -386,6 +395,7 @@ fn build_nstring(pair: Pair<Rule>) -> Option<String> {
|
||||||
if matches!(pair.as_rule(), Rule::nil) {
|
if matches!(pair.as_rule(), Rule::nil) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(build_string(pair))
|
Some(build_string(pair))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
streamed_response = { response ~ ANY* }
|
||||||
|
|
||||||
// formal syntax from https://tools.ietf.org/html/rfc3501#section-9
|
// formal syntax from https://tools.ietf.org/html/rfc3501#section-9
|
||||||
addr_adl = { nstring }
|
addr_adl = { nstring }
|
||||||
addr_host = { nstring }
|
addr_host = { nstring }
|
||||||
|
@ -93,7 +95,7 @@ msg_att_static_internaldate = { ^"INTERNALDATE" ~ sp ~ date_time }
|
||||||
msg_att_static_rfc822_size = { ^"RFC822.SIZE" ~ sp ~ number }
|
msg_att_static_rfc822_size = { ^"RFC822.SIZE" ~ sp ~ number }
|
||||||
msg_att_static_uid = { ^"UID" ~ sp ~ uniqueid }
|
msg_att_static_uid = { ^"UID" ~ sp ~ uniqueid }
|
||||||
nil = { ^"NIL" }
|
nil = { ^"NIL" }
|
||||||
nstring = { string | nil }
|
nstring = { nil | string }
|
||||||
number = @{ digit{1,} }
|
number = @{ digit{1,} }
|
||||||
nz_number = @{ digit_nz ~ digit* }
|
nz_number = @{ digit_nz ~ digit* }
|
||||||
quoted = @{ dquote ~ quoted_char* ~ dquote }
|
quoted = @{ dquote ~ quoted_char* ~ dquote }
|
||||||
|
@ -124,7 +126,7 @@ section_spec = { section_msgtext | (section_part ~ ("." ~ section_text)?) }
|
||||||
section_text = { section_msgtext | "MIME" }
|
section_text = { section_msgtext | "MIME" }
|
||||||
status_att = { ^"MESSAGES" | ^"RECENT" | ^"UIDNEXT" | ^"UIDVALIDITY" | ^"UNSEEN" }
|
status_att = { ^"MESSAGES" | ^"RECENT" | ^"UIDNEXT" | ^"UIDVALIDITY" | ^"UNSEEN" }
|
||||||
status_att_list = { status_att ~ sp ~ number ~ (sp ~ status_att ~ sp ~ number)* }
|
status_att_list = { status_att ~ sp ~ number ~ (sp ~ status_att ~ sp ~ number)* }
|
||||||
string = @{ quoted | literal }
|
string = ${ quoted | literal }
|
||||||
tag = @{ tag_char{1,} }
|
tag = @{ tag_char{1,} }
|
||||||
tag_char = @{ !"+" ~ astring_char }
|
tag_char = @{ !"+" ~ astring_char }
|
||||||
text = @{ text_char{1,} }
|
text = @{ text_char{1,} }
|
||||||
|
|
|
@ -35,6 +35,9 @@ pub enum MailCommand {
|
||||||
pub enum MailEvent {
|
pub enum MailEvent {
|
||||||
/// Got the list of folders
|
/// Got the list of folders
|
||||||
FolderList(Vec<String>),
|
FolderList(Vec<String>),
|
||||||
|
|
||||||
|
/// Got the current list of messages
|
||||||
|
MessageList(Vec<String>),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Main entrypoint for the mail listener.
|
/// Main entrypoint for the mail listener.
|
||||||
|
@ -139,8 +142,13 @@ async fn imap_main(acct: MailAccountConfig, mail2ui_tx: UnboundedSender<MailEven
|
||||||
debug!("mailbox list: {:?}", folder_list);
|
debug!("mailbox list: {:?}", folder_list);
|
||||||
let _ = mail2ui_tx.send(MailEvent::FolderList(folder_list));
|
let _ = mail2ui_tx.send(MailEvent::FolderList(folder_list));
|
||||||
|
|
||||||
let message_list = authed.uid_search().await?;
|
let message_uids = authed.uid_search().await?;
|
||||||
authed.uid_fetch(&message_list).await?;
|
let message_list = authed.uid_fetch(&message_uids).await?;
|
||||||
|
let mut messages = Vec::new();
|
||||||
|
for (_, attrs) in message_list {
|
||||||
|
messages.push(format!("{:?}", attrs));
|
||||||
|
}
|
||||||
|
let _ = mail2ui_tx.send(MailEvent::MessageList(messages));
|
||||||
|
|
||||||
let mut idle_stream = authed.idle().await?;
|
let mut idle_stream = authed.idle().await?;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use std::thread;
|
||||||
|
|
||||||
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::{
|
use panorama::{
|
||||||
config::spawn_config_watcher_system,
|
config::spawn_config_watcher_system,
|
||||||
mail::{self, MailEvent},
|
mail::{self, MailEvent},
|
||||||
|
@ -109,6 +109,7 @@ fn setup_logger(log_file: Option<impl AsRef<Path>>) -> Result<()> {
|
||||||
.warn(Color::Yellow)
|
.warn(Color::Yellow)
|
||||||
.error(Color::Red);
|
.error(Color::Red);
|
||||||
let mut logger = fern::Dispatch::new()
|
let mut logger = fern::Dispatch::new()
|
||||||
|
.filter(|meta| meta.target() != "tokio_util::codec::framed_impl")
|
||||||
.format(move |out, message, record| {
|
.format(move |out, message, record| {
|
||||||
out.finish(format_args!(
|
out.finish(format_args!(
|
||||||
"{}[{}][{}] {}",
|
"{}[{}][{}] {}",
|
||||||
|
|
|
@ -2,12 +2,13 @@ use tui::{
|
||||||
buffer::Buffer,
|
buffer::Buffer,
|
||||||
layout::{Constraint, Direction, Layout, Rect},
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
style::{Color, Modifier, Style},
|
style::{Color, Modifier, Style},
|
||||||
|
text::{Span, Spans},
|
||||||
widgets::*,
|
widgets::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::FrameType;
|
use super::FrameType;
|
||||||
|
|
||||||
pub fn render_mail_tab(f: &mut FrameType, area: Rect, folders: &[String]) {
|
pub fn render_mail_tab(f: &mut FrameType, area: Rect, folders: &[String], messages: &[String]) {
|
||||||
let chunks = Layout::default()
|
let chunks = Layout::default()
|
||||||
.direction(Direction::Horizontal)
|
.direction(Direction::Horizontal)
|
||||||
.margin(0)
|
.margin(0)
|
||||||
|
@ -25,5 +26,15 @@ pub fn render_mail_tab(f: &mut FrameType, area: Rect, folders: &[String]) {
|
||||||
.highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
.highlight_style(Style::default().add_modifier(Modifier::ITALIC))
|
||||||
.highlight_symbol(">>");
|
.highlight_symbol(">>");
|
||||||
|
|
||||||
|
let rows = messages
|
||||||
|
.iter()
|
||||||
|
.map(|s| Row::new(vec![s.as_ref()]))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
let table = Table::new(rows)
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.widths(&[Constraint::Max(5000)])
|
||||||
|
.highlight_style(Style::default().add_modifier(Modifier::BOLD));
|
||||||
|
|
||||||
f.render_widget(dirlist, chunks[0]);
|
f.render_widget(dirlist, chunks[0]);
|
||||||
|
f.render_widget(table, chunks[1]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ pub async fn run_ui(
|
||||||
let mut term = Terminal::new(backend)?;
|
let mut term = Terminal::new(backend)?;
|
||||||
|
|
||||||
let mut folders = Vec::<String>::new();
|
let mut folders = Vec::<String>::new();
|
||||||
|
let mut messages = Vec::<String>::new();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
term.draw(|f| {
|
term.draw(|f| {
|
||||||
|
@ -58,7 +59,7 @@ pub async fn run_ui(
|
||||||
let tabs = Tabs::new(titles);
|
let tabs = Tabs::new(titles);
|
||||||
f.render_widget(tabs, chunks[0]);
|
f.render_widget(tabs, chunks[0]);
|
||||||
|
|
||||||
render_mail_tab(f, chunks[1], &folders);
|
render_mail_tab(f, chunks[1], &folders, &messages);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let event = if event::poll(FRAME_DURATION)? {
|
let event = if event::poll(FRAME_DURATION)? {
|
||||||
|
@ -88,6 +89,9 @@ pub async fn run_ui(
|
||||||
MailEvent::FolderList(new_folders) => {
|
MailEvent::FolderList(new_folders) => {
|
||||||
folders = new_folders;
|
folders = new_folders;
|
||||||
}
|
}
|
||||||
|
MailEvent::MessageList(new_messages) => {
|
||||||
|
messages = new_messages;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue