imap: add watch
parent
c3e3c98fb0
commit
335a1011de
|
@ -28,6 +28,8 @@ mod operations;
|
|||
pub use operations::*;
|
||||
mod connection;
|
||||
pub use connection::*;
|
||||
mod watch;
|
||||
pub use watch::*;
|
||||
|
||||
extern crate native_tls;
|
||||
|
||||
|
@ -38,6 +40,7 @@ use crate::backends::RefreshEvent;
|
|||
use crate::backends::RefreshEventKind::{self, *};
|
||||
use crate::backends::{BackendFolder, Folder, FolderOperation, MailBackend, RefreshEventConsumer};
|
||||
use crate::conf::AccountSettings;
|
||||
use crate::email::parser::BytesExt;
|
||||
use crate::email::*;
|
||||
use crate::error::{MeliError, Result};
|
||||
use fnv::{FnvHashMap, FnvHashSet};
|
||||
|
@ -82,22 +85,21 @@ impl MailBackend for ImapType {
|
|||
let uid_index = self.uid_index.clone();
|
||||
let folder_path = folder.path().to_string();
|
||||
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 connection = connection.clone();
|
||||
let tx = tx.clone();
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
{
|
||||
let conn = connection.lock();
|
||||
exit_on_error!(&tx, conn);
|
||||
let mut conn = conn.unwrap();
|
||||
let conn = connection.lock();
|
||||
exit_on_error!(&tx, conn);
|
||||
let mut conn = conn.unwrap();
|
||||
debug!("locked for get {}", folder_path);
|
||||
|
||||
debug!("locked for get {}", folder_path);
|
||||
exit_on_error!(&tx,
|
||||
conn.send_command(format!("EXAMINE {}", folder_path).as_bytes())
|
||||
conn.read_response(&mut response)
|
||||
);
|
||||
}
|
||||
exit_on_error!(&tx,
|
||||
conn.send_command(format!("EXAMINE {}", folder_path).as_bytes())
|
||||
conn.read_response(&mut response)
|
||||
);
|
||||
let examine_response = protocol_parser::select_response(&response)
|
||||
.to_full_result()
|
||||
.map_err(MeliError::from);
|
||||
|
@ -106,18 +108,17 @@ impl MailBackend for ImapType {
|
|||
SelectResponse::Ok(ok) => ok.exists,
|
||||
SelectResponse::Bad(b) => b.exists,
|
||||
};
|
||||
{
|
||||
let mut folder_exists = folder_exists.lock().unwrap();
|
||||
*folder_exists = exists;
|
||||
}
|
||||
|
||||
while exists > 1 {
|
||||
let mut envelopes = vec![];
|
||||
{
|
||||
let conn = connection.lock();
|
||||
exit_on_error!(&tx, conn);
|
||||
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)
|
||||
);
|
||||
}
|
||||
exit_on_error!(&tx,
|
||||
conn.send_command(format!("UID FETCH {}:{} (FLAGS RFC822.HEADER)", std::cmp::max(exists.saturating_sub(20000), 1), exists).as_bytes())
|
||||
conn.read_response(&mut response)
|
||||
);
|
||||
debug!(
|
||||
"fetch response is {} bytes and {} lines",
|
||||
response.len(),
|
||||
|
@ -145,10 +146,11 @@ impl MailBackend for ImapType {
|
|||
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");
|
||||
tx.send(AsyncStatus::Payload(Ok(envelopes)));
|
||||
}
|
||||
drop(conn);
|
||||
tx.send(AsyncStatus::Finished);
|
||||
};
|
||||
Box::new(closure)
|
||||
|
@ -157,286 +159,29 @@ impl MailBackend for ImapType {
|
|||
}
|
||||
|
||||
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 sender = Arc::new(sender);
|
||||
for f in self.folders.values() {
|
||||
let mut conn = self.new_connection()?;
|
||||
let main_conn = self.connection.clone();
|
||||
let f_path = f.path().to_string();
|
||||
let hash_index = self.hash_index.clone();
|
||||
let uid_index = self.uid_index.clone();
|
||||
let folder_hash = f.hash();
|
||||
let sender = sender.clone();
|
||||
std::thread::Builder::new()
|
||||
.name(format!(
|
||||
"{},{}: imap connection",
|
||||
self.account_name.as_str(),
|
||||
f_path.as_str()
|
||||
))
|
||||
.spawn(move || {
|
||||
let mut response = String::with_capacity(8 * 1024);
|
||||
exit_on_error!(
|
||||
sender.as_ref(),
|
||||
folder_hash,
|
||||
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));
|
||||
}
|
||||
}
|
||||
})?;
|
||||
}
|
||||
let folders = self.imap_folders();
|
||||
let conn = self.new_connection()?;
|
||||
let main_conn = self.connection.clone();
|
||||
let hash_index = self.hash_index.clone();
|
||||
let uid_index = self.uid_index.clone();
|
||||
std::thread::Builder::new()
|
||||
.name(format!("{} imap connection", self.account_name.as_str(),))
|
||||
.spawn(move || {
|
||||
let kit = ImapWatchKit {
|
||||
conn,
|
||||
main_conn,
|
||||
hash_index,
|
||||
uid_index,
|
||||
folders,
|
||||
sender,
|
||||
};
|
||||
if has_idle {
|
||||
idle(kit);
|
||||
} else {
|
||||
poll_with_examine(kit);
|
||||
}
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
use crate::backends::{BackendFolder, Folder, FolderHash};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct ImapFolder {
|
||||
|
@ -27,6 +28,8 @@ pub struct ImapFolder {
|
|||
pub(super) name: String,
|
||||
pub(super) parent: Option<FolderHash>,
|
||||
pub(super) children: Vec<FolderHash>,
|
||||
|
||||
pub exists: Arc<Mutex<usize>>,
|
||||
}
|
||||
|
||||
impl BackendFolder for ImapFolder {
|
||||
|
@ -57,6 +60,7 @@ impl BackendFolder for ImapFolder {
|
|||
name: self.name.clone(),
|
||||
parent: self.parent,
|
||||
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 {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
let total_cols = width!(area);
|
||||
if self.entries.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
/* If user has selected another mail to view, change to it */
|
||||
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::fs;
|
||||
use std::io;
|
||||
use std::mem;
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::result;
|
||||
use std::sync::Arc;
|
||||
|
|
Loading…
Reference in New Issue