imap: add watch
parent
c3e3c98fb0
commit
335a1011de
|
@ -28,6 +28,8 @@ mod operations;
|
||||||
pub use operations::*;
|
pub use operations::*;
|
||||||
mod connection;
|
mod connection;
|
||||||
pub use connection::*;
|
pub use connection::*;
|
||||||
|
mod watch;
|
||||||
|
pub use watch::*;
|
||||||
|
|
||||||
extern crate native_tls;
|
extern crate native_tls;
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@ use crate::backends::RefreshEvent;
|
||||||
use crate::backends::RefreshEventKind::{self, *};
|
use crate::backends::RefreshEventKind::{self, *};
|
||||||
use crate::backends::{BackendFolder, Folder, FolderOperation, MailBackend, RefreshEventConsumer};
|
use crate::backends::{BackendFolder, Folder, FolderOperation, MailBackend, RefreshEventConsumer};
|
||||||
use crate::conf::AccountSettings;
|
use crate::conf::AccountSettings;
|
||||||
|
use crate::email::parser::BytesExt;
|
||||||
use crate::email::*;
|
use crate::email::*;
|
||||||
use crate::error::{MeliError, Result};
|
use crate::error::{MeliError, Result};
|
||||||
use fnv::{FnvHashMap, FnvHashSet};
|
use fnv::{FnvHashMap, FnvHashSet};
|
||||||
|
@ -82,22 +85,21 @@ impl MailBackend for ImapType {
|
||||||
let uid_index = self.uid_index.clone();
|
let uid_index = self.uid_index.clone();
|
||||||
let folder_path = folder.path().to_string();
|
let folder_path = folder.path().to_string();
|
||||||
let folder_hash = folder.hash();
|
let folder_hash = folder.hash();
|
||||||
let connection = self.folder_connections[&folder_hash].clone();
|
let folder_exists = self.folders[&folder_hash].exists.clone();
|
||||||
|
let connection = self.connection.clone();
|
||||||
let closure = move || {
|
let closure = move || {
|
||||||
let connection = connection.clone();
|
let connection = connection.clone();
|
||||||
let tx = tx.clone();
|
let tx = tx.clone();
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
{
|
let conn = connection.lock();
|
||||||
let conn = connection.lock();
|
exit_on_error!(&tx, conn);
|
||||||
exit_on_error!(&tx, conn);
|
let mut conn = conn.unwrap();
|
||||||
let mut conn = conn.unwrap();
|
debug!("locked for get {}", folder_path);
|
||||||
|
|
||||||
debug!("locked for get {}", folder_path);
|
exit_on_error!(&tx,
|
||||||
exit_on_error!(&tx,
|
conn.send_command(format!("EXAMINE {}", folder_path).as_bytes())
|
||||||
conn.send_command(format!("EXAMINE {}", folder_path).as_bytes())
|
conn.read_response(&mut response)
|
||||||
conn.read_response(&mut response)
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
let examine_response = protocol_parser::select_response(&response)
|
let examine_response = protocol_parser::select_response(&response)
|
||||||
.to_full_result()
|
.to_full_result()
|
||||||
.map_err(MeliError::from);
|
.map_err(MeliError::from);
|
||||||
|
@ -106,18 +108,17 @@ impl MailBackend for ImapType {
|
||||||
SelectResponse::Ok(ok) => ok.exists,
|
SelectResponse::Ok(ok) => ok.exists,
|
||||||
SelectResponse::Bad(b) => b.exists,
|
SelectResponse::Bad(b) => b.exists,
|
||||||
};
|
};
|
||||||
|
{
|
||||||
|
let mut folder_exists = folder_exists.lock().unwrap();
|
||||||
|
*folder_exists = exists;
|
||||||
|
}
|
||||||
|
|
||||||
while exists > 1 {
|
while exists > 1 {
|
||||||
let mut envelopes = vec![];
|
let mut envelopes = vec![];
|
||||||
{
|
exit_on_error!(&tx,
|
||||||
let conn = connection.lock();
|
conn.send_command(format!("UID FETCH {}:{} (FLAGS RFC822.HEADER)", std::cmp::max(exists.saturating_sub(20000), 1), exists).as_bytes())
|
||||||
exit_on_error!(&tx, conn);
|
conn.read_response(&mut response)
|
||||||
let mut conn = conn.unwrap();
|
);
|
||||||
exit_on_error!(&tx,
|
|
||||||
conn.send_command(format!("UID FETCH {}:{} (FLAGS RFC822.HEADER)", std::cmp::max(exists.saturating_sub(10000), 1), exists).as_bytes())
|
|
||||||
conn.read_response(&mut response)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
debug!(
|
debug!(
|
||||||
"fetch response is {} bytes and {} lines",
|
"fetch response is {} bytes and {} lines",
|
||||||
response.len(),
|
response.len(),
|
||||||
|
@ -145,10 +146,11 @@ impl MailBackend for ImapType {
|
||||||
tx.send(AsyncStatus::Payload(Err(e)));
|
tx.send(AsyncStatus::Payload(Err(e)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
exists = std::cmp::max(exists.saturating_sub(10000), 1);
|
exists = std::cmp::max(exists.saturating_sub(20000), 1);
|
||||||
debug!("sending payload");
|
debug!("sending payload");
|
||||||
tx.send(AsyncStatus::Payload(Ok(envelopes)));
|
tx.send(AsyncStatus::Payload(Ok(envelopes)));
|
||||||
}
|
}
|
||||||
|
drop(conn);
|
||||||
tx.send(AsyncStatus::Finished);
|
tx.send(AsyncStatus::Finished);
|
||||||
};
|
};
|
||||||
Box::new(closure)
|
Box::new(closure)
|
||||||
|
@ -157,286 +159,29 @@ impl MailBackend for ImapType {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
|
fn watch(&self, sender: RefreshEventConsumer) -> Result<()> {
|
||||||
macro_rules! exit_on_error {
|
|
||||||
($sender:expr, $folder_hash:ident, $($result:expr)+) => {
|
|
||||||
$(if let Err(e) = $result {
|
|
||||||
debug!("failure: {}", e.to_string());
|
|
||||||
$sender.send(RefreshEvent {
|
|
||||||
hash: $folder_hash,
|
|
||||||
kind: RefreshEventKind::Failure(e),
|
|
||||||
});
|
|
||||||
std::process::exit(1);
|
|
||||||
})+
|
|
||||||
};
|
|
||||||
};
|
|
||||||
let has_idle: bool = self.capabilities.contains(&b"IDLE"[0..]);
|
let has_idle: bool = self.capabilities.contains(&b"IDLE"[0..]);
|
||||||
let sender = Arc::new(sender);
|
let folders = self.imap_folders();
|
||||||
for f in self.folders.values() {
|
let conn = self.new_connection()?;
|
||||||
let mut conn = self.new_connection()?;
|
let main_conn = self.connection.clone();
|
||||||
let main_conn = self.connection.clone();
|
let hash_index = self.hash_index.clone();
|
||||||
let f_path = f.path().to_string();
|
let uid_index = self.uid_index.clone();
|
||||||
let hash_index = self.hash_index.clone();
|
std::thread::Builder::new()
|
||||||
let uid_index = self.uid_index.clone();
|
.name(format!("{} imap connection", self.account_name.as_str(),))
|
||||||
let folder_hash = f.hash();
|
.spawn(move || {
|
||||||
let sender = sender.clone();
|
let kit = ImapWatchKit {
|
||||||
std::thread::Builder::new()
|
conn,
|
||||||
.name(format!(
|
main_conn,
|
||||||
"{},{}: imap connection",
|
hash_index,
|
||||||
self.account_name.as_str(),
|
uid_index,
|
||||||
f_path.as_str()
|
folders,
|
||||||
))
|
sender,
|
||||||
.spawn(move || {
|
};
|
||||||
let mut response = String::with_capacity(8 * 1024);
|
if has_idle {
|
||||||
exit_on_error!(
|
idle(kit);
|
||||||
sender.as_ref(),
|
} else {
|
||||||
folder_hash,
|
poll_with_examine(kit);
|
||||||
conn.read_response(&mut response)
|
}
|
||||||
conn.send_command(format!("SELECT {}", f_path).as_bytes())
|
})?;
|
||||||
conn.read_response(&mut response)
|
|
||||||
);
|
|
||||||
debug!("select response {}", &response);
|
|
||||||
let mut prev_exists = match protocol_parser::select_response(&response)
|
|
||||||
.to_full_result()
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(SelectResponse::Bad(bad)) => {
|
|
||||||
debug!(bad);
|
|
||||||
panic!("could not select mailbox");
|
|
||||||
}
|
|
||||||
Ok(SelectResponse::Ok(ok)) => {
|
|
||||||
debug!(&ok);
|
|
||||||
ok.exists
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!("{:?}", e);
|
|
||||||
panic!("could not select mailbox");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if has_idle {
|
|
||||||
exit_on_error!(sender.as_ref(), folder_hash, conn.send_command(b"IDLE"));
|
|
||||||
let mut iter = ImapBlockingConnection::from(conn);
|
|
||||||
let mut beat = std::time::Instant::now();
|
|
||||||
let _26_mins = std::time::Duration::from_secs(26 * 60);
|
|
||||||
while let Some(line) = iter.next() {
|
|
||||||
let now = std::time::Instant::now();
|
|
||||||
if now.duration_since(beat) >= _26_mins {
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
iter.conn.set_nonblocking(true)
|
|
||||||
iter.conn.send_raw(b"DONE")
|
|
||||||
iter.conn.read_response(&mut response)
|
|
||||||
);
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
iter.conn.send_command(b"IDLE")
|
|
||||||
iter.conn.set_nonblocking(false)
|
|
||||||
);
|
|
||||||
{
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
main_conn.lock().unwrap().send_command(b"NOOP")
|
|
||||||
main_conn.lock().unwrap().read_response(&mut response)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
beat = now;
|
|
||||||
}
|
|
||||||
match protocol_parser::untagged_responses(line.as_slice())
|
|
||||||
.to_full_result()
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(Some(Recent(_))) => {
|
|
||||||
/* UID SEARCH RECENT */
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
iter.conn.set_nonblocking(true)
|
|
||||||
iter.conn.send_raw(b"DONE")
|
|
||||||
iter.conn.read_response(&mut response)
|
|
||||||
iter.conn.send_command(b"UID SEARCH RECENT")
|
|
||||||
iter.conn.read_response(&mut response)
|
|
||||||
);
|
|
||||||
match protocol_parser::search_results_raw(response.as_bytes())
|
|
||||||
.to_full_result()
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(&[]) => {
|
|
||||||
debug!("UID SEARCH RECENT returned no results");
|
|
||||||
}
|
|
||||||
Ok(v) => {
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
iter.conn.send_command(
|
|
||||||
&[b"UID FETCH", v, b"(FLAGS RFC822.HEADER)"]
|
|
||||||
.join(&b' '),
|
|
||||||
)
|
|
||||||
iter.conn.read_response(&mut response)
|
|
||||||
);
|
|
||||||
debug!(&response);
|
|
||||||
match protocol_parser::uid_fetch_response(
|
|
||||||
response.as_bytes(),
|
|
||||||
)
|
|
||||||
.to_full_result()
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(v) => {
|
|
||||||
for (uid, flags, b) in v {
|
|
||||||
if let Ok(env) =
|
|
||||||
Envelope::from_bytes(&b, flags)
|
|
||||||
{
|
|
||||||
hash_index.lock().unwrap().insert(
|
|
||||||
env.hash(),
|
|
||||||
(uid, folder_hash),
|
|
||||||
);
|
|
||||||
uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(uid, env.hash());
|
|
||||||
debug!(
|
|
||||||
"Create event {} {} {}",
|
|
||||||
env.hash(),
|
|
||||||
env.subject(),
|
|
||||||
f_path.as_str()
|
|
||||||
);
|
|
||||||
sender.send(RefreshEvent {
|
|
||||||
hash: folder_hash,
|
|
||||||
kind: Create(Box::new(env)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(
|
|
||||||
"UID SEARCH RECENT err: {}\nresp: {}",
|
|
||||||
e.to_string(),
|
|
||||||
&response
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
iter.conn.send_command(b"IDLE")
|
|
||||||
iter.conn.set_nonblocking(false)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(Some(Expunge(n))) => {
|
|
||||||
debug!("expunge {}", n);
|
|
||||||
}
|
|
||||||
Ok(Some(Exists(n))) => {
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
iter.conn.set_nonblocking(true)
|
|
||||||
iter.conn.send_raw(b"DONE")
|
|
||||||
iter.conn.read_response(&mut response)
|
|
||||||
);
|
|
||||||
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
|
||||||
* */
|
|
||||||
debug!("exists {}", n);
|
|
||||||
if n > prev_exists {
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
iter.conn.send_command(
|
|
||||||
&[
|
|
||||||
b"FETCH",
|
|
||||||
format!("{}:{}", prev_exists + 1, n).as_bytes(),
|
|
||||||
b"(UID FLAGS RFC822.HEADER)",
|
|
||||||
]
|
|
||||||
.join(&b' '),
|
|
||||||
)
|
|
||||||
iter.conn.read_response(&mut response)
|
|
||||||
);
|
|
||||||
match protocol_parser::uid_fetch_response(
|
|
||||||
response.as_bytes(),
|
|
||||||
)
|
|
||||||
.to_full_result()
|
|
||||||
.map_err(MeliError::from)
|
|
||||||
{
|
|
||||||
Ok(v) => {
|
|
||||||
for (uid, flags, b) in v {
|
|
||||||
if let Ok(env) = Envelope::from_bytes(&b, flags)
|
|
||||||
{
|
|
||||||
hash_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(env.hash(), (uid, folder_hash));
|
|
||||||
uid_index
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert(uid, env.hash());
|
|
||||||
debug!(
|
|
||||||
"Create event {} {} {}",
|
|
||||||
env.hash(),
|
|
||||||
env.subject(),
|
|
||||||
f_path.as_str()
|
|
||||||
);
|
|
||||||
sender.send(RefreshEvent {
|
|
||||||
hash: folder_hash,
|
|
||||||
kind: Create(Box::new(env)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
debug!(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_exists = n;
|
|
||||||
} else if n < prev_exists {
|
|
||||||
prev_exists = n;
|
|
||||||
}
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
iter.conn.send_command(b"IDLE")
|
|
||||||
iter.conn.set_nonblocking(false)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Ok(None) | Err(_) => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
debug!("failure");
|
|
||||||
sender.send(RefreshEvent {
|
|
||||||
hash: folder_hash,
|
|
||||||
kind: RefreshEventKind::Failure(MeliError::new("conn_error")),
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
loop {
|
|
||||||
{
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
main_conn.lock().unwrap().send_command(b"NOOP")
|
|
||||||
main_conn.lock().unwrap().read_response(&mut response)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
exit_on_error!(
|
|
||||||
sender.as_ref(),
|
|
||||||
folder_hash,
|
|
||||||
conn.send_command(b"NOOP")
|
|
||||||
conn.read_response(&mut response)
|
|
||||||
);
|
|
||||||
for r in response.lines() {
|
|
||||||
// FIXME mimic IDLE
|
|
||||||
debug!(&r);
|
|
||||||
}
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(10 * 1000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use crate::backends::{BackendFolder, Folder, FolderHash};
|
use crate::backends::{BackendFolder, Folder, FolderHash};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ImapFolder {
|
pub struct ImapFolder {
|
||||||
|
@ -27,6 +28,8 @@ pub struct ImapFolder {
|
||||||
pub(super) name: String,
|
pub(super) name: String,
|
||||||
pub(super) parent: Option<FolderHash>,
|
pub(super) parent: Option<FolderHash>,
|
||||||
pub(super) children: Vec<FolderHash>,
|
pub(super) children: Vec<FolderHash>,
|
||||||
|
|
||||||
|
pub exists: Arc<Mutex<usize>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BackendFolder for ImapFolder {
|
impl BackendFolder for ImapFolder {
|
||||||
|
@ -57,6 +60,7 @@ impl BackendFolder for ImapFolder {
|
||||||
name: self.name.clone(),
|
name: self.name.clone(),
|
||||||
parent: self.parent,
|
parent: self.parent,
|
||||||
children: self.children.clone(),
|
children: self.children.clone(),
|
||||||
|
exists: self.exists.clone(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,479 @@
|
||||||
|
/*
|
||||||
|
* meli - imap module.
|
||||||
|
*
|
||||||
|
* Copyright 2019 Manos Pitsidianakis
|
||||||
|
*
|
||||||
|
* This file is part of meli.
|
||||||
|
*
|
||||||
|
* meli is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* meli is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
use super::*;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
/// Arguments for IMAP watching functions
|
||||||
|
pub struct ImapWatchKit {
|
||||||
|
pub conn: ImapConnection,
|
||||||
|
pub main_conn: Arc<Mutex<ImapConnection>>,
|
||||||
|
pub hash_index: Arc<Mutex<FnvHashMap<EnvelopeHash, (UID, FolderHash)>>>,
|
||||||
|
pub uid_index: Arc<Mutex<FnvHashMap<usize, EnvelopeHash>>>,
|
||||||
|
pub folders: FnvHashMap<FolderHash, ImapFolder>,
|
||||||
|
pub sender: RefreshEventConsumer,
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! exit_on_error {
|
||||||
|
($sender:expr, $folder_hash:ident, $($result:expr)+) => {
|
||||||
|
$(if let Err(e) = $result {
|
||||||
|
debug!("failure: {}", e.to_string());
|
||||||
|
$sender.send(RefreshEvent {
|
||||||
|
hash: $folder_hash,
|
||||||
|
kind: RefreshEventKind::Failure(e),
|
||||||
|
});
|
||||||
|
std::process::exit(1);
|
||||||
|
})+
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll_with_examine(kit: ImapWatchKit) {
|
||||||
|
debug!("poll with examine");
|
||||||
|
let ImapWatchKit {
|
||||||
|
mut conn,
|
||||||
|
main_conn,
|
||||||
|
hash_index,
|
||||||
|
uid_index,
|
||||||
|
folders,
|
||||||
|
sender,
|
||||||
|
} = kit;
|
||||||
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(std::time::Duration::from_millis(5 * 60 * 1000));
|
||||||
|
for (hash, folder) in &folders {
|
||||||
|
examine_updates(folder, &sender, &mut conn, &hash_index, &uid_index);
|
||||||
|
}
|
||||||
|
let mut main_conn = main_conn.lock().unwrap();
|
||||||
|
main_conn.send_command(b"NOOP").unwrap();
|
||||||
|
main_conn.read_response(&mut response).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn idle(kit: ImapWatchKit) {
|
||||||
|
debug!("IDLE");
|
||||||
|
/* IDLE only watches the connection's selected mailbox. We will IDLE on INBOX and every ~5
|
||||||
|
* minutes wake up and poll the others */
|
||||||
|
let ImapWatchKit {
|
||||||
|
mut conn,
|
||||||
|
main_conn,
|
||||||
|
hash_index,
|
||||||
|
uid_index,
|
||||||
|
folders,
|
||||||
|
sender,
|
||||||
|
} = kit;
|
||||||
|
let folder: &ImapFolder = folders
|
||||||
|
.values()
|
||||||
|
.find(|f| f.parent.is_none() && f.path().eq_ignore_ascii_case("INBOX"))
|
||||||
|
.unwrap();
|
||||||
|
let folder_hash = folder.hash();
|
||||||
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
conn.read_response(&mut response)
|
||||||
|
conn.send_command(format!("SELECT {}", folder.path()).as_bytes())
|
||||||
|
conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
debug!("select response {}", &response);
|
||||||
|
{
|
||||||
|
let mut prev_exists = folder.exists.lock().unwrap();
|
||||||
|
*prev_exists = match protocol_parser::select_response(&response)
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(SelectResponse::Bad(bad)) => {
|
||||||
|
debug!(bad);
|
||||||
|
panic!("could not select mailbox");
|
||||||
|
}
|
||||||
|
Ok(SelectResponse::Ok(ok)) => {
|
||||||
|
debug!(&ok);
|
||||||
|
ok.exists
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("{:?}", e);
|
||||||
|
panic!("could not select mailbox");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
exit_on_error!(sender, folder_hash, conn.send_command(b"IDLE"));
|
||||||
|
let mut iter = ImapBlockingConnection::from(conn);
|
||||||
|
let mut beat = std::time::Instant::now();
|
||||||
|
let mut watch = std::time::Instant::now();
|
||||||
|
/* duration interval to send heartbeat */
|
||||||
|
let _26_mins = std::time::Duration::from_secs(26 * 60);
|
||||||
|
/* duration interval to check other folders for changes */
|
||||||
|
let _5_mins = std::time::Duration::from_secs(5 * 60);
|
||||||
|
loop {
|
||||||
|
while let Some(line) = iter.next() {
|
||||||
|
let now = std::time::Instant::now();
|
||||||
|
if now.duration_since(beat) >= _26_mins {
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.set_nonblocking(true)
|
||||||
|
iter.conn.send_raw(b"DONE")
|
||||||
|
iter.conn.read_response(&mut response)
|
||||||
|
iter.conn.send_command(b"IDLE")
|
||||||
|
iter.conn.set_nonblocking(false)
|
||||||
|
main_conn.lock().unwrap().send_command(b"NOOP")
|
||||||
|
main_conn.lock().unwrap().read_response(&mut response)
|
||||||
|
);
|
||||||
|
beat = now;
|
||||||
|
}
|
||||||
|
if now.duration_since(watch) >= _5_mins {
|
||||||
|
/* Time to poll the other inboxes */
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.set_nonblocking(true)
|
||||||
|
iter.conn.send_raw(b"DONE")
|
||||||
|
iter.conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
for (hash, folder) in &folders {
|
||||||
|
if *hash == folder_hash {
|
||||||
|
/* Skip INBOX */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
examine_updates(folder, &sender, &mut iter.conn, &hash_index, &uid_index);
|
||||||
|
}
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.send_command(b"IDLE")
|
||||||
|
iter.conn.set_nonblocking(false)
|
||||||
|
main_conn.lock().unwrap().send_command(b"NOOP")
|
||||||
|
main_conn.lock().unwrap().read_response(&mut response)
|
||||||
|
);
|
||||||
|
watch = now;
|
||||||
|
}
|
||||||
|
match protocol_parser::untagged_responses(line.as_slice())
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(Some(Recent(_))) => {
|
||||||
|
/* UID SEARCH RECENT */
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.set_nonblocking(true)
|
||||||
|
iter.conn.send_raw(b"DONE")
|
||||||
|
iter.conn.read_response(&mut response)
|
||||||
|
iter.conn.send_command(b"UID SEARCH RECENT")
|
||||||
|
iter.conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
match protocol_parser::search_results_raw(response.as_bytes())
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(&[]) => {
|
||||||
|
debug!("UID SEARCH RECENT returned no results");
|
||||||
|
}
|
||||||
|
Ok(v) => {
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.send_command(
|
||||||
|
&[b"UID FETCH", v, b"(FLAGS RFC822.HEADER)"]
|
||||||
|
.join(&b' '),
|
||||||
|
)
|
||||||
|
iter.conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
debug!(&response);
|
||||||
|
match protocol_parser::uid_fetch_response(response.as_bytes())
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(v) => {
|
||||||
|
for (uid, flags, b) in v {
|
||||||
|
if let Ok(env) = Envelope::from_bytes(&b, flags) {
|
||||||
|
hash_index
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(env.hash(), (uid, folder_hash));
|
||||||
|
uid_index.lock().unwrap().insert(uid, env.hash());
|
||||||
|
debug!(
|
||||||
|
"Create event {} {} {}",
|
||||||
|
env.hash(),
|
||||||
|
env.subject(),
|
||||||
|
folder.path(),
|
||||||
|
);
|
||||||
|
sender.send(RefreshEvent {
|
||||||
|
hash: folder_hash,
|
||||||
|
kind: Create(Box::new(env)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!(
|
||||||
|
"UID SEARCH RECENT err: {}\nresp: {}",
|
||||||
|
e.to_string(),
|
||||||
|
&response
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.send_command(b"IDLE")
|
||||||
|
iter.conn.set_nonblocking(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(Some(Expunge(n))) => {
|
||||||
|
debug!("expunge {}", n);
|
||||||
|
}
|
||||||
|
Ok(Some(Exists(n))) => {
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.set_nonblocking(true)
|
||||||
|
iter.conn.send_raw(b"DONE")
|
||||||
|
iter.conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
||||||
|
* */
|
||||||
|
let mut prev_exists = folder.exists.lock().unwrap();
|
||||||
|
debug!("exists {}", n);
|
||||||
|
if n > *prev_exists {
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.send_command(
|
||||||
|
&[
|
||||||
|
b"FETCH",
|
||||||
|
format!("{}:{}", *prev_exists + 1, n).as_bytes(),
|
||||||
|
b"(UID FLAGS RFC822.HEADER)",
|
||||||
|
]
|
||||||
|
.join(&b' '),
|
||||||
|
)
|
||||||
|
iter.conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
match protocol_parser::uid_fetch_response(response.as_bytes())
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(v) => {
|
||||||
|
for (uid, flags, b) in v {
|
||||||
|
if uid_index.lock().unwrap().contains_key(&uid) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let Ok(env) = Envelope::from_bytes(&b, flags) {
|
||||||
|
hash_index
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(env.hash(), (uid, folder_hash));
|
||||||
|
uid_index.lock().unwrap().insert(uid, env.hash());
|
||||||
|
debug!(
|
||||||
|
"Create event {} {} {}",
|
||||||
|
env.hash(),
|
||||||
|
env.subject(),
|
||||||
|
folder.path(),
|
||||||
|
);
|
||||||
|
sender.send(RefreshEvent {
|
||||||
|
hash: folder_hash,
|
||||||
|
kind: Create(Box::new(env)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*prev_exists = n;
|
||||||
|
} else if n < *prev_exists {
|
||||||
|
*prev_exists = n;
|
||||||
|
}
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
iter.conn.send_command(b"IDLE")
|
||||||
|
iter.conn.set_nonblocking(false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(None) | Err(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
debug!("failure");
|
||||||
|
sender.send(RefreshEvent {
|
||||||
|
hash: folder_hash,
|
||||||
|
kind: RefreshEventKind::Failure(MeliError::new("conn_error")),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examine_updates(
|
||||||
|
folder: &ImapFolder,
|
||||||
|
sender: &RefreshEventConsumer,
|
||||||
|
conn: &mut ImapConnection,
|
||||||
|
hash_index: &Arc<Mutex<FnvHashMap<EnvelopeHash, (UID, FolderHash)>>>,
|
||||||
|
uid_index: &Arc<Mutex<FnvHashMap<usize, EnvelopeHash>>>,
|
||||||
|
) {
|
||||||
|
let folder_hash = folder.hash();
|
||||||
|
debug!("examining folder {} {}", folder_hash, folder.path());
|
||||||
|
let mut response = String::with_capacity(8 * 1024);
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
conn.send_command(format!("EXAMINE {}", folder.path()).as_bytes())
|
||||||
|
conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
match protocol_parser::select_response(&response)
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(SelectResponse::Bad(bad)) => {
|
||||||
|
debug!(bad);
|
||||||
|
panic!("could not select mailbox");
|
||||||
|
}
|
||||||
|
Ok(SelectResponse::Ok(ok)) => {
|
||||||
|
debug!(&ok);
|
||||||
|
let mut prev_exists = folder.exists.lock().unwrap();
|
||||||
|
let n = ok.exists;
|
||||||
|
if ok.recent > 0 {
|
||||||
|
{
|
||||||
|
/* UID SEARCH RECENT */
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
conn.send_command(b"UID SEARCH RECENT")
|
||||||
|
conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
match protocol_parser::search_results_raw(response.as_bytes())
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(&[]) => {
|
||||||
|
debug!("UID SEARCH RECENT returned no results");
|
||||||
|
}
|
||||||
|
Ok(v) => {
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
conn.send_command(
|
||||||
|
&[b"UID FETCH", v, b"(FLAGS RFC822.HEADER)"]
|
||||||
|
.join(&b' '),
|
||||||
|
)
|
||||||
|
conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
debug!(&response);
|
||||||
|
match protocol_parser::uid_fetch_response(response.as_bytes())
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(v) => {
|
||||||
|
for (uid, flags, b) in v {
|
||||||
|
if let Ok(env) = Envelope::from_bytes(&b, flags) {
|
||||||
|
hash_index
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(env.hash(), (uid, folder_hash));
|
||||||
|
uid_index.lock().unwrap().insert(uid, env.hash());
|
||||||
|
debug!(
|
||||||
|
"Create event {} {} {}",
|
||||||
|
env.hash(),
|
||||||
|
env.subject(),
|
||||||
|
folder.path(),
|
||||||
|
);
|
||||||
|
sender.send(RefreshEvent {
|
||||||
|
hash: folder_hash,
|
||||||
|
kind: Create(Box::new(env)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!(
|
||||||
|
"UID SEARCH RECENT err: {}\nresp: {}",
|
||||||
|
e.to_string(),
|
||||||
|
&response
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if n > *prev_exists {
|
||||||
|
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
||||||
|
* */
|
||||||
|
debug!("exists {}", n);
|
||||||
|
exit_on_error!(
|
||||||
|
sender,
|
||||||
|
folder_hash,
|
||||||
|
conn.send_command(
|
||||||
|
&[
|
||||||
|
b"FETCH",
|
||||||
|
format!("{}:{}", *prev_exists + 1, n).as_bytes(),
|
||||||
|
b"(UID FLAGS RFC822.HEADER)",
|
||||||
|
]
|
||||||
|
.join(&b' '),
|
||||||
|
)
|
||||||
|
conn.read_response(&mut response)
|
||||||
|
);
|
||||||
|
match protocol_parser::uid_fetch_response(response.as_bytes())
|
||||||
|
.to_full_result()
|
||||||
|
.map_err(MeliError::from)
|
||||||
|
{
|
||||||
|
Ok(v) => {
|
||||||
|
for (uid, flags, b) in v {
|
||||||
|
if let Ok(env) = Envelope::from_bytes(&b, flags) {
|
||||||
|
hash_index
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert(env.hash(), (uid, folder_hash));
|
||||||
|
uid_index.lock().unwrap().insert(uid, env.hash());
|
||||||
|
debug!(
|
||||||
|
"Create event {} {} {}",
|
||||||
|
env.hash(),
|
||||||
|
env.subject(),
|
||||||
|
folder.path(),
|
||||||
|
);
|
||||||
|
sender.send(RefreshEvent {
|
||||||
|
hash: folder_hash,
|
||||||
|
kind: Create(Box::new(env)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*prev_exists = n;
|
||||||
|
} else if n < *prev_exists {
|
||||||
|
*prev_exists = n;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("{:?}", e);
|
||||||
|
panic!("could not select mailbox");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -869,6 +869,9 @@ impl fmt::Display for ThreadView {
|
||||||
impl Component for ThreadView {
|
impl Component for ThreadView {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
let total_cols = width!(area);
|
let total_cols = width!(area);
|
||||||
|
if self.entries.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* If user has selected another mail to view, change to it */
|
/* If user has selected another mail to view, change to it */
|
||||||
if self.new_expanded_pos != self.expanded_pos {
|
if self.new_expanded_pos != self.expanded_pos {
|
||||||
|
|
|
@ -40,7 +40,6 @@ use crate::types::UIEvent::{self, EnvelopeRemove, EnvelopeRename, EnvelopeUpdate
|
||||||
use std::collections::VecDeque;
|
use std::collections::VecDeque;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::mem;
|
|
||||||
use std::ops::{Index, IndexMut};
|
use std::ops::{Index, IndexMut};
|
||||||
use std::result;
|
use std::result;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
Loading…
Reference in New Issue