imap: Add support for untagged FETCH (FLAG.. messages

IDLE connection can get untagged "* FETCH (FLAGS ({flag_list))" messages
if any client has changed flags. Support this refresh event.
memfd
Manos Pitsidianakis 2020-02-28 09:09:43 +02:00
parent c1a64d6c33
commit ca51077f53
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
4 changed files with 92 additions and 2 deletions

View File

@ -210,6 +210,7 @@ pub enum RefreshEventKind {
Rename(EnvelopeHash, EnvelopeHash),
Create(Box<Envelope>),
Remove(EnvelopeHash),
NewFlags(EnvelopeHash, (Flag, Vec<String>)),
Rescan,
Failure(MeliError),
}

View File

@ -648,6 +648,7 @@ pub enum UntaggedResponse {
/// messages).
/// ```
Recent(usize),
Fetch(usize, (Flag, Vec<String>)),
}
named!(
@ -664,6 +665,8 @@ named!(
b"EXPUNGE" => Some(Expunge(num)),
b"EXISTS" => Some(Exists(num)),
b"RECENT" => Some(Recent(num)),
_ if tag.starts_with(b"FETCH ") => flags(
unsafe { std::str::from_utf8_unchecked(&tag[b"FETCH (FLAGS (".len()..]) }).map(|flags| Fetch(num, flags)).to_full_result().map_err(|err| debug!("untagged_response malformed fetch: {}", unsafe { std::str::from_utf8_unchecked(tag) })).ok(),
_ => {
debug!("unknown untagged_response: {}", unsafe { std::str::from_utf8_unchecked(tag) });
None
@ -675,8 +678,8 @@ named!(
named!(
pub search_results<Vec<usize>>,
alt_complete!(do_parse!( tag!("* SEARCH")
>> list: separated_nonempty_list_complete!(tag!(" "), map_res!(is_not!("\r\n"), |s| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }))
alt_complete!(do_parse!( tag!("* SEARCH ")
>> list: separated_nonempty_list_complete!(tag!(b" "), map_res!(is_not!(" \r\n"), |s: &[u8]| { usize::from_str(unsafe { std::str::from_utf8_unchecked(s) }) }))
>> tag!("\r\n")
>> ({ list })) |
do_parse!(tag!("* SEARCH\r\n") >> ({ Vec::new() })))
@ -691,6 +694,23 @@ named!(
do_parse!(tag!("* SEARCH\r\n") >> ({ &b""[0..] })))
);
#[test]
fn test_imap_search() {
assert_eq!(search_results(b"* SEARCH\r\n").to_full_result(), Ok(vec![]));
assert_eq!(
search_results(b"* SEARCH 1\r\n").to_full_result(),
Ok(vec![1])
);
assert_eq!(
search_results(b"* SEARCH 1 2 3 4\r\n").to_full_result(),
Ok(vec![1, 2, 3, 4])
);
assert_eq!(
search_results_raw(b"* SEARCH 1 2 3 4\r\n").to_full_result(),
Ok(&b"1 2 3 4"[..])
);
}
#[derive(Debug, Default, Clone)]
pub struct SelectResponse {
pub exists: usize,

View File

@ -492,6 +492,47 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
*prev_exists = n;
}
}
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().unwrap();
debug!("fetch {} {:?}", msg_seq, flags);
exit_on_error!(
sender,
mailbox_hash,
work_context,
thread_id,
conn.send_command(b"EXAMINE INBOX")
conn.read_response(&mut response)
conn.send_command(
&[
b"UID SEARCH ",
format!("{}", msg_seq).as_bytes(),
]
.join(&b' '),
)
conn.read_response(&mut response)
);
match search_results(response.split_rn().next().unwrap_or("").as_bytes())
.to_full_result()
{
Ok(mut v) => {
if let Some(uid) = v.pop() {
if let Some(env_hash) = uid_store.uid_index.lock().unwrap().get(&uid) {
sender.send(RefreshEvent {
hash: mailbox_hash,
kind: NewFlags(*env_hash, flags),
});
}
}
}
Err(e) => {
debug!(&response);
debug!(e);
}
}
}
Ok(None) | Err(_) => {}
}
work_context

View File

@ -453,6 +453,34 @@ impl Account {
self.collection.update(old_hash, *envelope, mailbox_hash);
return Some(EnvelopeUpdate(old_hash));
}
RefreshEventKind::NewFlags(env_hash, (flags, tags)) => {
let mut envelopes = self.collection.envelopes.write().unwrap();
envelopes.entry(env_hash).and_modify(|entry| {
for f in tags {
let hash = tag_hash!(f);
if !entry.labels().contains(&hash) {
entry.labels_mut().push(hash);
}
}
entry.set_flags(flags);
});
#[cfg(feature = "sqlite3")]
{
if let Err(err) = crate::sqlite3::remove(env_hash).and_then(|_| {
crate::sqlite3::insert(&envelopes[&env_hash], &self.backend, &self.name)
}) {
melib::log(
format!(
"Failed to update envelope {} in cache: {}",
envelopes[&env_hash].message_id_display(),
err.to_string()
),
melib::ERROR,
);
}
}
return Some(EnvelopeUpdate(env_hash));
}
RefreshEventKind::Rename(old_hash, new_hash) => {
debug!("rename {} to {}", old_hash, new_hash);
self.collection.rename(old_hash, new_hash, mailbox_hash);