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
parent
c1a64d6c33
commit
ca51077f53
|
@ -210,6 +210,7 @@ pub enum RefreshEventKind {
|
||||||
Rename(EnvelopeHash, EnvelopeHash),
|
Rename(EnvelopeHash, EnvelopeHash),
|
||||||
Create(Box<Envelope>),
|
Create(Box<Envelope>),
|
||||||
Remove(EnvelopeHash),
|
Remove(EnvelopeHash),
|
||||||
|
NewFlags(EnvelopeHash, (Flag, Vec<String>)),
|
||||||
Rescan,
|
Rescan,
|
||||||
Failure(MeliError),
|
Failure(MeliError),
|
||||||
}
|
}
|
||||||
|
|
|
@ -648,6 +648,7 @@ pub enum UntaggedResponse {
|
||||||
/// messages).
|
/// messages).
|
||||||
/// ```
|
/// ```
|
||||||
Recent(usize),
|
Recent(usize),
|
||||||
|
Fetch(usize, (Flag, Vec<String>)),
|
||||||
}
|
}
|
||||||
|
|
||||||
named!(
|
named!(
|
||||||
|
@ -664,6 +665,8 @@ named!(
|
||||||
b"EXPUNGE" => Some(Expunge(num)),
|
b"EXPUNGE" => Some(Expunge(num)),
|
||||||
b"EXISTS" => Some(Exists(num)),
|
b"EXISTS" => Some(Exists(num)),
|
||||||
b"RECENT" => Some(Recent(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) });
|
debug!("unknown untagged_response: {}", unsafe { std::str::from_utf8_unchecked(tag) });
|
||||||
None
|
None
|
||||||
|
@ -676,7 +679,7 @@ named!(
|
||||||
named!(
|
named!(
|
||||||
pub search_results<Vec<usize>>,
|
pub search_results<Vec<usize>>,
|
||||||
alt_complete!(do_parse!( tag!("* SEARCH ")
|
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) }) }))
|
>> 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")
|
>> tag!("\r\n")
|
||||||
>> ({ list })) |
|
>> ({ list })) |
|
||||||
do_parse!(tag!("* SEARCH\r\n") >> ({ Vec::new() })))
|
do_parse!(tag!("* SEARCH\r\n") >> ({ Vec::new() })))
|
||||||
|
@ -691,6 +694,23 @@ named!(
|
||||||
do_parse!(tag!("* SEARCH\r\n") >> ({ &b""[0..] })))
|
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)]
|
#[derive(Debug, Default, Clone)]
|
||||||
pub struct SelectResponse {
|
pub struct SelectResponse {
|
||||||
pub exists: usize,
|
pub exists: usize,
|
||||||
|
|
|
@ -492,6 +492,47 @@ pub fn idle(kit: ImapWatchKit) -> Result<()> {
|
||||||
*prev_exists = n;
|
*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(_) => {}
|
Ok(None) | Err(_) => {}
|
||||||
}
|
}
|
||||||
work_context
|
work_context
|
||||||
|
|
|
@ -453,6 +453,34 @@ impl Account {
|
||||||
self.collection.update(old_hash, *envelope, mailbox_hash);
|
self.collection.update(old_hash, *envelope, mailbox_hash);
|
||||||
return Some(EnvelopeUpdate(old_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) => {
|
RefreshEventKind::Rename(old_hash, new_hash) => {
|
||||||
debug!("rename {} to {}", old_hash, new_hash);
|
debug!("rename {} to {}", old_hash, new_hash);
|
||||||
self.collection.rename(old_hash, new_hash, mailbox_hash);
|
self.collection.rename(old_hash, new_hash, mailbox_hash);
|
||||||
|
|
Loading…
Reference in New Issue