aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Auvolat <alex@adnab.me>2022-07-12 17:32:57 +0200
committerAlex Auvolat <alex@adnab.me>2022-07-12 17:32:57 +0200
commit7959adb8e970c9006ea9799b5ddd6b2c9aac217a (patch)
tree92f1a3b952e1e46de061c935e37bf6d2e5766a1c
parent23e0eff42b905c636274e82850429e073a730159 (diff)
downloadaerogramme-7959adb8e970c9006ea9799b5ddd6b2c9aac217a.tar.gz
aerogramme-7959adb8e970c9006ea9799b5ddd6b2c9aac217a.zip
implement STORE
-rw-r--r--src/imap/command/selected.rs20
-rw-r--r--src/imap/mailbox_view.rs128
2 files changed, 119 insertions, 29 deletions
diff --git a/src/imap/command/selected.rs b/src/imap/command/selected.rs
index 848302c..a6fa645 100644
--- a/src/imap/command/selected.rs
+++ b/src/imap/command/selected.rs
@@ -67,13 +67,21 @@ impl<'a> SelectedContext<'a> {
async fn store(
self,
- _sequence_set: &SequenceSet,
- _kind: &StoreType,
- _response: &StoreResponse,
- _flags: &[Flag],
- _uid: &bool,
+ sequence_set: &SequenceSet,
+ kind: &StoreType,
+ response: &StoreResponse,
+ flags: &[Flag],
+ uid: &bool,
) -> Result<(Response, flow::Transition)> {
- Ok((Response::bad("Not implemented")?, flow::Transition::None))
+ let data = self
+ .mailbox
+ .store(sequence_set, kind, response, flags, uid)
+ .await?;
+
+ Ok((
+ Response::ok("STORE completed")?.with_body(data),
+ flow::Transition::None,
+ ))
}
async fn copy(
diff --git a/src/imap/mailbox_view.rs b/src/imap/mailbox_view.rs
index e4df3d0..93d3b4d 100644
--- a/src/imap/mailbox_view.rs
+++ b/src/imap/mailbox_view.rs
@@ -13,13 +13,14 @@ use imap_codec::types::core::{Atom, IString, NString};
use imap_codec::types::datetime::MyDateTime;
use imap_codec::types::envelope::Envelope;
use imap_codec::types::fetch_attributes::{FetchAttribute, MacroOrFetchAttributes};
-use imap_codec::types::flag::Flag;
+use imap_codec::types::flag::{Flag, StoreResponse, StoreType};
use imap_codec::types::response::{Code, Data, MessageAttribute, Status};
use imap_codec::types::sequence::{self, SequenceSet};
use mail_parser::*;
use crate::mail::mailbox::Mailbox;
use crate::mail::uidindex::{ImapUid, ImapUidvalidity, UidIndex};
+use crate::mail::unique_ident::UniqueIdent;
const DEFAULT_FLAGS: [Flag; 5] = [
Flag::Seen,
@@ -144,6 +145,60 @@ impl MailboxView {
Ok(data)
}
+ pub async fn store(
+ &mut self,
+ sequence_set: &SequenceSet,
+ kind: &StoreType,
+ _response: &StoreResponse,
+ flags: &[Flag],
+ uid: &bool,
+ ) -> Result<Vec<Body>> {
+ if *uid {
+ bail!("UID STORE not implemented");
+ }
+
+ let flags = flags.iter().map(|x| x.to_string()).collect::<Vec<_>>();
+
+ let mails = self.get_mail_ids(sequence_set)?;
+ for (i, uid, uuid) in mails.iter() {
+ match kind {
+ StoreType::Add => {
+ self.mailbox.add_flags(*uuid, &flags[..]).await?;
+ }
+ StoreType::Remove => {
+ self.mailbox.del_flags(*uuid, &flags[..]).await?;
+ }
+ StoreType::Replace => {
+ let old_flags = &self
+ .known_state
+ .table
+ .get(uuid)
+ .ok_or(anyhow!(
+ "Missing message: {} (UID {}, UUID {})",
+ i,
+ uid,
+ uuid
+ ))?
+ .1;
+ let to_remove = old_flags
+ .iter()
+ .filter(|x| !flags.contains(&x))
+ .cloned()
+ .collect::<Vec<_>>();
+ let to_add = flags
+ .iter()
+ .filter(|x| !old_flags.contains(&x))
+ .cloned()
+ .collect::<Vec<_>>();
+ self.mailbox.add_flags(*uuid, &to_add[..]).await?;
+ self.mailbox.del_flags(*uuid, &to_remove[..]).await?;
+ }
+ }
+ }
+
+ self.update().await
+ }
+
/// Looks up state changes in the mailbox and produces a set of IMAP
/// responses describing the new state.
pub async fn fetch(
@@ -156,28 +211,11 @@ impl MailboxView {
bail!("UID FETCH not implemented");
}
- let mail_vec = self
- .known_state
- .idx_by_uid
- .iter()
- .map(|(uid, uuid)| (*uid, *uuid))
- .collect::<Vec<_>>();
-
- let mut mails = vec![];
- let iter_strat = sequence::Strategy::Naive {
- largest: NonZeroU32::try_from((self.known_state.idx_by_uid.len() + 1) as u32).unwrap(),
- };
- for i in sequence_set.iter(iter_strat) {
- if let Some(mail) = mail_vec.get(i.get() as usize - 1) {
- mails.push((i, *mail));
- } else {
- bail!("No such mail: {}", i);
- }
- }
+ let mails = self.get_mail_ids(sequence_set)?;
let mails_uuid = mails
.iter()
- .map(|(_i, (_uid, uuid))| *uuid)
+ .map(|(_i, _uid, uuid)| *uuid)
.collect::<Vec<_>>();
let mails_meta = self.mailbox.fetch_meta(&mails_uuid).await?;
@@ -200,7 +238,7 @@ impl MailboxView {
let mut iter = mails
.into_iter()
.zip(mails_meta.into_iter())
- .map(|((i, (uid, uuid)), meta)| async move {
+ .map(|((i, uid, uuid), meta)| async move {
let body = self.mailbox.fetch_full(uuid, &meta.message_key).await?;
Ok::<_, anyhow::Error>((i, uid, uuid, meta, Some(body)))
})
@@ -214,7 +252,7 @@ impl MailboxView {
mails
.into_iter()
.zip(mails_meta.into_iter())
- .map(|((i, (uid, uuid)), meta)| (i, uid, uuid, meta, None))
+ .map(|((i, uid, uuid), meta)| (i, uid, uuid, meta, None))
.collect::<Vec<_>>()
};
@@ -314,6 +352,36 @@ impl MailboxView {
// ----
+ // Gets the UIDs and UUIDs of mails identified by a SequenceSet of
+ // sequence numbers
+ fn get_mail_ids(
+ &self,
+ sequence_set: &SequenceSet,
+ ) -> Result<Vec<(NonZeroU32, ImapUid, UniqueIdent)>> {
+ let mail_vec = self
+ .known_state
+ .idx_by_uid
+ .iter()
+ .map(|(uid, uuid)| (*uid, *uuid))
+ .collect::<Vec<_>>();
+
+ let mut mails = vec![];
+ let iter_strat = sequence::Strategy::Naive {
+ largest: NonZeroU32::try_from((self.known_state.idx_by_uid.len() + 1) as u32).unwrap(),
+ };
+ for i in sequence_set.iter(iter_strat) {
+ if let Some(mail) = mail_vec.get(i.get() as usize - 1) {
+ mails.push((i, mail.0, mail.1));
+ } else {
+ bail!("No such mail: {}", i);
+ }
+ }
+
+ Ok(mails)
+ }
+
+ // ----
+
/// Produce an OK [UIDVALIDITY _] message corresponding to `known_state`
fn uidvalidity_status(&self) -> Result<Body> {
let uid_validity = Status::ok(
@@ -420,7 +488,21 @@ impl MailboxView {
fn string_to_flag(f: &str) -> Option<Flag> {
match f.chars().next() {
- Some('\\') => None,
+ Some('\\') => match f {
+ "\\Seen" => Some(Flag::Seen),
+ "\\Answered" => Some(Flag::Answered),
+ "\\Flagged" => Some(Flag::Flagged),
+ "\\Deleted" => Some(Flag::Deleted),
+ "\\Draft" => Some(Flag::Draft),
+ "\\Recent" => Some(Flag::Recent),
+ _ => match Atom::try_from(f.strip_prefix('\\').unwrap().clone()) {
+ Err(_) => {
+ tracing::error!(flag=%f, "Unable to encode flag as IMAP atom");
+ None
+ }
+ Ok(a) => Some(Flag::Extension(a)),
+ },
+ },
Some('$') if f == "$unseen" => None,
Some(_) => match Atom::try_from(f.clone()) {
Err(_) => {