melib/jmap: respect max_objects_in_get when fetching email
parent
88a1f0d4bc
commit
e64f2077a8
|
@ -72,6 +72,9 @@ macro_rules! _impl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const JMAP_CORE_CAPABILITY: &str = "urn:ietf:params:jmap:core";
|
||||||
|
pub const JMAP_MAIL_CAPABILITY: &str = "urn:ietf:params:jmap:mail";
|
||||||
|
|
||||||
pub mod operations;
|
pub mod operations;
|
||||||
use operations::*;
|
use operations::*;
|
||||||
|
|
||||||
|
@ -196,6 +199,7 @@ pub struct Store {
|
||||||
pub mailbox_state: Arc<Mutex<State<MailboxObject>>>,
|
pub mailbox_state: Arc<Mutex<State<MailboxObject>>>,
|
||||||
pub online_status: Arc<FutureMutex<(Instant, Result<()>)>>,
|
pub online_status: Arc<FutureMutex<(Instant, Result<()>)>>,
|
||||||
pub is_subscribed: Arc<IsSubscribedFn>,
|
pub is_subscribed: Arc<IsSubscribedFn>,
|
||||||
|
pub core_capabilities: Arc<Mutex<rfc8620::CapabilitiesObject>>,
|
||||||
pub event_consumer: BackendEventConsumer,
|
pub event_consumer: BackendEventConsumer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,15 +334,19 @@ impl MailBackend for JmapType {
|
||||||
Ok(Box::pin(async_stream::try_stream! {
|
Ok(Box::pin(async_stream::try_stream! {
|
||||||
let mut conn = connection.lock().await;
|
let mut conn = connection.lock().await;
|
||||||
conn.connect().await?;
|
conn.connect().await?;
|
||||||
let res = protocol::fetch(
|
let batch_size: u64 = conn.store.core_capabilities.lock().unwrap().max_objects_in_get;
|
||||||
&conn,
|
let mut fetch_state = protocol::EmailFetchState::Start { batch_size };
|
||||||
&store,
|
loop {
|
||||||
mailbox_hash,
|
let res = fetch_state.fetch(
|
||||||
).await?;
|
&conn,
|
||||||
if res.is_empty() {
|
&store,
|
||||||
return;
|
mailbox_hash,
|
||||||
|
).await?;
|
||||||
|
if res.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
yield res;
|
||||||
}
|
}
|
||||||
yield res;
|
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -889,7 +897,7 @@ impl JmapType {
|
||||||
event_consumer,
|
event_consumer,
|
||||||
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
|
is_subscribed: Arc::new(IsSubscribedFn(is_subscribed)),
|
||||||
collection: Collection::default(),
|
collection: Collection::default(),
|
||||||
|
core_capabilities: Arc::new(Mutex::new(rfc8620::CapabilitiesObject::default())),
|
||||||
byte_cache: Default::default(),
|
byte_cache: Default::default(),
|
||||||
id_store: Default::default(),
|
id_store: Default::default(),
|
||||||
reverse_id_store: Default::default(),
|
reverse_id_store: Default::default(),
|
||||||
|
|
|
@ -88,19 +88,15 @@ impl JmapConnection {
|
||||||
}
|
}
|
||||||
Ok(s) => s,
|
Ok(s) => s,
|
||||||
};
|
};
|
||||||
if !session
|
if !session.capabilities.contains_key(JMAP_CORE_CAPABILITY) {
|
||||||
.capabilities
|
let err = MeliError::new(format!("Server {} did not return JMAP Core capability ({core_capability}). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", "), core_capability=JMAP_CORE_CAPABILITY));
|
||||||
.contains_key("urn:ietf:params:jmap:core")
|
|
||||||
{
|
|
||||||
let err = MeliError::new(format!("Server {} did not return JMAP Core capability (urn:ietf:params:jmap:core). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", ")));
|
|
||||||
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
if !session
|
*self.store.core_capabilities.lock().unwrap() =
|
||||||
.capabilities
|
session.capabilities["urn:ietf:params:jmap:core"].clone();
|
||||||
.contains_key("urn:ietf:params:jmap:mail")
|
if !session.capabilities.contains_key(JMAP_MAIL_CAPABILITY) {
|
||||||
{
|
let err = MeliError::new(format!("Server {} does not support JMAP Mail capability ({mail_capability}). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", "), mail_capability=JMAP_MAIL_CAPABILITY));
|
||||||
let err = MeliError::new(format!("Server {} does not support JMAP Mail capability (urn:ietf:params:jmap:mail). Returned capabilities were: {}", &self.server_conf.server_url, session.capabilities.keys().map(String::as_str).collect::<Vec<&str>>().join(", ")));
|
|
||||||
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
|
@ -111,7 +107,7 @@ impl JmapConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn mail_account_id(&self) -> Id<Account> {
|
pub fn mail_account_id(&self) -> Id<Account> {
|
||||||
self.session.lock().unwrap().primary_accounts["urn:ietf:params:jmap:mail"].clone()
|
self.session.lock().unwrap().primary_accounts[JMAP_MAIL_CAPABILITY].clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn session_guard(&'_ self) -> MutexGuard<'_, JmapSession> {
|
pub fn session_guard(&'_ self) -> MutexGuard<'_, JmapSession> {
|
||||||
|
@ -359,4 +355,29 @@ impl JmapConnection {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn send_request(&self, request: String) -> Result<String> {
|
||||||
|
let api_url = self.session.lock().unwrap().api_url.clone();
|
||||||
|
let mut res = self.client.post_async(api_url.as_str(), request).await?;
|
||||||
|
|
||||||
|
let res_text = res.text().await?;
|
||||||
|
debug!(&res_text);
|
||||||
|
let _: MethodResponse = match serde_json::from_str(&res_text) {
|
||||||
|
Err(err) => {
|
||||||
|
let err = MeliError::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &self.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
|
||||||
|
*self.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
||||||
|
crate::log(err.to_string(), crate::ERROR);
|
||||||
|
crate::log(
|
||||||
|
format!(
|
||||||
|
"Tried to deserialize this server response as a MethodResponse json:\n{}",
|
||||||
|
&res_text
|
||||||
|
),
|
||||||
|
crate::DEBUG,
|
||||||
|
);
|
||||||
|
return Err(err);
|
||||||
|
}
|
||||||
|
Ok(s) => s,
|
||||||
|
};
|
||||||
|
Ok(res_text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,3 +26,6 @@ pub use email::*;
|
||||||
|
|
||||||
mod mailbox;
|
mod mailbox;
|
||||||
pub use mailbox::*;
|
pub use mailbox::*;
|
||||||
|
|
||||||
|
mod thread;
|
||||||
|
pub use thread::*;
|
||||||
|
|
|
@ -33,13 +33,6 @@ use std::hash::Hasher;
|
||||||
mod import;
|
mod import;
|
||||||
pub use import::*;
|
pub use import::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ThreadObject;
|
|
||||||
|
|
||||||
impl Object for ThreadObject {
|
|
||||||
const NAME: &'static str = "Thread";
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Id<EmailObject> {
|
impl Id<EmailObject> {
|
||||||
pub fn into_hash(&self) -> EnvelopeHash {
|
pub fn into_hash(&self) -> EnvelopeHash {
|
||||||
let mut h = DefaultHasher::new();
|
let mut h = DefaultHasher::new();
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* meli - jmap module.
|
||||||
|
*
|
||||||
|
* Copyright 2019-2022 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::*;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ThreadObject {
|
||||||
|
#[serde(default)]
|
||||||
|
pub id: Id<ThreadObject>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub email_ids: Vec<Id<EmailObject>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Object for ThreadObject {
|
||||||
|
const NAME: &'static str = "Thread";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadObject {
|
||||||
|
_impl!(get email_ids, email_ids: Vec<Id<EmailObject>>);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ThreadGet {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub get_call: Get<ThreadObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Method<ThreadObject> for ThreadGet {
|
||||||
|
const NAME: &'static str = "Thread/get";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadGet {
|
||||||
|
pub const RESULT_FIELD_THREAD_IDS: ResultField<EmailGet, EmailObject> =
|
||||||
|
ResultField::<EmailGet, EmailObject> {
|
||||||
|
field: "/list/*/threadId",
|
||||||
|
_ph: PhantomData,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn new(get_call: Get<ThreadObject>) -> Self {
|
||||||
|
ThreadGet { get_call }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct ThreadChanges {
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub changes_call: Changes<ThreadObject>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Method<ThreadObject> for ThreadChanges {
|
||||||
|
const NAME: &'static str = "Thread/changes";
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ThreadChanges {
|
||||||
|
pub fn new(changes_call: Changes<ThreadObject>) -> Self {
|
||||||
|
ThreadChanges { changes_call }
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,7 +27,7 @@ use std::convert::{TryFrom, TryInto};
|
||||||
|
|
||||||
pub type UtcDate = String;
|
pub type UtcDate = String;
|
||||||
|
|
||||||
use super::rfc8620::Object;
|
use super::rfc8620::{Object, State};
|
||||||
|
|
||||||
macro_rules! get_request_no {
|
macro_rules! get_request_no {
|
||||||
($lock:expr) => {{
|
($lock:expr) => {{
|
||||||
|
@ -86,30 +86,16 @@ pub struct JsonResponse<'a> {
|
||||||
|
|
||||||
pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, JmapMailbox>> {
|
pub async fn get_mailboxes(conn: &JmapConnection) -> Result<HashMap<MailboxHash, JmapMailbox>> {
|
||||||
let seq = get_request_no!(conn.request_no);
|
let seq = get_request_no!(conn.request_no);
|
||||||
let api_url = conn.session.lock().unwrap().api_url.clone();
|
let res_text = conn
|
||||||
let mut res = conn
|
.send_request(serde_json::to_string(&json!({
|
||||||
.client
|
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
||||||
.post_async(
|
"methodCalls": [["Mailbox/get", {
|
||||||
api_url.as_str(),
|
"accountId": conn.mail_account_id()
|
||||||
serde_json::to_string(&json!({
|
},
|
||||||
"using": ["urn:ietf:params:jmap:core", "urn:ietf:params:jmap:mail"],
|
format!("#m{}",seq).as_str()]],
|
||||||
"methodCalls": [["Mailbox/get", {
|
}))?)
|
||||||
"accountId": conn.mail_account_id()
|
|
||||||
},
|
|
||||||
format!("#m{}",seq).as_str()]],
|
|
||||||
}))?,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||||
let res_text = res.text().await?;
|
|
||||||
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
|
|
||||||
Err(err) => {
|
|
||||||
let err = MeliError::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
|
|
||||||
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
|
||||||
return Err(err);
|
|
||||||
}
|
|
||||||
Ok(s) => s,
|
|
||||||
};
|
|
||||||
*conn.store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
*conn.store.online_status.lock().await = (std::time::Instant::now(), Ok(()));
|
||||||
let m = GetResponse::<MailboxObject>::try_from(v.method_responses.remove(0))?;
|
let m = GetResponse::<MailboxObject>::try_from(v.method_responses.remove(0))?;
|
||||||
let GetResponse::<MailboxObject> {
|
let GetResponse::<MailboxObject> {
|
||||||
|
@ -249,109 +235,131 @@ pub async fn get_message(conn: &JmapConnection, ids: &[String]) -> Result<Vec<En
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub async fn fetch(
|
#[derive(Copy, Clone)]
|
||||||
conn: &JmapConnection,
|
pub enum EmailFetchState {
|
||||||
store: &Store,
|
Start { batch_size: u64 },
|
||||||
mailbox_hash: MailboxHash,
|
Ongoing { position: u64, batch_size: u64 },
|
||||||
) -> Result<Vec<Envelope>> {
|
}
|
||||||
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().clone())
|
|
||||||
.filter(Some(Filter::Condition(
|
|
||||||
EmailFilterCondition::new()
|
|
||||||
.in_mailbox(Some(mailbox_id))
|
|
||||||
.into(),
|
|
||||||
)))
|
|
||||||
.position(0),
|
|
||||||
)
|
|
||||||
.collapse_threads(false);
|
|
||||||
|
|
||||||
let mut req = Request::new(conn.request_no.clone());
|
impl EmailFetchState {
|
||||||
let prev_seq = req.add_call(&email_query_call);
|
pub async fn must_update_state(
|
||||||
|
&mut self,
|
||||||
let email_call: EmailGet = EmailGet::new(
|
conn: &JmapConnection,
|
||||||
Get::new()
|
mailbox_hash: MailboxHash,
|
||||||
.ids(Some(JmapArgument::reference(
|
state: State<EmailObject>,
|
||||||
prev_seq,
|
) -> Result<bool> {
|
||||||
EmailQuery::RESULT_FIELD_IDS,
|
{
|
||||||
)))
|
let (is_empty, is_equal) = {
|
||||||
.account_id(conn.mail_account_id().clone()),
|
let mailboxes_lck = conn.store.mailboxes.read().unwrap();
|
||||||
);
|
mailboxes_lck
|
||||||
|
.get(&mailbox_hash)
|
||||||
req.add_call(&email_call);
|
.map(|mbox| {
|
||||||
|
let current_state_lck = mbox.email_state.lock().unwrap();
|
||||||
let api_url = conn.session.lock().unwrap().api_url.clone();
|
(
|
||||||
let mut res = conn
|
current_state_lck.is_none(),
|
||||||
.client
|
current_state_lck.as_ref() != Some(&state),
|
||||||
.post_async(api_url.as_str(), serde_json::to_string(&req)?)
|
)
|
||||||
.await?;
|
})
|
||||||
|
.unwrap_or((true, true))
|
||||||
let res_text = res.text().await?;
|
};
|
||||||
|
if is_empty {
|
||||||
let mut v: MethodResponse = match serde_json::from_str(&res_text) {
|
let mut mailboxes_lck = conn.store.mailboxes.write().unwrap();
|
||||||
Err(err) => {
|
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
|
||||||
let err = MeliError::new(format!("BUG: Could not deserialize {} server JSON response properly, please report this!\nReply from server: {}", &conn.server_conf.server_url, &res_text)).set_source(Some(Arc::new(err))).set_kind(ErrorKind::Bug);
|
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
|
||||||
*conn.store.online_status.lock().await = (Instant::now(), Err(err.clone()));
|
*mbox.email_state.lock().unwrap() = Some(state);
|
||||||
return Err(err);
|
});
|
||||||
|
} else if !is_equal {
|
||||||
|
conn.email_changes(mailbox_hash).await?;
|
||||||
|
}
|
||||||
|
Ok(is_empty || !is_equal)
|
||||||
}
|
}
|
||||||
Ok(s) => s,
|
}
|
||||||
};
|
|
||||||
let e = GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
pub async fn fetch(
|
||||||
let query_response = QueryResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
&mut self,
|
||||||
store
|
conn: &JmapConnection,
|
||||||
.mailboxes
|
store: &Store,
|
||||||
.write()
|
mailbox_hash: MailboxHash,
|
||||||
.unwrap()
|
) -> Result<Vec<Envelope>> {
|
||||||
.entry(mailbox_hash)
|
loop {
|
||||||
.and_modify(|mbox| {
|
match *self {
|
||||||
*mbox.email_query_state.lock().unwrap() = Some(query_response.query_state);
|
EmailFetchState::Start { batch_size } => {
|
||||||
});
|
*self = EmailFetchState::Ongoing {
|
||||||
let GetResponse::<EmailObject> { list, state, .. } = e;
|
position: 0,
|
||||||
{
|
batch_size,
|
||||||
let (is_empty, is_equal) = {
|
};
|
||||||
let mailboxes_lck = conn.store.mailboxes.read().unwrap();
|
continue;
|
||||||
mailboxes_lck
|
}
|
||||||
.get(&mailbox_hash)
|
EmailFetchState::Ongoing {
|
||||||
.map(|mbox| {
|
mut position,
|
||||||
let current_state_lck = mbox.email_state.lock().unwrap();
|
batch_size,
|
||||||
(
|
} => {
|
||||||
current_state_lck.is_none(),
|
let mailbox_id = store.mailboxes.read().unwrap()[&mailbox_hash].id.clone();
|
||||||
current_state_lck.as_ref() != Some(&state),
|
let email_query_call: EmailQuery = EmailQuery::new(
|
||||||
|
Query::new()
|
||||||
|
.account_id(conn.mail_account_id().clone())
|
||||||
|
.filter(Some(Filter::Condition(
|
||||||
|
EmailFilterCondition::new()
|
||||||
|
.in_mailbox(Some(mailbox_id))
|
||||||
|
.into(),
|
||||||
|
)))
|
||||||
|
.position(position)
|
||||||
|
.limit(Some(batch_size)),
|
||||||
)
|
)
|
||||||
})
|
.collapse_threads(false);
|
||||||
.unwrap_or((true, true))
|
|
||||||
};
|
let mut req = Request::new(conn.request_no.clone());
|
||||||
if is_empty {
|
let prev_seq = req.add_call(&email_query_call);
|
||||||
let mut mailboxes_lck = conn.store.mailboxes.write().unwrap();
|
|
||||||
debug!("{:?}: inserting state {}", EmailObject::NAME, &state);
|
let email_call: EmailGet = EmailGet::new(
|
||||||
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
|
Get::new()
|
||||||
*mbox.email_state.lock().unwrap() = Some(state);
|
.ids(Some(JmapArgument::reference(
|
||||||
});
|
prev_seq,
|
||||||
} else if !is_equal {
|
EmailQuery::RESULT_FIELD_IDS,
|
||||||
conn.email_changes(mailbox_hash).await?;
|
)))
|
||||||
|
.account_id(conn.mail_account_id().clone()),
|
||||||
|
);
|
||||||
|
|
||||||
|
let _prev_seq = req.add_call(&email_call);
|
||||||
|
let res_text = conn.send_request(serde_json::to_string(&req)?).await?;
|
||||||
|
let mut v: MethodResponse = serde_json::from_str(&res_text).unwrap();
|
||||||
|
let e =
|
||||||
|
GetResponse::<EmailObject>::try_from(v.method_responses.pop().unwrap())?;
|
||||||
|
let GetResponse::<EmailObject> { list, state, .. } = e;
|
||||||
|
|
||||||
|
if self.must_update_state(conn, mailbox_hash, state).await? {
|
||||||
|
*self = EmailFetchState::Start { batch_size };
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let mut total = BTreeSet::default();
|
||||||
|
let mut unread = BTreeSet::default();
|
||||||
|
let mut ret = Vec::with_capacity(list.len());
|
||||||
|
for obj in list {
|
||||||
|
let env = store.add_envelope(obj);
|
||||||
|
total.insert(env.hash());
|
||||||
|
if !env.is_seen() {
|
||||||
|
unread.insert(env.hash());
|
||||||
|
}
|
||||||
|
ret.push(env);
|
||||||
|
}
|
||||||
|
let mut mailboxes_lck = store.mailboxes.write().unwrap();
|
||||||
|
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
|
||||||
|
mbox.total_emails.lock().unwrap().insert_existing_set(total);
|
||||||
|
mbox.unread_emails
|
||||||
|
.lock()
|
||||||
|
.unwrap()
|
||||||
|
.insert_existing_set(unread);
|
||||||
|
});
|
||||||
|
position += batch_size;
|
||||||
|
*self = EmailFetchState::Ongoing {
|
||||||
|
position,
|
||||||
|
batch_size,
|
||||||
|
};
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let mut total = BTreeSet::default();
|
|
||||||
let mut unread = BTreeSet::default();
|
|
||||||
let mut ret = Vec::with_capacity(list.len());
|
|
||||||
for obj in list {
|
|
||||||
let env = store.add_envelope(obj);
|
|
||||||
total.insert(env.hash());
|
|
||||||
if !env.is_seen() {
|
|
||||||
unread.insert(env.hash());
|
|
||||||
}
|
|
||||||
ret.push(env);
|
|
||||||
}
|
|
||||||
let mut mailboxes_lck = store.mailboxes.write().unwrap();
|
|
||||||
mailboxes_lck.entry(mailbox_hash).and_modify(|mbox| {
|
|
||||||
mbox.total_emails.lock().unwrap().insert_existing_set(total);
|
|
||||||
mbox.unread_emails
|
|
||||||
.lock()
|
|
||||||
.unwrap()
|
|
||||||
.insert_existing_set(unread);
|
|
||||||
});
|
|
||||||
Ok(ret)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn keywords_to_flags(keywords: Vec<String>) -> (Flag, Vec<String>) {
|
pub fn keywords_to_flags(keywords: Vec<String>) -> (Flag, Vec<String>) {
|
||||||
|
|
|
@ -202,7 +202,7 @@ impl<OBJ> State<OBJ> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, Default)]
|
#[derive(Deserialize, Serialize, Debug, Clone, Default)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct JmapSession {
|
pub struct JmapSession {
|
||||||
pub capabilities: HashMap<String, CapabilitiesObject>,
|
pub capabilities: HashMap<String, CapabilitiesObject>,
|
||||||
|
@ -223,7 +223,7 @@ impl Object for JmapSession {
|
||||||
const NAME: &'static str = "Session";
|
const NAME: &'static str = "Session";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Default, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CapabilitiesObject {
|
pub struct CapabilitiesObject {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
@ -244,7 +244,7 @@ pub struct CapabilitiesObject {
|
||||||
pub collation_algorithms: Vec<String>,
|
pub collation_algorithms: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -259,7 +259,7 @@ impl Object for Account {
|
||||||
const NAME: &'static str = "Account";
|
const NAME: &'static str = "Account";
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub struct BlobObject;
|
pub struct BlobObject;
|
||||||
|
|
||||||
impl Object for BlobObject {
|
impl Object for BlobObject {
|
||||||
|
@ -276,7 +276,7 @@ impl Object for BlobObject {
|
||||||
///
|
///
|
||||||
/// The id of the account to use.
|
/// The id of the account to use.
|
||||||
///
|
///
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Get<OBJ: Object>
|
pub struct Get<OBJ: Object>
|
||||||
where
|
where
|
||||||
|
@ -389,7 +389,7 @@ impl<OBJ: Object + Serialize + std::fmt::Debug> Serialize for Get<OBJ> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct MethodResponse<'a> {
|
pub struct MethodResponse<'a> {
|
||||||
#[serde(borrow)]
|
#[serde(borrow)]
|
||||||
|
@ -400,7 +400,7 @@ pub struct MethodResponse<'a> {
|
||||||
pub session_state: State<JmapSession>,
|
pub session_state: State<JmapSession>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct GetResponse<OBJ: Object> {
|
pub struct GetResponse<OBJ: Object> {
|
||||||
pub account_id: Id<Account>,
|
pub account_id: Id<Account>,
|
||||||
|
@ -427,7 +427,7 @@ impl<OBJ: Object> GetResponse<OBJ> {
|
||||||
_impl!(get_mut not_found_mut, not_found: Vec<Id<OBJ>>);
|
_impl!(get_mut not_found_mut, not_found: Vec<Id<OBJ>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Clone, Copy, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
enum JmapError {
|
enum JmapError {
|
||||||
RequestTooLarge,
|
RequestTooLarge,
|
||||||
|
@ -435,7 +435,7 @@ enum JmapError {
|
||||||
InvalidResultReference,
|
InvalidResultReference,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Query<F: FilterTrait<OBJ>, OBJ: Object>
|
pub struct Query<F: FilterTrait<OBJ>, OBJ: Object>
|
||||||
where
|
where
|
||||||
|
@ -499,7 +499,7 @@ pub fn bool_true() -> bool {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct QueryResponse<OBJ: Object> {
|
pub struct QueryResponse<OBJ: Object> {
|
||||||
pub account_id: Id<Account>,
|
pub account_id: Id<Account>,
|
||||||
|
@ -580,7 +580,7 @@ impl<M: Method<OBJ>, OBJ: Object> ResultField<M, OBJ> {
|
||||||
/// positive integer greater than 0. If a value outside of this range
|
/// positive integer greater than 0. If a value outside of this range
|
||||||
/// is given, the server MUST re
|
/// is given, the server MUST re
|
||||||
///
|
///
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
/* ch-ch-ch-ch-ch-Changes */
|
/* ch-ch-ch-ch-ch-Changes */
|
||||||
pub struct Changes<OBJ: Object>
|
pub struct Changes<OBJ: Object>
|
||||||
|
@ -636,7 +636,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct ChangesResponse<OBJ: Object> {
|
pub struct ChangesResponse<OBJ: Object> {
|
||||||
pub account_id: Id<Account>,
|
pub account_id: Id<Account>,
|
||||||
|
@ -678,7 +678,7 @@ impl<OBJ: Object> ChangesResponse<OBJ> {
|
||||||
///and dependencies that may exist if doing multiple operations at once
|
///and dependencies that may exist if doing multiple operations at once
|
||||||
///(for example, to ensure there is always a minimum number of a certain
|
///(for example, to ensure there is always a minimum number of a certain
|
||||||
///record type).
|
///record type).
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Set<OBJ: Object>
|
pub struct Set<OBJ: Object>
|
||||||
where
|
where
|
||||||
|
@ -788,7 +788,7 @@ where
|
||||||
_impl!(update: Option<HashMap<Id<OBJ>, Value>>);
|
_impl!(update: Option<HashMap<Id<OBJ>, Value>>);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct SetResponse<OBJ: Object> {
|
pub struct SetResponse<OBJ: Object> {
|
||||||
///o accountId: "Id"
|
///o accountId: "Id"
|
||||||
|
@ -859,7 +859,7 @@ impl<OBJ: Object + DeserializeOwned> std::convert::TryFrom<&RawValue> for SetRes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
#[serde(tag = "type", content = "description")]
|
#[serde(tag = "type", content = "description")]
|
||||||
pub enum SetError {
|
pub enum SetError {
|
||||||
|
@ -992,7 +992,7 @@ pub fn upload_request_format(upload_url: &str, account_id: &Id<Account>) -> Stri
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct UploadResponse {
|
pub struct UploadResponse {
|
||||||
///o accountId: "Id"
|
///o accountId: "Id"
|
||||||
|
@ -1024,7 +1024,7 @@ pub struct UploadResponse {
|
||||||
/// The "Foo/queryChanges" method allows a client to efficiently update
|
/// The "Foo/queryChanges" method allows a client to efficiently update
|
||||||
/// the state of a cached query to match the new state on the server. It
|
/// the state of a cached query to match the new state on the server. It
|
||||||
/// takes the following arguments:
|
/// takes the following arguments:
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct QueryChanges<F: FilterTrait<OBJ>, OBJ: Object>
|
pub struct QueryChanges<F: FilterTrait<OBJ>, OBJ: Object>
|
||||||
where
|
where
|
||||||
|
@ -1093,7 +1093,7 @@ where
|
||||||
_impl!(calculate_total: bool);
|
_impl!(calculate_total: bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct QueryChangesResponse<OBJ: Object> {
|
pub struct QueryChangesResponse<OBJ: Object> {
|
||||||
/// The id of the account used for the call.
|
/// The id of the account used for the call.
|
||||||
|
@ -1170,7 +1170,7 @@ pub struct QueryChangesResponse<OBJ: Object> {
|
||||||
pub added: Vec<AddedItem<OBJ>>,
|
pub added: Vec<AddedItem<OBJ>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct AddedItem<OBJ: Object> {
|
pub struct AddedItem<OBJ: Object> {
|
||||||
pub id: Id<OBJ>,
|
pub id: Id<OBJ>,
|
||||||
|
|
|
@ -23,9 +23,9 @@ use crate::backends::jmap::protocol::Method;
|
||||||
use crate::backends::jmap::rfc8620::Object;
|
use crate::backends::jmap::rfc8620::Object;
|
||||||
use crate::backends::jmap::rfc8620::ResultField;
|
use crate::backends::jmap::rfc8620::ResultField;
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub enum JmapArgument<T> {
|
pub enum JmapArgument<T: Clone> {
|
||||||
Value(T),
|
Value(T),
|
||||||
ResultReference {
|
ResultReference {
|
||||||
result_of: String,
|
result_of: String,
|
||||||
|
@ -34,7 +34,7 @@ pub enum JmapArgument<T> {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> JmapArgument<T> {
|
impl<T: Clone> JmapArgument<T> {
|
||||||
pub fn value(v: T) -> Self {
|
pub fn value(v: T) -> Self {
|
||||||
JmapArgument::Value(v)
|
JmapArgument::Value(v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Comparator<OBJ: Object> {
|
pub struct Comparator<OBJ: Object> {
|
||||||
property: String,
|
property: String,
|
||||||
|
|
Loading…
Reference in New Issue