meli/melib/src/backends/imap/untagged.rs

333 lines
15 KiB
Rust

/*
* meli - imap
*
* Copyright 2020 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::{ImapConnection, MailboxSelection};
use crate::backends::imap::protocol_parser::{
ImapLineSplit, RequiredResponses, UidFetchResponse, UntaggedResponse,
};
use crate::backends::BackendMailbox;
use crate::backends::{
RefreshEvent,
RefreshEventKind::{self, *},
};
use crate::email::Envelope;
use crate::error::*;
use std::time::Instant;
impl ImapConnection {
pub async fn process_untagged(&mut self, line: &str) -> Result<bool> {
macro_rules! try_fail {
($mailbox_hash: expr, $($result:expr)+) => {
$(if let Err(err) = $result {
*self.uid_store.is_online.lock().unwrap() = (
Instant::now(),
Err(err.clone()),
);
debug!("failure: {}", err.to_string());
self.add_refresh_event(RefreshEvent {
account_hash: self.uid_store.account_hash,
mailbox_hash: $mailbox_hash,
kind: RefreshEventKind::Failure(err.clone()),
});
Err(err)
} else { Ok(()) }?;)+
};
}
//FIXME
let mailbox_hash = match self.current_mailbox {
MailboxSelection::Select(h) | MailboxSelection::Examine(h) => h,
MailboxSelection::None => return Ok(false),
};
let mailbox =
std::clone::Clone::clone(&self.uid_store.mailboxes.lock().await[&mailbox_hash]);
let mut response = String::with_capacity(8 * 1024);
let untagged_response =
match super::protocol_parser::untagged_responses(line.as_bytes()).map(|(_, v)| v) {
Ok(None) | Err(_) => {
return Ok(false);
}
Ok(Some(r)) => r,
};
match untagged_response {
UntaggedResponse::Bye { reason } => {
*self.uid_store.is_online.lock().unwrap() =
(std::time::Instant::now(), Err(reason.into()));
}
UntaggedResponse::Expunge(n) => {
let deleted_uid = self
.uid_store
.msn_index
.lock()
.unwrap()
.entry(mailbox_hash)
.or_default()
.remove(n);
debug!("expunge {}, UID = {}", n, deleted_uid);
let deleted_hash: crate::email::EnvelopeHash = self
.uid_store
.uid_index
.lock()
.unwrap()
.remove(&(mailbox_hash, deleted_uid))
.unwrap();
self.uid_store
.hash_index
.lock()
.unwrap()
.remove(&deleted_hash);
self.add_refresh_event(RefreshEvent {
account_hash: self.uid_store.account_hash,
mailbox_hash,
kind: Remove(deleted_hash),
});
}
UntaggedResponse::Exists(n) => {
/* UID FETCH ALL UID, cross-ref, then FETCH difference headers
* */
debug!("exists {}", n);
if n > mailbox.exists.lock().unwrap().len() {
try_fail!(
mailbox_hash,
self.send_command(
&[
b"FETCH",
format!("{}:{}", mailbox.exists.lock().unwrap().len() + 1, n).as_bytes(),
b"(UID FLAGS RFC822)",
]
.join(&b' '),
).await
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
);
match super::protocol_parser::uid_fetch_responses(&response) {
Ok((_, v, _)) => {
'fetch_responses: for UidFetchResponse {
uid, flags, body, ..
} in v
{
if self
.uid_store
.uid_index
.lock()
.unwrap()
.contains_key(&(mailbox_hash, uid))
{
continue 'fetch_responses;
}
if let Ok(mut env) = Envelope::from_bytes(
body.unwrap(),
flags.as_ref().map(|&(f, _)| f),
) {
self.uid_store
.hash_index
.lock()
.unwrap()
.insert(env.hash(), (uid, mailbox_hash));
self.uid_store
.uid_index
.lock()
.unwrap()
.insert((mailbox_hash, uid), env.hash());
if let Some((_, keywords)) = flags {
let mut tag_lck = self.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());
}
mailbox.exists.lock().unwrap().insert_new(env.hash());
self.add_refresh_event(RefreshEvent {
account_hash: self.uid_store.account_hash,
mailbox_hash,
kind: Create(Box::new(env)),
});
}
}
}
Err(e) => {
debug!(e);
}
}
}
}
UntaggedResponse::Recent(_) => {
try_fail!(
mailbox_hash,
self.send_command(b"UID SEARCH RECENT").await
self.read_response(&mut response, RequiredResponses::SEARCH).await
);
match super::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) => {
try_fail!(
mailbox_hash,
self.send_command(
&[b"UID FETCH", v, b"(FLAGS RFC822)"]
.join(&b' '),
).await
self.read_response(&mut response, RequiredResponses::FETCH_REQUIRED).await
);
debug!(&response);
match super::protocol_parser::uid_fetch_responses(&response) {
Ok((_, v, _)) => {
for UidFetchResponse {
uid, flags, body, ..
} in v
{
if !self
.uid_store
.uid_index
.lock()
.unwrap()
.contains_key(&(mailbox_hash, uid))
{
if let Ok(mut env) = Envelope::from_bytes(
body.unwrap(),
flags.as_ref().map(|&(f, _)| f),
) {
self.uid_store
.hash_index
.lock()
.unwrap()
.insert(env.hash(), (uid, mailbox_hash));
self.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 =
self.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());
}
mailbox.exists.lock().unwrap().insert_new(env.hash());
self.add_refresh_event(RefreshEvent {
account_hash: self.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
);
}
}
}
UntaggedResponse::Fetch(msg_seq, flags) => {
/* a * {msg_seq} FETCH (FLAGS ({flags})) was received, so find out UID from msg_seq
* and send update
*/
debug!("fetch {} {:?}", msg_seq, flags);
try_fail!(
mailbox_hash,
self.send_command(
&[
b"UID SEARCH ",
format!("{}", msg_seq).as_bytes(),
]
.join(&b' '),
).await
self.read_response(&mut response, RequiredResponses::SEARCH).await
);
debug!(&response);
match super::protocol_parser::search_results(
response.split_rn().next().unwrap_or("").as_bytes(),
)
.map(|(_, v)| v)
{
Ok(mut v) => {
if let Some(uid) = v.pop() {
let lck = self.uid_store.uid_index.lock().unwrap();
let env_hash = lck.get(&(mailbox_hash, uid)).copied();
drop(lck);
if let Some(env_hash) = env_hash {
if !flags.0.intersects(crate::email::Flag::SEEN) {
mailbox.unseen.lock().unwrap().insert_new(env_hash);
} else {
mailbox.unseen.lock().unwrap().remove(env_hash);
}
self.add_refresh_event(RefreshEvent {
account_hash: self.uid_store.account_hash,
mailbox_hash,
kind: NewFlags(env_hash, flags),
});
};
}
}
Err(e) => {
debug!(&response);
debug!(e);
}
}
}
}
Ok(true)
}
}