summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorManos Pitsidianakis <el13635@mail.ntua.gr>2020-09-21 19:13:44 +0300
committerManos Pitsidianakis <el13635@mail.ntua.gr>2020-09-23 10:52:19 +0300
commit425f4b9930048b1a1b6e8f240a4196e2aab82fc0 (patch)
tree8b72a5048d8b981e816e3115454af2f4f585e26e
parent19d4a191d81c2f2225d8825d6645f061eda84468 (diff)
melib/jmap: add Type parameter to Id, State
Make Id, State have a type parameter to the object it refers to (eg `Id<EmailObject>`) instead of just a String
-rw-r--r--melib/src/backends/jmap.rs79
-rw-r--r--melib/src/backends/jmap/connection.rs37
-rw-r--r--melib/src/backends/jmap/mailbox.rs7
-rw-r--r--melib/src/backends/jmap/objects/email.rs49
-rw-r--r--melib/src/backends/jmap/objects/email/import.rs42
-rw-r--r--melib/src/backends/jmap/objects/mailbox.rs12
-rw-r--r--melib/src/backends/jmap/protocol.rs39
-rw-r--r--melib/src/backends/jmap/rfc8620.rs313
8 files changed, 375 insertions, 203 deletions
diff --git a/melib/src/backends/jmap.rs b/melib/src/backends/jmap.rs
index 31e58ab3..cf1f7c3a 100644
--- a/melib/src/backends/jmap.rs
+++ b/melib/src/backends/jmap.rs
@@ -191,15 +191,16 @@ macro_rules! get_conf_val {
pub struct Store {
pub account_name: Arc<String>,
pub account_hash: AccountHash,
- pub account_id: Arc<Mutex<String>>,
+ pub account_id: Arc<Mutex<Id<Account>>>,
pub byte_cache: Arc<Mutex<HashMap<EnvelopeHash, EnvelopeCache>>>,
- pub id_store: Arc<Mutex<HashMap<EnvelopeHash, Id>>>,
- pub reverse_id_store: Arc<Mutex<HashMap<Id, EnvelopeHash>>>,
- pub blob_id_store: Arc<Mutex<HashMap<EnvelopeHash, Id>>>,
+ pub id_store: Arc<Mutex<HashMap<EnvelopeHash, Id<EmailObject>>>>,
+ pub reverse_id_store: Arc<Mutex<HashMap<Id<EmailObject>, EnvelopeHash>>>,
+ pub blob_id_store: Arc<Mutex<HashMap<EnvelopeHash, Id<BlobObject>>>>,
pub tag_index: Arc<RwLock<BTreeMap<u64, String>>>,
pub mailboxes: Arc<RwLock<HashMap<MailboxHash, JmapMailbox>>>,
pub mailboxes_index: Arc<RwLock<HashMap<MailboxHash, HashSet<EnvelopeHash>>>>,
- pub object_set_states: Arc<Mutex<HashMap<&'static str, String>>>,
+ pub email_state: Arc<Mutex<State<EmailObject>>>,
+ pub mailbox_state: Arc<Mutex<State<MailboxObject>>>,
pub online_status: Arc<FutureMutex<(Instant, Result<()>)>>,
pub is_subscribed: Arc<IsSubscribedFn>,
pub event_consumer: BackendEventConsumer,
@@ -275,7 +276,7 @@ impl Store {
pub fn remove_envelope(
&self,
- obj_id: Id,
+ obj_id: Id<EmailObject>,
) -> Option<(EnvelopeHash, SmallVec<[MailboxHash; 8]>)> {
let env_hash = self.reverse_id_store.lock().unwrap().remove(&obj_id)?;
self.id_store.lock().unwrap().remove(&env_hash);
@@ -413,7 +414,7 @@ impl MailBackend for JmapType {
)
.await?;
- let mailbox_id: String = {
+ let mailbox_id: Id<MailboxObject> = {
let mailboxes_lck = store.mailboxes.read().unwrap();
if let Some(mailbox) = mailboxes_lck.get(&mailbox_hash) {
mailbox.id.clone()
@@ -428,7 +429,7 @@ impl MailBackend for JmapType {
let upload_response: UploadResponse = serde_json::from_str(&res_text)?;
let mut req = Request::new(conn.request_no.clone());
- let creation_id = "1".to_string();
+ let creation_id: Id<EmailObject> = "1".to_string().into();
let mut email_imports = HashMap::default();
let mut mailbox_ids = HashMap::default();
mailbox_ids.insert(mailbox_id, true);
@@ -440,7 +441,7 @@ impl MailBackend for JmapType {
);
let import_call: ImportCall = ImportCall::new()
- .account_id(conn.mail_account_id().to_string())
+ .account_id(conn.mail_account_id().clone())
.emails(email_imports);
req.add_call(&import_call);
@@ -508,7 +509,7 @@ impl MailBackend for JmapType {
conn.connect().await?;
let email_call: EmailQuery = EmailQuery::new(
Query::new()
- .account_id(conn.mail_account_id().to_string())
+ .account_id(conn.mail_account_id().clone())
.filter(Some(filter))
.position(0),
)
@@ -527,14 +528,7 @@ impl MailBackend for JmapType {
*store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
let m = QueryResponse::<EmailObject>::try_from(v.method_responses.remove(0))?;
let QueryResponse::<EmailObject> { ids, .. } = m;
- let ret = ids
- .into_iter()
- .map(|id| {
- let mut h = DefaultHasher::new();
- h.write(id.as_bytes());
- h.finish()
- })
- .collect();
+ let ret = ids.into_iter().map(|id| id.into_hash()).collect();
Ok(ret)
}))
}
@@ -584,9 +578,9 @@ impl MailBackend for JmapType {
mailboxes_lck[&destination_mailbox_hash].id.clone(),
)
};
- let mut update_map: HashMap<String, Value> = HashMap::default();
- let mut ids: Vec<Id> = Vec::with_capacity(env_hashes.rest.len() + 1);
- let mut id_map: HashMap<Id, EnvelopeHash> = HashMap::default();
+ let mut update_map: HashMap<Id<EmailObject>, Value> = HashMap::default();
+ let mut ids: Vec<Id<EmailObject>> = Vec::with_capacity(env_hashes.rest.len() + 1);
+ let mut id_map: HashMap<Id<EmailObject>, EnvelopeHash> = HashMap::default();
let mut update_keywords: HashMap<String, Value> = HashMap::default();
update_keywords.insert(
format!("mailboxIds/{}", &destination_mailbox_id),
@@ -594,7 +588,7 @@ impl MailBackend for JmapType {
);
if move_ {
update_keywords.insert(
- format!("mailboxIds/{}", &source_mailbox_hash),
+ format!("mailboxIds/{}", &source_mailbox_id),
serde_json::json!(null),
);
}
@@ -611,7 +605,7 @@ impl MailBackend for JmapType {
let email_set_call: EmailSet = EmailSet::new(
Set::<EmailObject>::new()
- .account_id(conn.mail_account_id().to_string())
+ .account_id(conn.mail_account_id().clone())
.update(Some(update_map)),
);
@@ -653,9 +647,9 @@ impl MailBackend for JmapType {
let connection = self.connection.clone();
Ok(Box::pin(async move {
let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
- let mut update_map: HashMap<String, Value> = HashMap::default();
- let mut ids: Vec<Id> = Vec::with_capacity(env_hashes.rest.len() + 1);
- let mut id_map: HashMap<Id, EnvelopeHash> = HashMap::default();
+ let mut update_map: HashMap<Id<EmailObject>, Value> = HashMap::default();
+ let mut ids: Vec<Id<EmailObject>> = Vec::with_capacity(env_hashes.rest.len() + 1);
+ let mut id_map: HashMap<Id<EmailObject>, EnvelopeHash> = HashMap::default();
let mut update_keywords: HashMap<String, Value> = HashMap::default();
for (flag, value) in flags.iter() {
match flag {
@@ -701,11 +695,11 @@ impl MailBackend for JmapType {
}
}
}
- let conn = connection.lock().await;
+ let mut conn = connection.lock().await;
let email_set_call: EmailSet = EmailSet::new(
Set::<EmailObject>::new()
- .account_id(conn.mail_account_id().to_string())
+ .account_id(conn.mail_account_id().clone())
.update(Some(update_map)),
);
@@ -714,7 +708,7 @@ impl MailBackend for JmapType {
let email_call: EmailGet = EmailGet::new(
Get::new()
.ids(Some(JmapArgument::Value(ids)))
- .account_id(conn.mail_account_id().to_string())
+ .account_id(conn.mail_account_id().clone())
.properties(Some(vec!["keywords".to_string()])),
);
@@ -759,21 +753,15 @@ impl MailBackend for JmapType {
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
let GetResponse::<EmailObject> { list, state, .. } = e;
{
- let c = store
- .object_set_states
- .lock()
- .unwrap()
- .get(&EmailObject::NAME)
- .map(|prev_state| *prev_state == state);
- if let Some(false) = c {
- conn.email_changes().await?;
- } else {
+ let (is_empty, is_equal) = {
+ let current_state_lck = conn.store.email_state.lock().unwrap();
+ (current_state_lck.is_empty(), *current_state_lck != state)
+ };
+ if is_empty {
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
- store
- .object_set_states
- .lock()
- .unwrap()
- .insert(EmailObject::NAME, state);
+ *conn.store.email_state.lock().unwrap() = state;
+ } else if !is_equal {
+ conn.email_changes().await?;
}
}
debug!(&list);
@@ -813,7 +801,7 @@ impl JmapType {
let store = Arc::new(Store {
account_name: Arc::new(s.name.clone()),
account_hash,
- account_id: Arc::new(Mutex::new(String::new())),
+ account_id: Arc::new(Mutex::new(Id::new())),
online_status,
event_consumer,
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
@@ -825,7 +813,8 @@ impl JmapType {
tag_index: Default::default(),
mailboxes: Default::default(),
mailboxes_index: Default::default(),
- object_set_states: Default::default(),
+ email_state: Default::default(),
+ mailbox_state: Default::default(),
});
Ok(Box::new(JmapType {
diff --git a/melib/src/backends/jmap/connection.rs b/melib/src/backends/jmap/connection.rs
index af46bba6..97ad15c6 100644
--- a/melib/src/backends/jmap/connection.rs
+++ b/melib/src/backends/jmap/connection.rs
@@ -100,7 +100,7 @@ impl JmapConnection {
Ok(())
}
- pub fn mail_account_id(&self) -> &Id {
+ pub fn mail_account_id(&self) -> &Id<Account> {
&self.session.primary_accounts["urn:ietf:params:jmap:mail"]
}
@@ -109,22 +109,16 @@ impl JmapConnection {
}
pub async fn email_changes(&self) -> Result<()> {
- let mut current_state: String = {
- let object_set_states_lck = self.store.object_set_states.lock().unwrap();
- let v = if let Some(prev_state) = debug!(object_set_states_lck.get(&EmailObject::NAME))
- {
- prev_state.clone()
- } else {
- return Ok(());
- };
- drop(object_set_states_lck);
- v
- };
+ let mut current_state: State<EmailObject> = self.store.email_state.lock().unwrap().clone();
+ if current_state.is_empty() {
+ debug!("{:?}: has no saved state", EmailObject::NAME);
+ return Ok(());
+ }
loop {
let email_changes_call: EmailChanges = EmailChanges::new(
Changes::<EmailObject>::new()
- .account_id(self.mail_account_id().to_string())
+ .account_id(self.mail_account_id().clone())
.since_state(current_state.clone()),
);
@@ -136,7 +130,7 @@ impl JmapConnection {
prev_seq,
ResultField::<EmailChanges, EmailObject>::new("created"),
)))
- .account_id(self.mail_account_id().to_string()),
+ .account_id(self.mail_account_id().clone()),
);
req.add_call(&email_get_call);
@@ -153,6 +147,12 @@ impl JmapConnection {
GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
debug!(&get_response);
let GetResponse::<EmailObject> { list, .. } = get_response;
+ let changes_response =
+ ChangesResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
+ if changes_response.new_state == current_state {
+ return Ok(());
+ }
+
let mut mailbox_hashes: Vec<SmallVec<[MailboxHash; 8]>> =
Vec::with_capacity(list.len());
for envobj in &list {
@@ -189,9 +189,6 @@ impl JmapConnection {
}
}
- let changes_response =
- ChangesResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
-
let ChangesResponse::<EmailObject> {
account_id: _,
new_state,
@@ -218,11 +215,7 @@ impl JmapConnection {
if has_more_changes {
current_state = new_state;
} else {
- self.store
- .object_set_states
- .lock()
- .unwrap()
- .insert(EmailObject::NAME, new_state);
+ *self.store.email_state.lock().unwrap() = new_state;
break;
}
}
diff --git a/melib/src/backends/jmap/mailbox.rs b/melib/src/backends/jmap/mailbox.rs
index cf1987a4..33e87637 100644
--- a/melib/src/backends/jmap/mailbox.rs
+++ b/melib/src/backends/jmap/mailbox.rs
@@ -29,10 +29,11 @@ pub struct JmapMailbox {
pub path: String,
pub hash: MailboxHash,
pub children: Vec<MailboxHash>,
- pub id: String,
+ pub id: Id<MailboxObject>,
pub is_subscribed: bool,
pub my_rights: JmapRights,
- pub parent_id: Option<String>,
+ pub parent_id: Option<Id<MailboxObject>>,
+ pub parent_hash: Option<MailboxHash>,
pub role: Option<String>,
pub sort_order: u64,
pub total_emails: Arc<Mutex<u64>>,
@@ -66,7 +67,7 @@ impl BackendMailbox for JmapMailbox {
}
fn parent(&self) -> Option<MailboxHash> {
- None
+ self.parent_hash
}
fn permissions(&self) -> MailboxPermissions {
diff --git a/melib/src/backends/jmap/objects/email.rs b/melib/src/backends/jmap/objects/email.rs
index 415b62bc..6e2feda4 100644
--- a/melib/src/backends/jmap/objects/email.rs
+++ b/melib/src/backends/jmap/objects/email.rs
@@ -32,6 +32,21 @@ use std::hash::Hasher;
mod import;
pub use import::*;
+#[derive(Debug)]
+pub struct ThreadObject;
+
+impl Object for ThreadObject {
+ const NAME: &'static str = "Thread";
+}
+
+impl Id<EmailObject> {
+ pub fn into_hash(&self) -> EnvelopeHash {
+ let mut h = DefaultHasher::new();
+ h.write(self.inner.as_bytes());
+ h.finish()
+ }
+}
+
// 4.1.1.
// Metadata
// These properties represent metadata about the message in the mail
@@ -133,11 +148,11 @@ pub use import::*;
#[serde(rename_all = "camelCase")]
pub struct EmailObject {
#[serde(default)]
- pub id: Id,
+ pub id: Id<EmailObject>,
#[serde(default)]
- pub blob_id: String,
+ pub blob_id: Id<BlobObject>,
#[serde(default)]
- pub mailbox_ids: HashMap<Id, bool>,
+ pub mailbox_ids: HashMap<Id<MailboxObject>, bool>,
#[serde(default)]
pub size: u64,
#[serde(default)]
@@ -163,7 +178,7 @@ pub struct EmailObject {
#[serde(default)]
pub keywords: HashMap<String, bool>,
#[serde(default)]
- pub attached_emails: Option<Id>,
+ pub attached_emails: Option<Id<BlobObject>>,
#[serde(default)]
pub attachments: Vec<Value>,
#[serde(default)]
@@ -182,7 +197,7 @@ pub struct EmailObject {
#[serde(default)]
pub text_body: Vec<TextBody>,
#[serde(default)]
- pub thread_id: Id,
+ pub thread_id: Id<ThreadObject>,
#[serde(flatten)]
pub extra: HashMap<String, Value>,
}
@@ -313,9 +328,7 @@ impl std::convert::From<EmailObject> for crate::Envelope {
}
}
- let mut h = DefaultHasher::new();
- h.write(t.id.as_bytes());
- env.set_hash(h.finish());
+ env.set_hash(t.id.into_hash());
env
}
}
@@ -323,7 +336,7 @@ impl std::convert::From<EmailObject> for crate::Envelope {
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct HtmlBody {
- pub blob_id: Id,
+ pub blob_id: Id<BlobObject>,
#[serde(default)]
pub charset: String,
#[serde(default)]
@@ -350,7 +363,7 @@ pub struct HtmlBody {
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TextBody {
- pub blob_id: Id,
+ pub blob_id: Id<BlobObject>,
#[serde(default)]
pub charset: String,
#[serde(default)]
@@ -381,12 +394,12 @@ impl Object for EmailObject {
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct EmailQueryResponse {
- pub account_id: Id,
+ pub account_id: Id<Account>,
pub can_calculate_changes: bool,
pub collapse_threads: bool,
// FIXME
pub filter: String,
- pub ids: Vec<Id>,
+ pub ids: Vec<Id<EmailObject>>,
pub position: u64,
pub query_state: String,
pub sort: Option<String>,
@@ -469,9 +482,9 @@ impl EmailGet {
#[serde(rename_all = "camelCase")]
pub struct EmailFilterCondition {
#[serde(skip_serializing_if = "Option::is_none")]
- pub in_mailbox: Option<Id>,
+ pub in_mailbox: Option<Id<MailboxObject>>,
#[serde(skip_serializing_if = "Vec::is_empty")]
- pub in_mailbox_other_than: Vec<Id>,
+ pub in_mailbox_other_than: Vec<Id<MailboxObject>>,
#[serde(skip_serializing_if = "String::is_empty")]
pub before: UtcDate,
#[serde(skip_serializing_if = "String::is_empty")]
@@ -517,8 +530,8 @@ impl EmailFilterCondition {
Self::default()
}
- _impl!(in_mailbox: Option<Id>);
- _impl!(in_mailbox_other_than: Vec<Id>);
+ _impl!(in_mailbox: Option<Id<MailboxObject>>);
+ _impl!(in_mailbox_other_than: Vec<Id<MailboxObject>>);
_impl!(before: UtcDate);
_impl!(after: UtcDate);
_impl!(min_size: Option<u64>);
@@ -728,7 +741,7 @@ fn test_jmap_query() {
let mut r = Filter::Condition(
EmailFilterCondition::new()
- .in_mailbox(Some(mailbox_id))
+ .in_mailbox(Some(mailbox_id.into()))
.into(),
);
r &= f;
@@ -737,7 +750,7 @@ fn test_jmap_query() {
let email_call: EmailQuery = EmailQuery::new(
Query::new()
- .account_id("account_id".to_string())
+ .account_id("account_id".to_string().into())
.filter(Some(filter))
.position(0),
)
diff --git a/melib/src/backends/jmap/objects/email/import.rs b/melib/src/backends/jmap/objects/email/import.rs
index 5fd0895a..638c58c3 100644
--- a/melib/src/backends/jmap/objects/email/import.rs
+++ b/melib/src/backends/jmap/objects/email/import.rs
@@ -37,7 +37,7 @@ use serde_json::value::RawValue;
pub struct ImportCall {
///accountId: "Id"
///The id of the account to use.
- pub account_id: String,
+ pub account_id: Id<Account>,
///ifInState: "String|null"
///This is a state string as returned by the "Email/get" method. If
///supplied, the string must match the current state of the account
@@ -45,10 +45,10 @@ pub struct ImportCall {
///and a "stateMismatch" error returned. If null, any changes will
///be applied to the current state.
#[serde(skip_serializing_if = "Option::is_none")]
- pub if_in_state: Option<String>,
+ pub if_in_state: Option<State<EmailObject>>,
///o emails: "Id[EmailImport]"
///A map of creation id (client specified) to EmailImport objects.
- pub emails: HashMap<Id, EmailImport>,
+ pub emails: HashMap<Id<EmailObject>, EmailImport>,
}
#[derive(Deserialize, Serialize, Debug)]
@@ -56,11 +56,11 @@ pub struct ImportCall {
pub struct EmailImport {
///o blobId: "Id"
///The id of the blob containing the raw message [RFC5322].
- pub blob_id: String,
+ pub blob_id: Id<BlobObject>,
///o mailboxIds: "Id[Boolean]"
///The ids of the Mailboxes to assign this Email to. At least one
///Mailbox MUST be given.
- pub mailbox_ids: HashMap<Id, bool>,
+ pub mailbox_ids: HashMap<Id<MailboxObject>, bool>,
///o keywords: "String[Boolean]" (default: {})
///The keywords to apply to the Email.
pub keywords: HashMap<String, bool>,
@@ -74,7 +74,7 @@ pub struct EmailImport {
impl ImportCall {
pub fn new() -> Self {
Self {
- account_id: String::new(),
+ account_id: Id::new(),
if_in_state: None,
emails: HashMap::default(),
}
@@ -85,10 +85,10 @@ impl ImportCall {
///
/// The id of the account to use.
///
- account_id: String
+ account_id: Id<Account>
);
- _impl!(if_in_state: Option<String>);
- _impl!(emails: HashMap<Id, EmailImport>);
+ _impl!(if_in_state: Option<State<EmailObject>>);
+ _impl!(emails: HashMap<Id<EmailObject>, EmailImport>);
}
impl Method<EmailObject> for ImportCall {
@@ -98,15 +98,15 @@ impl Method<EmailObject> for ImportCall {
impl EmailImport {
pub fn new() -> Self {
Self {
- blob_id: String::new(),
+ blob_id: Id::new(),
mailbox_ids: HashMap::default(),
keywords: HashMap::default(),
received_at: None,
}
}
- _impl!(blob_id: String);
- _impl!(mailbox_ids: HashMap<Id, bool>);
+ _impl!(blob_id: Id<BlobObject>);
+ _impl!(mailbox_ids: HashMap<Id<MailboxObject>, bool>);
_impl!(keywords: HashMap<String, bool>);
_impl!(received_at: Option<String>);
}
@@ -126,7 +126,7 @@ pub enum ImportError {
///the SetError object with the id of the existing Email. If duplicates
///are allowed, the newly created Email object MUST have a separate id
///and independent mutable properties to the existing object.
- existing_id: Id,
+ existing_id: Id<EmailObject>,
},
///If the "blobId", "mailboxIds", or "keywords" properties are invalid
///(e.g., missing, wrong type, id not found), the server MUST reject the
@@ -155,30 +155,30 @@ pub enum ImportError {
pub struct ImportResponse {
///o accountId: "Id"
///The id of the account used for this call.
- pub account_id: Id,
+ pub account_id: Id<Account>,
///o oldState: "String|null"
///The state string that would have been returned by "Email/get" on
///this account before making the requested changes, or null if the
///server doesn't know what the previous state string was.
- pub old_state: Option<String>,
+ pub old_state: Option<State<EmailObject>>,
///o newState: "String"
///The state string that will now be returned by "Email/get" on this
///account.
- pub new_state: Option<String>,
+ pub new_state: Option<State<EmailObject>>,
///o created: "Id[Email]|null"
///A map of the creation id to an object containing the "id",
///"blobId", "threadId", and "size" properties for each successfully
///imported Email, or null if none.
- pub created: HashMap<Id, ImportEmailResult>,
+ pub created: HashMap<Id<EmailObject>, ImportEmailResult>,
///o notCreated: "Id[SetError]|null"
///A map of the creation id to a SetError object for each Email that
///failed to be created, or null if all successful. The possible
///errors are defined above.
- pub not_created: HashMap<Id, ImportError>,
+ pub not_created: HashMap<Id<EmailObject>, ImportError>,
}
impl std::convert::TryFrom<&RawValue> for ImportResponse {
@@ -193,8 +193,8 @@ impl std::convert::TryFrom<&RawValue> for ImportResponse {
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ImportEmailResult {
- pub id: Id,
- pub blob_id: Id,
- pub thread_id: Id,
+ pub id: Id<EmailObject>,
+ pub blob_id: Id<BlobObject>,
+ pub thread_id: Id<ThreadObject>,
pub size: usize,
}
diff --git a/melib/src/backends/jmap/objects/mailbox.rs b/melib/src/backends/jmap/objects/mailbox.rs
index 1c41e392..9a25b4ec 100644
--- a/melib/src/backends/jmap/objects/mailbox.rs
+++ b/melib/src/backends/jmap/objects/mailbox.rs
@@ -21,14 +21,22 @@
use super::*;
+impl Id<MailboxObject> {
+ pub fn into_hash(&self) -> MailboxHash {
+ let mut h = DefaultHasher::new();
+ h.write(self.inner.as_bytes());
+ h.finish()
+ }
+}
+
#[derive(Deserialize, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct MailboxObject {
- pub id: String,
+ pub id: Id<MailboxObject>,
pub is_subscribed: bool,
pub my_rights: JmapRights,
pub name: String,
- pub parent_id: Option<String>,
+ pub parent_id: Option<Id<MailboxObject>>,
pub role: Option<String>,
pub sort_order: u64,
pub total_emails: u64,
diff --git a/melib/src/backends/jmap/protocol.rs b/melib/src/backends/jmap/protocol.rs
index cf6b3d0c..382a56e2 100644
--- a/melib/src/backends/jmap/protocol.rs
+++ b/melib/src/backends/jmap/protocol.rs
@@ -25,7 +25,6 @@ use serde::Serialize;
use serde_json::{json, Value};
use std::convert::TryFrom;
-pub type Id = String;
pub type UtcDate = String;
use super::rfc8620::Object;
@@ -125,7 +124,8 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
unread_emails,
unread_threads,
} = r;
- let hash = crate::get_path_hash!(&name);
+ let hash = id.into_hash();
+ let parent_hash = parent_id.clone().map(|id| id.into_hash());
(
hash,
JmapMailbox {
@@ -137,6 +137,7 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
is_subscribed,
my_rights,
parent_id,
+ parent_hash,
role,
usage: Default::default(),
sort_order,
@@ -150,10 +151,13 @@ pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash,
.collect())
}
-pub async fn get_message_list(conn: &JmapConnection, mailbox: &JmapMailbox) -> Result<Vec<String>> {
+pub async fn get_message_list(
+ conn: &JmapConnection,
+ mailbox: &JmapMailbox,
+) -> Result<Vec<Id<EmailObject>>> {
let email_call: EmailQuery = EmailQuery::new(
Query::new()
- .account_id(conn.mail_account_id().to_string())
+ .account_id(conn.mail_account_id().clone())
.filter(Some(Filter::Condition(
EmailFilterCondition::new()
.in_mailbox(Some(mailbox.id.clone()))
@@ -213,7 +217,7 @@ pub async fn fetch(
let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
let email_query_call: EmailQuery = EmailQuery::new(
Query::new()
- .account_id(conn.mail_account_id().to_string())
+ .account_id(conn.mail_account_id().clone())
.filter(Some(Filter::Condition(
EmailFilterCondition::new()
.in_mailbox(Some(mailbox_id))
@@ -232,7 +236,7 @@ pub async fn fetch(
prev_seq,
EmailQuery::RESULT_FIELD_IDS,
)))
- .account_id(conn.mail_account_id().to_string()),
+ .account_id(conn.mail_account_id().clone()),
);
req.add_call(&email_call);
@@ -248,22 +252,15 @@ pub async fn fetch(
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
let GetResponse::<EmailObject> { list, state, .. } = e;
{
- let v = conn
- .store
- .object_set_states
- .lock()
- .unwrap()
- .get(&EmailObject::NAME)
- .map(|prev_state| *prev_state == state);
- if let Some(false) = v {
- conn.email_changes().await?;
- } else {
+ let (is_empty, is_equal) = {
+ let current_state_lck = conn.store.email_state.lock().unwrap();
+ (current_state_lck.is_empty(), *current_state_lck != state)
+ };
+ if is_empty {
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
- conn.store
- .object_set_states
- .lock()
- .unwrap()
- .insert(EmailObject::NAME, state);
+ *conn.store.email_state.lock().unwrap() = state;
+ } else if !is_equal {
+ conn.email_changes().await?;
}
}
let mut ret = Vec::with_capacity(list.len());
diff --git a/melib/src/backends/jmap/rfc8620.rs b/melib/src/backends/jmap/rfc8620.rs
index 863221ee..d49bb593 100644
--- a/melib/src/backends/jmap/rfc8620.rs
+++ b/melib/src/backends/jmap/rfc8620.rs
@@ -19,12 +19,12 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
-use super::Id;
use crate::email::parser::BytesExt;
use core::marker::PhantomData;
use serde::de::DeserializeOwned;
use serde::ser::{Serialize, SerializeStruct, Serializer};
use serde_json::{value::RawValue, Value};
+use std::hash::{Hash, Hasher};
mod filters;
pub use filters::*;
@@ -39,23 +39,189 @@ pub trait Object {
const NAME: &'static str;
}
+#[derive(Deserialize, Serialize)]
+#[serde(transparent)]
+pub struct Id<OBJ> {
+ pub inner: String,
+ #[serde(skip)]
+ pub _ph: PhantomData<fn() -> OBJ>,
+}
+
+impl<OBJ: Object> core::fmt::Debug for Id<OBJ> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple(&format!("Id<{}>", OBJ::NAME))
+ .field(&self.inner)
+ .finish()
+ }
+}
+
+impl core::fmt::Debug for Id<String> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ f.debug_tuple("Id<Any>").field(&self.inner).finish()
+ }
+}
+
+//, Hash, Eq, PartialEq, Default)]
+impl<OBJ> Clone for Id<OBJ> {
+ fn clone(&self) -> Self {
+ Id {
+ inner: self.inner.clone(),
+ _ph: PhantomData,
+ }
+ }
+}
+
+impl<OBJ> std::cmp::Eq for Id<OBJ> {}
+
+impl<OBJ> std::cmp::PartialEq for Id<OBJ> {
+ fn eq(&self, other: &Self) -> bool {
+ self.inner == other.inner
+ }
+}
+
+impl<OBJ> Hash for Id<OBJ> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.inner.hash(state);
+ }
+}
+
+impl<OBJ> Default for Id<OBJ> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<OBJ> From<String> for Id<OBJ> {
+ fn from(inner: String) -> Self {
+ Id {
+ inner,
+ _ph: PhantomData,
+ }
+ }
+}
+
+impl<OBJ> core::fmt::Display for Id<OBJ> {
+ fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
+ core::fmt::Display::fmt(&self.inner, fmt)
+ }
+}
+
+impl<OBJ> Id<OBJ> {
+ pub fn new() -> Self {
+ Self {
+ inner: String::new(),
+ _ph: PhantomData,
+ }
+ }
+
+ pub fn as_str(&self) -> &str {
+ self.inner.as_str()
+ }
+
+ pub fn len(&self) -> usize {
+ self.inner.len()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.inner.is_empty()
+ }
+}
+
+#[derive(Deserialize, Serialize, Debug)]
+#[serde(transparent)]
+pub struct State<OBJ> {
+ pub inner: String,
+ #[serde(skip)]
+ pub _ph: PhantomData<fn() -> OBJ>,
+}
+
+//, Hash, Eq, PartialEq, Default)]
+impl<OBJ> Clone for State<OBJ> {
+ fn clone(&self) -> Self {
+ State {
+ inner: self.inner.clone(),
+ _ph: PhantomData,
+ }
+ }
+}
+
+impl<OBJ> std::cmp::Eq for State<OBJ> {}
+
+impl<OBJ> std::cmp::PartialEq for State<OBJ> {
+ fn eq(&self, other: &Self) -> bool {
+ self.inner == other.inner
+ }
+}
+
+impl<OBJ> Hash for State<OBJ> {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.inner.hash(state);
+ }
+}
+
+impl<OBJ> Default for State<OBJ> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<OBJ> From<String> for State<OBJ> {
+ fn from(inner: String) -> Self {
+ State {
+ inner,
+ _ph: PhantomData,
+ }
+ }
+}
+
+impl<OBJ> core::fmt::Display for State<OBJ> {
+ fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
+ core::fmt::Display::fmt(&self.inner, fmt)
+ }
+}
+
+impl<OBJ> State<OBJ> {
+ pub fn new() -> Self {
+ Self {
+ inner: String::new(),
+ _ph: PhantomData,
+ }
+ }
+
+ pub fn as_str(&self) -> &str {
+ self.inner.as_str()
+ }
+
+ pub fn len(&self) -> usize {
+ self.inner.len()
+ }
+
+ pub fn is_empty(&self) -> bool {
+ self.inner.is_empty()
+ }
+}
+
#[derive(Deserialize, Serialize, Debug, Default)]
#[serde(rename_all = "camelCase")]
pub struct JmapSession {
pub capabilities: HashMap<String, CapabilitiesObject>,
- pub accounts: HashMap<Id, Account>,
- pub primary_accounts: HashMap<String, Id>,
+ pub accounts: HashMap<Id<Account>, Account>,
+ pub primary_accounts: HashMap<String, Id<Account>>,
pub username: String,
pub api_url: String,
pub download_url: String,