parent
cfc380b47d
commit
3210ee5c67
|
@ -283,11 +283,80 @@ impl MailBackend for JmapType {
|
|||
|
||||
fn save(
|
||||
&self,
|
||||
_bytes: Vec<u8>,
|
||||
_mailbox_hash: MailboxHash,
|
||||
bytes: Vec<u8>,
|
||||
mailbox_hash: MailboxHash,
|
||||
_flags: Option<Flag>,
|
||||
) -> ResultFuture<()> {
|
||||
Err(MeliError::new("Unimplemented."))
|
||||
let mailboxes = self.mailboxes.clone();
|
||||
let connection = self.connection.clone();
|
||||
Ok(Box::pin(async move {
|
||||
let mut conn = connection.lock().await;
|
||||
conn.connect().await?;
|
||||
/*
|
||||
* 1. upload binary blob, get blobId
|
||||
* 2. Email/import
|
||||
*/
|
||||
let mut res = conn
|
||||
.client
|
||||
.post_async(
|
||||
&upload_request_format(&conn.session, conn.mail_account_id()),
|
||||
bytes,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mailbox_id: String = {
|
||||
let mailboxes_lck = mailboxes.read().unwrap();
|
||||
if let Some(mailbox) = mailboxes_lck.get(&mailbox_hash) {
|
||||
mailbox.id.clone()
|
||||
} else {
|
||||
return Err(MeliError::new(format!(
|
||||
"Mailbox with hash {} not found",
|
||||
mailbox_hash
|
||||
)));
|
||||
}
|
||||
};
|
||||
let res_text = res.text_async().await?;
|
||||
|
||||
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 mut email_imports = HashMap::default();
|
||||
let mut mailbox_ids = HashMap::default();
|
||||
mailbox_ids.insert(mailbox_id, true);
|
||||
email_imports.insert(
|
||||
creation_id.clone(),
|
||||
EmailImport::new()
|
||||
.blob_id(upload_response.blob_id)
|
||||
.mailbox_ids(mailbox_ids),
|
||||
);
|
||||
|
||||
let import_call: ImportCall = ImportCall::new()
|
||||
.account_id(conn.mail_account_id().to_string())
|
||||
.emails(email_imports);
|
||||
|
||||
req.add_call(&import_call);
|
||||
let mut res = conn
|
||||
.client
|
||||
.post_async(&conn.session.api_url, serde_json::to_string(&req)?)
|
||||
.await?;
|
||||
let res_text = res.text_async().await?;
|
||||
|
||||
let mut v: MethodResponse = serde_json::from_str(&res_text)?;
|
||||
let m = ImportResponse::try_from(v.method_responses.remove(0)).or_else(|err| {
|
||||
let ierr: Result<ImportError> =
|
||||
serde_json::from_str(&res_text).map_err(|err| err.into());
|
||||
if let Ok(err) = ierr {
|
||||
Err(MeliError::new(format!("Could not save message: {:?}", err)))
|
||||
} else {
|
||||
Err(err.into())
|
||||
}
|
||||
})?;
|
||||
|
||||
if let Some(err) = m.not_created.get(&creation_id) {
|
||||
return Err(MeliError::new(format!("Could not save message: {:?}", err)));
|
||||
}
|
||||
Ok(())
|
||||
}))
|
||||
}
|
||||
|
||||
fn tags(&self) -> Option<Arc<RwLock<BTreeMap<u64, String>>>> {
|
||||
|
|
|
@ -29,6 +29,9 @@ use std::collections::hash_map::DefaultHasher;
|
|||
use std::collections::HashMap;
|
||||
use std::hash::Hasher;
|
||||
|
||||
mod import;
|
||||
pub use import::*;
|
||||
|
||||
// 4.1.1.
|
||||
// Metadata
|
||||
// These properties represent metadata about the message in the mail
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* meli -
|
||||
*
|
||||
* Copyright 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 serde_json::value::RawValue;
|
||||
|
||||
/// #`import`
|
||||
///
|
||||
/// Objects of type `Foo` are imported via a call to `Foo/import`.
|
||||
///
|
||||
/// It takes the following arguments:
|
||||
///
|
||||
/// - `account_id`: "Id"
|
||||
///
|
||||
/// The id of the account to use.
|
||||
///
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImportCall {
|
||||
///accountId: "Id"
|
||||
///The id of the account to use.
|
||||
pub account_id: String,
|
||||
///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
|
||||
///referenced by the accountId; otherwise, the method will be aborted
|
||||
///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>,
|
||||
///o emails: "Id[EmailImport]"
|
||||
///A map of creation id (client specified) to EmailImport objects.
|
||||
pub emails: HashMap<Id, EmailImport>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct EmailImport {
|
||||
///o blobId: "Id"
|
||||
///The id of the blob containing the raw message [RFC5322].
|
||||
pub blob_id: String,
|
||||
///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>,
|
||||
///o keywords: "String[Boolean]" (default: {})
|
||||
///The keywords to apply to the Email.
|
||||
pub keywords: HashMap<String, bool>,
|
||||
|
||||
///o receivedAt: "UTCDate" (default: time of most recent Received
|
||||
///header, or time of import on server if none)
|
||||
///The "receivedAt" date to set on the Email.
|
||||
pub received_at: Option<String>,
|
||||
}
|
||||
|
||||
impl ImportCall {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
account_id: String::new(),
|
||||
if_in_state: None,
|
||||
emails: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
_impl!(
|
||||
/// - accountId: "Id"
|
||||
///
|
||||
/// The id of the account to use.
|
||||
///
|
||||
account_id: String
|
||||
);
|
||||
_impl!(if_in_state: Option<String>);
|
||||
_impl!(emails: HashMap<Id, EmailImport>);
|
||||
}
|
||||
|
||||
impl Method<EmailObject> for ImportCall {
|
||||
const NAME: &'static str = "Email/import";
|
||||
}
|
||||
|
||||
impl EmailImport {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
blob_id: String::new(),
|
||||
mailbox_ids: HashMap::default(),
|
||||
keywords: HashMap::default(),
|
||||
received_at: None,
|
||||
}
|
||||
}
|
||||
|
||||
_impl!(blob_id: String);
|
||||
_impl!(mailbox_ids: HashMap<Id, bool>);
|
||||
_impl!(keywords: HashMap<String, bool>);
|
||||
_impl!(received_at: Option<String>);
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum ImportError {
|
||||
///The server MAY forbid two Email objects with the same exact content
|
||||
/// [RFC5322], or even just with the same Message-ID [RFC5322], to
|
||||
/// coexist within an account. In this case, it MUST reject attempts to
|
||||
/// import an Email considered to be a duplicate with an "alreadyExists"
|
||||
/// SetError.
|
||||
AlreadyExists {
|
||||
description: Option<String>,
|
||||
/// An "existingId" property of type "Id" MUST be included on
|
||||
///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,
|
||||
},
|
||||
///If the "blobId", "mailboxIds", or "keywords" properties are invalid
|
||||
///(e.g., missing, wrong type, id not found), the server MUST reject the
|
||||
///import with an "invalidProperties" SetError.
|
||||
InvalidProperties {
|
||||
description: Option<String>,
|
||||
properties: Vec<String>,
|
||||
},
|
||||
///If the Email cannot be imported because it would take the account
|
||||
///over quota, the import should be rejected with an "overQuota"
|
||||
///SetError.
|
||||
OverQuota { description: Option<String> },
|
||||
///If the blob referenced is not a valid message [RFC5322], the server
|
||||
///MAY modify the message to fix errors (such as removing NUL octets or
|
||||
///fixing invalid headers). If it does this, the "blobId" on the
|
||||
///response MUST represent the new representation and therefore be
|
||||
///different to the "blobId" on the EmailImport object. Alternatively,
|
||||
///the server MAY reject the import with an "invalidEmail" SetError.
|
||||
InvalidEmail { description: Option<String> },
|
||||
///An "ifInState" argument was supplied, and it does not match the current state.
|
||||
StateMismatch,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImportResponse {
|
||||
///o accountId: "Id"
|
||||
///The id of the account used for this call.
|
||||
pub account_id: Id,
|
||||
|
||||
///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>,
|
||||
|
||||
///o newState: "String"
|
||||
///The state string that will now be returned by "Email/get" on this
|
||||
///account.
|
||||
pub new_state: Option<String>,
|
||||
|
||||
///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>,
|
||||
|
||||
///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>,
|
||||
}
|
||||
|
||||
impl std::convert::TryFrom<&RawValue> for ImportResponse {
|
||||
type Error = crate::error::MeliError;
|
||||
fn try_from(t: &RawValue) -> Result<ImportResponse> {
|
||||
let res: (String, ImportResponse, String) = serde_json::from_str(t.get())?;
|
||||
assert_eq!(&res.0, &ImportCall::NAME);
|
||||
Ok(res.1)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImportEmailResult {
|
||||
pub id: Id,
|
||||
pub blob_id: Id,
|
||||
pub thread_id: Id,
|
||||
pub size: usize,
|
||||
}
|
|
@ -815,3 +815,30 @@ pub fn upload_request_format(session: &JmapSession, account_id: &Id) -> String {
|
|||
}
|
||||
ret
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UploadResponse {
|
||||
///o accountId: "Id"
|
||||
///
|
||||
/// The id of the account used for the call.
|
||||
pub account_id: String,
|
||||
///o blobId: "Id"
|
||||
///
|
||||
///The id representing the binary data uploaded. The data for this id is immutable.
|
||||
///The id *only* refers to the binary data, not any metadata.
|
||||
pub blob_id: String,
|
||||
///o type: "String"
|
||||
///
|
||||
///The media type of the file (as specified in [RFC6838],
|
||||
///Section 4.2) as set in the Content-Type header of the upload HTTP
|
||||
///request.
|
||||
|
||||
#[serde(rename = "type")]
|
||||
pub _type: String,
|
||||
|
||||
///o size: "UnsignedInt"
|
||||
///
|
||||
/// The size of the file in octets.
|
||||
pub size: usize,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue