777 lines
36 KiB
Rust
777 lines
36 KiB
Rust
/*
|
|
* 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 crate::backends::SpecialUsageMailbox;
|
|
use crate::email::parser::BytesExt;
|
|
use crate::email::parser::BytesIterExt;
|
|
use std::sync::Arc;
|
|
|
|
/// Arguments for IMAP watching functions
|
|
pub struct ImapWatchKit {
|
|
pub conn: ImapConnection,
|
|
pub main_conn: Arc<FutureMutex<ImapConnection>>,
|
|
pub uid_store: Arc<UIDStore>,
|
|
}
|
|
|
|
macro_rules! exit_on_error {
|
|
($conn:expr, $mailbox_hash:ident, $($result:expr)+) => {
|
|
$(if let Err(e) = $result {
|
|
*$conn.uid_store.is_online.lock().unwrap() = (
|
|
Instant::now(),
|
|
Err(e.clone()),
|
|
);
|
|
debug!("failure: {}", e.to_string());
|
|
let account_hash = $conn.uid_store.account_hash;
|
|
$conn.add_refresh_event(RefreshEvent {
|
|
account_hash,
|
|
mailbox_hash: $mailbox_hash,
|
|
kind: RefreshEventKind::Failure(e.clone()),
|
|
});
|
|
Err(e)
|
|
} else { Ok(()) }?;)+
|
|
};
|
|
}
|
|
|
|
pub async fn poll_with_examine(kit: ImapWatchKit) -> Result<()> {
|
|
debug!("poll with examine");
|
|
let ImapWatchKit {
|
|
mut conn,
|
|
main_conn,
|
|
uid_store,
|
|
} = kit;
|
|
conn.connect().await?;
|
|
let mut response = String::with_capacity(8 * 1024);
|
|
loop {
|
|
let mailboxes: HashMap<MailboxHash, ImapMailbox> = {
|
|
let mailboxes_lck = uid_store.mailboxes.lock().await;
|
|
mailboxes_lck.clone()
|
|
};
|
|
for (_, mailbox) in mailboxes {
|
|
examine_updates(mailbox, &mut conn, &uid_store).await?;
|
|
}
|
|
let mut main_conn = main_conn.lock().await;
|
|
main_conn.send_command(b"NOOP").await?;
|
|
main_conn
|
|
.read_response(&mut response, RequiredResponses::empty())
|
|
.await?;
|
|
}
|
|
}
|
|
|
|
pub async fn idle(kit: ImapWatchKit) -> Result<()> {
|
|
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,
|
|
uid_store,
|
|
} = kit;
|
|
conn.connect().await?;
|
|
let mailbox: ImapMailbox = match uid_store
|
|
.mailboxes
|
|
.lock()
|
|
.await
|
|
.values()
|
|
.find(|f| f.parent.is_none() && (f.special_usage() == SpecialUsageMailbox::Inbox))
|
|
.map(std::clone::Clone::clone)
|
|
{
|
|
Some(mailbox) => mailbox,
|
|
None => {
|
|
let err = MeliError::new("INBOX mailbox not found in local mailbox index. meli may have not parsed the IMAP mailboxes correctly");
|
|
debug!("failure: {}", err.to_string());
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash: 0,
|
|
kind: RefreshEventKind::Failure(err.clone()),
|
|
});
|
|
return Err(err);
|
|
}
|
|
};
|
|
let mailbox_hash = mailbox.hash();
|
|
let uidvalidity;
|
|
let mut response = String::with_capacity(8 * 1024);
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.send_command(format!("SELECT \"{}\"", mailbox.imap_path()).as_bytes())
|
|
.await
|
|
conn.read_response(&mut response, RequiredResponses::SELECT_REQUIRED)
|
|
.await
|
|
);
|
|
debug!("select response {}", &response);
|
|
{
|
|
let mut prev_exists = mailbox.exists.lock().unwrap();
|
|
match protocol_parser::select_response(&response) {
|
|
Ok(ok) => {
|
|
{
|
|
uidvalidity = ok.uidvalidity;
|
|
let mut uidvalidities = uid_store.uidvalidity.lock().unwrap();
|
|
|
|
if let Some(v) = uidvalidities.get_mut(&mailbox_hash) {
|
|
if *v != ok.uidvalidity {
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: RefreshEventKind::Rescan,
|
|
});
|
|
prev_exists.clear();
|
|
/*
|
|
uid_store.uid_index.lock().unwrap().clear();
|
|
uid_store.hash_index.lock().unwrap().clear();
|
|
uid_store.byte_cache.lock().unwrap().clear();
|
|
*/
|
|
*v = ok.uidvalidity;
|
|
}
|
|
} else {
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: RefreshEventKind::Rescan,
|
|
});
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: RefreshEventKind::Failure(MeliError::new(format!(
|
|
"Unknown mailbox: {} {}",
|
|
mailbox.path(),
|
|
mailbox_hash
|
|
))),
|
|
});
|
|
}
|
|
}
|
|
debug!(&ok);
|
|
}
|
|
Err(e) => {
|
|
debug!("{:?}", e);
|
|
return Err(e).chain_err_summary(|| "could not select mailbox");
|
|
}
|
|
};
|
|
}
|
|
exit_on_error!(conn, mailbox_hash, conn.send_command(b"IDLE").await);
|
|
let mut blockn = ImapBlockingConnection::from(conn);
|
|
let mut beat = std::time::Instant::now();
|
|
let mut watch = std::time::Instant::now();
|
|
/* duration interval to send heartbeat */
|
|
const _26_MINS: std::time::Duration = std::time::Duration::from_secs(26 * 60);
|
|
/* duration interval to check other mailboxes for changes */
|
|
const _5_MINS: std::time::Duration = std::time::Duration::from_secs(5 * 60);
|
|
while let Some(line) = blockn.as_stream().await {
|
|
let now = std::time::Instant::now();
|
|
if now.duration_since(beat) >= _26_MINS {
|
|
let mut main_conn_lck = main_conn.lock().await;
|
|
exit_on_error!(
|
|
blockn.conn,
|
|
mailbox_hash,
|
|
blockn.conn.send_raw(b"DONE").await
|
|
blockn.conn.read_response(&mut response, RequiredResponses::empty()).await
|
|
blockn.conn.send_command(b"IDLE").await
|
|
main_conn_lck.send_command(b"NOOP").await
|
|
main_conn_lck.read_response(&mut response, RequiredResponses::empty()).await
|
|
);
|
|
beat = now;
|
|
}
|
|
if now.duration_since(watch) >= _5_MINS {
|
|
/* Time to poll all inboxes */
|
|
let mut conn = main_conn.lock().await;
|
|
let mailboxes: HashMap<MailboxHash, ImapMailbox> = {
|
|
let mailboxes_lck = uid_store.mailboxes.lock().await;
|
|
mailboxes_lck.clone()
|
|
};
|
|
for (_, mailbox) in mailboxes {
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
examine_updates(mailbox, &mut conn, &uid_store).await
|
|
);
|
|
}
|
|
watch = now;
|
|
}
|
|
*uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(()));
|
|
match protocol_parser::untagged_responses(line.as_slice())
|
|
.map(|(_, v)| v)
|
|
.map_err(MeliError::from)
|
|
{
|
|
Ok(Some(Recent(_r))) => {
|
|
let mut conn = main_conn.lock().await;
|
|
/* UID SEARCH RECENT */
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.examine_mailbox(mailbox_hash, &mut response, false).await
|
|
conn.send_command(b"UID SEARCH RECENT").await
|
|
conn.read_response(&mut response, RequiredResponses::SEARCH).await
|
|
);
|
|
match protocol_parser::search_results_raw(response.as_bytes())
|
|
.map(|(_, v)| v)
|
|
.map_err(MeliError::from)
|
|
{
|
|
Ok(&[]) => {
|
|
debug!("UID SEARCH RECENT returned no results");
|
|
}
|
|
Ok(v) => {
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.send_command(
|
|
&[&b"UID FETCH"[..], &v.trim().split(|b| b == &b' ').join(b','), &b"(FLAGS RFC822)"[..]]
|
|
.join(&b' '),
|
|
).await
|
|
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
);
|
|
debug!(&response);
|
|
match protocol_parser::uid_fetch_responses(&response) {
|
|
Ok((_, v, _)) => {
|
|
for UidFetchResponse {
|
|
uid, flags, body, ..
|
|
} in v
|
|
{
|
|
if !uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.contains_key(&(mailbox_hash, uid))
|
|
{
|
|
if let Ok(mut env) = Envelope::from_bytes(
|
|
/* unwrap() is safe since we ask for RFC822 in the
|
|
* above FETCH, thus uid_fetch_responses() if
|
|
* returns a successful parse, it will include the
|
|
* RFC822 response */
|
|
body.unwrap(),
|
|
flags.as_ref().map(|&(f, _)| f),
|
|
) {
|
|
uid_store
|
|
.hash_index
|
|
.lock()
|
|
.unwrap()
|
|
.insert(env.hash(), (uid, mailbox_hash));
|
|
uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.insert((mailbox_hash, uid), env.hash());
|
|
debug!(
|
|
"Create event {} {} {}",
|
|
env.hash(),
|
|
env.subject(),
|
|
mailbox.path(),
|
|
);
|
|
if let Some((_, keywords)) = flags {
|
|
let mut tag_lck =
|
|
uid_store.tag_index.write().unwrap();
|
|
for f in keywords {
|
|
let hash = tag_hash!(f);
|
|
if !tag_lck.contains_key(&hash) {
|
|
tag_lck.insert(hash, f);
|
|
}
|
|
env.labels_mut().push(hash);
|
|
}
|
|
}
|
|
if !env.is_seen() {
|
|
mailbox
|
|
.unseen
|
|
.lock()
|
|
.unwrap()
|
|
.insert_new(env.hash());
|
|
}
|
|
if uid_store.cache_headers {
|
|
cache::save_envelopes(
|
|
uid_store.account_hash,
|
|
mailbox_hash,
|
|
uidvalidity,
|
|
&[(uid, &env)],
|
|
)?;
|
|
}
|
|
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
|
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: Create(Box::new(env)),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
debug!(e);
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
debug!(
|
|
"UID SEARCH RECENT err: {}\nresp: {}",
|
|
e.to_string(),
|
|
&response
|
|
);
|
|
}
|
|
}
|
|
}
|
|
Ok(Some(Expunge(n))) => {
|
|
// The EXPUNGE response reports that the specified message sequence
|
|
// number has been permanently removed from the mailbox. The message
|
|
// sequence number for each successive message in the mailbox is
|
|
// immediately decremented by 1, and this decrement is reflected in
|
|
// message sequence numbers in subsequent responses (including other
|
|
// untagged EXPUNGE responses).
|
|
let mut conn = main_conn.lock().await;
|
|
let deleted_uid = uid_store
|
|
.msn_index
|
|
.lock()
|
|
.unwrap()
|
|
.entry(mailbox_hash)
|
|
.or_default()
|
|
.remove(n);
|
|
debug!("expunge {}, UID = {}", n, deleted_uid);
|
|
let deleted_hash: EnvelopeHash = uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.remove(&(mailbox_hash, deleted_uid))
|
|
.unwrap();
|
|
uid_store.hash_index.lock().unwrap().remove(&deleted_hash);
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: Remove(deleted_hash),
|
|
});
|
|
}
|
|
Ok(Some(Exists(n))) => {
|
|
let mut conn = main_conn.lock().await;
|
|
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
|
* */
|
|
debug!("exists {}", n);
|
|
if n > mailbox.exists.lock().unwrap().len() {
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.examine_mailbox(mailbox_hash, &mut response, false).await
|
|
conn.send_command(
|
|
&[
|
|
b"FETCH",
|
|
format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(),
|
|
b"(UID FLAGS RFC822)",
|
|
]
|
|
.join(&b' '),
|
|
).await
|
|
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
);
|
|
match protocol_parser::uid_fetch_responses(&response) {
|
|
Ok((_, v, _)) => {
|
|
'fetch_responses_b: for UidFetchResponse {
|
|
uid, flags, body, ..
|
|
} in v
|
|
{
|
|
if uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.contains_key(&(mailbox_hash, uid))
|
|
{
|
|
continue 'fetch_responses_b;
|
|
}
|
|
if let Ok(mut env) = Envelope::from_bytes(
|
|
body.unwrap(),
|
|
flags.as_ref().map(|&(f, _)| f),
|
|
) {
|
|
uid_store
|
|
.hash_index
|
|
.lock()
|
|
.unwrap()
|
|
.insert(env.hash(), (uid, mailbox_hash));
|
|
uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.insert((mailbox_hash, uid), env.hash());
|
|
if let Some((_, keywords)) = flags {
|
|
let mut tag_lck = uid_store.tag_index.write().unwrap();
|
|
for f in keywords {
|
|
let hash = tag_hash!(f);
|
|
if !tag_lck.contains_key(&hash) {
|
|
tag_lck.insert(hash, f);
|
|
}
|
|
env.labels_mut().push(hash);
|
|
}
|
|
}
|
|
debug!(
|
|
"Create event {} {} {}",
|
|
env.hash(),
|
|
env.subject(),
|
|
mailbox.path(),
|
|
);
|
|
if !env.is_seen() {
|
|
mailbox.unseen.lock().unwrap().insert_new(env.hash());
|
|
}
|
|
if uid_store.cache_headers {
|
|
cache::save_envelopes(
|
|
uid_store.account_hash,
|
|
mailbox_hash,
|
|
uidvalidity,
|
|
&[(uid, &env)],
|
|
)?;
|
|
}
|
|
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
|
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: Create(Box::new(env)),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
debug!(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(Some(Fetch(msg_seq, flags))) => {
|
|
/* a * {msg_seq} FETCH (FLAGS ({flags})) was received, so find out UID from msg_seq
|
|
* and send update
|
|
*/
|
|
let mut conn = main_conn.lock().await;
|
|
debug!("fetch {} {:?}", msg_seq, flags);
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.examine_mailbox(mailbox_hash, &mut response, false).await
|
|
conn.send_command(
|
|
&[
|
|
b"UID SEARCH ",
|
|
format!("{}", msg_seq).as_bytes(),
|
|
]
|
|
.join(&b' '),
|
|
).await
|
|
conn.read_response(&mut response, RequiredResponses::SEARCH).await
|
|
);
|
|
match search_results(response.split_rn().next().unwrap_or("").as_bytes())
|
|
.map(|(_, v)| v)
|
|
{
|
|
Ok(mut v) => {
|
|
if let Some(uid) = v.pop() {
|
|
if let Some(env_hash) = uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.get(&(mailbox_hash, uid))
|
|
{
|
|
if !flags.0.intersects(crate::email::Flag::SEEN) {
|
|
mailbox.unseen.lock().unwrap().insert_new(*env_hash);
|
|
} else {
|
|
mailbox.unseen.lock().unwrap().remove(*env_hash);
|
|
}
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: NewFlags(*env_hash, flags),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
debug!(&response);
|
|
debug!(e);
|
|
}
|
|
}
|
|
}
|
|
Ok(Some(Bye { .. })) => break,
|
|
Ok(None) | Err(_) => {}
|
|
}
|
|
}
|
|
debug!("IDLE connection dropped");
|
|
let err: &str = blockn.err().unwrap_or("Unknown reason.");
|
|
main_conn.lock().await.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: RefreshEventKind::Failure(MeliError::new(format!(
|
|
"IDLE connection dropped: {}",
|
|
&err
|
|
))),
|
|
});
|
|
Err(MeliError::new(format!("IDLE connection dropped: {}", err)))
|
|
}
|
|
|
|
pub async fn examine_updates(
|
|
mailbox: ImapMailbox,
|
|
conn: &mut ImapConnection,
|
|
uid_store: &Arc<UIDStore>,
|
|
) -> Result<()> {
|
|
let mailbox_hash = mailbox.hash();
|
|
debug!("examining mailbox {} {}", mailbox_hash, mailbox.path());
|
|
let mut response = String::with_capacity(8 * 1024);
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.examine_mailbox(mailbox_hash, &mut response, true)
|
|
.await
|
|
);
|
|
*uid_store.is_online.lock().unwrap() = (Instant::now(), Ok(()));
|
|
let uidvalidity;
|
|
match protocol_parser::select_response(&response) {
|
|
Ok(ok) => {
|
|
uidvalidity = ok.uidvalidity;
|
|
debug!(&ok);
|
|
{
|
|
let mut uidvalidities = uid_store.uidvalidity.lock().unwrap();
|
|
|
|
if let Some(v) = uidvalidities.get_mut(&mailbox_hash) {
|
|
if *v != ok.uidvalidity {
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: RefreshEventKind::Rescan,
|
|
});
|
|
/*
|
|
uid_store.uid_index.lock().unwrap().clear();
|
|
uid_store.hash_index.lock().unwrap().clear();
|
|
uid_store.byte_cache.lock().unwrap().clear();
|
|
*/
|
|
*v = ok.uidvalidity;
|
|
}
|
|
} else {
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: RefreshEventKind::Rescan,
|
|
});
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: RefreshEventKind::Failure(MeliError::new(format!(
|
|
"Unknown mailbox: {} {}",
|
|
mailbox.path(),
|
|
mailbox_hash
|
|
))),
|
|
});
|
|
}
|
|
}
|
|
let n = ok.exists;
|
|
if ok.recent > 0 {
|
|
{
|
|
/* UID SEARCH RECENT */
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.send_command(b"UID SEARCH RECENT").await
|
|
conn.read_response(&mut response, RequiredResponses::SEARCH).await
|
|
);
|
|
match protocol_parser::search_results_raw(response.as_bytes())
|
|
.map(|(_, v)| v)
|
|
.map_err(MeliError::from)
|
|
{
|
|
Ok(&[]) => {
|
|
debug!("UID SEARCH RECENT returned no results");
|
|
}
|
|
Ok(v) => {
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.send_command(
|
|
&[&b"UID FETCH"[..], &v.trim().split(|b| b == &b' ').join(b','), &b"(FLAGS RFC822)"[..]]
|
|
.join(&b' '),
|
|
).await
|
|
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
);
|
|
debug!(&response);
|
|
match protocol_parser::uid_fetch_responses(&response) {
|
|
Ok((_, v, _)) => {
|
|
'fetch_responses_c: for UidFetchResponse {
|
|
uid,
|
|
flags,
|
|
body,
|
|
..
|
|
} in v
|
|
{
|
|
if uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.contains_key(&(mailbox_hash, uid))
|
|
{
|
|
continue 'fetch_responses_c;
|
|
}
|
|
if let Ok(mut env) = Envelope::from_bytes(
|
|
body.unwrap(),
|
|
flags.as_ref().map(|&(f, _)| f),
|
|
) {
|
|
uid_store
|
|
.hash_index
|
|
.lock()
|
|
.unwrap()
|
|
.insert(env.hash(), (uid, mailbox_hash));
|
|
uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.insert((mailbox_hash, uid), env.hash());
|
|
debug!(
|
|
"Create event {} {} {}",
|
|
env.hash(),
|
|
env.subject(),
|
|
mailbox.path(),
|
|
);
|
|
if let Some((_, keywords)) = flags {
|
|
let mut tag_lck =
|
|
uid_store.tag_index.write().unwrap();
|
|
for f in keywords {
|
|
let hash = tag_hash!(f);
|
|
if !tag_lck.contains_key(&hash) {
|
|
tag_lck.insert(hash, f);
|
|
}
|
|
env.labels_mut().push(hash);
|
|
}
|
|
}
|
|
if !env.is_seen() {
|
|
mailbox
|
|
.unseen
|
|
.lock()
|
|
.unwrap()
|
|
.insert_new(env.hash());
|
|
}
|
|
if uid_store.cache_headers {
|
|
cache::save_envelopes(
|
|
uid_store.account_hash,
|
|
mailbox_hash,
|
|
uidvalidity,
|
|
&[(uid, &env)],
|
|
)?;
|
|
}
|
|
let mut prev_exists = mailbox.exists.lock().unwrap();
|
|
prev_exists.insert_new(env.hash());
|
|
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_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 > mailbox.exists.lock().unwrap().len() {
|
|
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
|
|
* */
|
|
debug!("exists {}", n);
|
|
exit_on_error!(
|
|
conn,
|
|
mailbox_hash,
|
|
conn.send_command(
|
|
&[
|
|
b"FETCH",
|
|
format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(),
|
|
b"(UID FLAGS RFC822)",
|
|
]
|
|
.join(&b' '),
|
|
).await
|
|
conn.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
|
|
);
|
|
match protocol_parser::uid_fetch_responses(&response) {
|
|
Ok((_, v, _)) => {
|
|
'fetch_responses_a: for UidFetchResponse {
|
|
uid, flags, body, ..
|
|
} in v
|
|
{
|
|
if uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.contains_key(&(mailbox_hash, uid))
|
|
{
|
|
continue 'fetch_responses_a;
|
|
}
|
|
if let Ok(mut env) =
|
|
Envelope::from_bytes(body.unwrap(), flags.as_ref().map(|&(f, _)| f))
|
|
{
|
|
uid_store
|
|
.hash_index
|
|
.lock()
|
|
.unwrap()
|
|
.insert(env.hash(), (uid, mailbox_hash));
|
|
uid_store
|
|
.uid_index
|
|
.lock()
|
|
.unwrap()
|
|
.insert((mailbox_hash, uid), env.hash());
|
|
if let Some((_, keywords)) = flags {
|
|
let mut tag_lck = uid_store.tag_index.write().unwrap();
|
|
for f in keywords {
|
|
let hash = tag_hash!(f);
|
|
if !tag_lck.contains_key(&hash) {
|
|
tag_lck.insert(hash, f);
|
|
}
|
|
env.labels_mut().push(hash);
|
|
}
|
|
}
|
|
debug!(
|
|
"Create event {} {} {}",
|
|
env.hash(),
|
|
env.subject(),
|
|
mailbox.path(),
|
|
);
|
|
if !env.is_seen() {
|
|
mailbox.unseen.lock().unwrap().insert_new(env.hash());
|
|
}
|
|
if uid_store.cache_headers {
|
|
cache::save_envelopes(
|
|
uid_store.account_hash,
|
|
mailbox_hash,
|
|
uidvalidity,
|
|
&[(uid, &env)],
|
|
)?;
|
|
}
|
|
mailbox.exists.lock().unwrap().insert_new(env.hash());
|
|
|
|
conn.add_refresh_event(RefreshEvent {
|
|
account_hash: uid_store.account_hash,
|
|
mailbox_hash,
|
|
kind: Create(Box::new(env)),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
debug!(e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Err(e) => {
|
|
debug!("{:?}", e);
|
|
return Err(e).chain_err_summary(|| "could not select mailbox");
|
|
}
|
|
};
|
|
Ok(())
|
|
}
|