blobs
This commit is contained in:
43
crates/core/src/blob/get.rs
Normal file
43
crates/core/src/blob/get.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
use mail_parser::{
|
||||
decoders::{base64::base64_decode, quoted_printable::quoted_printable_decode},
|
||||
Encoding,
|
||||
};
|
||||
use protocol::types::blob::BlobId;
|
||||
|
||||
use crate::JMAP;
|
||||
|
||||
impl JMAP {
|
||||
pub async fn blob_retrieve(
|
||||
&self,
|
||||
blob_id: &BlobId,
|
||||
account_id: u32,
|
||||
) -> store::Result<Option<Vec<u8>>> {
|
||||
if !self
|
||||
.store
|
||||
.has_blob_access(&blob_id.hash, vec![account_id])
|
||||
.await?
|
||||
{
|
||||
// TODO: validate ACL
|
||||
let acl = "true";
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if let Some(section) = &blob_id.section {
|
||||
Ok(self
|
||||
.store
|
||||
.get_blob(
|
||||
&blob_id.hash,
|
||||
(section.offset_start as u32)
|
||||
..(section.offset_start.saturating_add(section.size) as u32),
|
||||
)
|
||||
.await?
|
||||
.and_then(|bytes| match Encoding::from(section.encoding) {
|
||||
Encoding::None => Some(bytes),
|
||||
Encoding::Base64 => base64_decode(&bytes),
|
||||
Encoding::QuotedPrintable => quoted_printable_decode(&bytes),
|
||||
}))
|
||||
} else {
|
||||
self.store.get_blob(&blob_id.hash, 0..u32::MAX).await
|
||||
}
|
||||
}
|
||||
}
|
||||
1
crates/core/src/blob/mod.rs
Normal file
1
crates/core/src/blob/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod get;
|
||||
@@ -1,17 +1,133 @@
|
||||
use protocol::{
|
||||
error::method::MethodError,
|
||||
error::{
|
||||
method::MethodError,
|
||||
set::{SetError, SetErrorType},
|
||||
},
|
||||
method::import::{ImportEmailRequest, ImportEmailResponse},
|
||||
types::{collection::Collection, property::Property, state::State},
|
||||
};
|
||||
use store::BitmapKey;
|
||||
use utils::map::vec_map::VecMap;
|
||||
|
||||
use crate::JMAP;
|
||||
use crate::{MaybeError, JMAP};
|
||||
|
||||
impl JMAP {
|
||||
pub async fn email_import(
|
||||
&self,
|
||||
request: ImportEmailRequest,
|
||||
) -> Result<ImportEmailResponse, MethodError> {
|
||||
for (id, email) in request.emails {}
|
||||
// Validate state
|
||||
let account_id = request.account_id.document_id();
|
||||
let old_state: State = self
|
||||
.store
|
||||
.get_last_change_id(account_id, Collection::Email)
|
||||
.await?
|
||||
.into();
|
||||
if let Some(if_in_state) = request.if_in_state {
|
||||
if old_state != if_in_state {
|
||||
return Err(MethodError::StateMismatch);
|
||||
}
|
||||
}
|
||||
|
||||
todo!()
|
||||
let cococ = "implement ACLS";
|
||||
let valid_mailbox_ids = self
|
||||
.store
|
||||
.get_bitmap(BitmapKey::document_ids(account_id, Collection::Mailbox))
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
let mut created = VecMap::with_capacity(request.emails.len());
|
||||
let mut not_created = VecMap::with_capacity(request.emails.len());
|
||||
|
||||
'outer: for (id, email) in request.emails {
|
||||
// Validate mailboxIds
|
||||
let mailbox_ids = email
|
||||
.mailbox_ids
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.map(|m| m.unwrap().document_id())
|
||||
.collect::<Vec<_>>();
|
||||
if mailbox_ids.is_empty() {
|
||||
not_created.append(
|
||||
id,
|
||||
SetError::invalid_properties()
|
||||
.with_property(Property::MailboxIds)
|
||||
.with_description("Message must belong to at least one mailbox."),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
for mailbox_id in &mailbox_ids {
|
||||
if !valid_mailbox_ids.contains(*mailbox_id) {
|
||||
not_created.append(
|
||||
id,
|
||||
SetError::invalid_properties()
|
||||
.with_property(Property::MailboxIds)
|
||||
.with_description(format!("Mailbox {} does not exist.", mailbox_id)),
|
||||
);
|
||||
continue 'outer;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch raw message to import
|
||||
let raw_message =
|
||||
if let Some(raw_message) = self.blob_retrieve(&email.blob_id, account_id).await? {
|
||||
raw_message
|
||||
} else {
|
||||
not_created.append(
|
||||
id,
|
||||
SetError::new(SetErrorType::BlobNotFound)
|
||||
.with_description(format!("BlobId {} not found.", email.blob_id)),
|
||||
);
|
||||
continue;
|
||||
};
|
||||
|
||||
// Import message
|
||||
match self
|
||||
.email_ingest(
|
||||
&raw_message,
|
||||
account_id,
|
||||
mailbox_ids,
|
||||
email.keywords,
|
||||
email.received_at.map(|r| r.into()),
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(email) => {
|
||||
created.append(id, email.into());
|
||||
}
|
||||
Err(MaybeError::Permanent(reason)) => {
|
||||
not_created.append(
|
||||
id,
|
||||
SetError::new(SetErrorType::InvalidEmail).with_description(reason),
|
||||
);
|
||||
}
|
||||
Err(MaybeError::Temporary(_)) => {
|
||||
return Err(MethodError::ServerPartialFail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ImportEmailResponse {
|
||||
account_id: request.account_id,
|
||||
new_state: if !created.is_empty() {
|
||||
self.store
|
||||
.get_last_change_id(account_id, Collection::Email)
|
||||
.await?
|
||||
.into()
|
||||
} else {
|
||||
old_state.clone()
|
||||
},
|
||||
old_state: old_state.into(),
|
||||
created: if !created.is_empty() {
|
||||
created.into()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
not_created: if !not_created.is_empty() {
|
||||
not_created.into()
|
||||
} else {
|
||||
None
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
use mail_parser::{
|
||||
parsers::fields::thread::thread_name, HeaderName, HeaderValue, Message, RfcHeader,
|
||||
};
|
||||
use protocol::types::{collection::Collection, id::Id, keyword::Keyword, property::Property};
|
||||
use protocol::{
|
||||
object::Object,
|
||||
types::{
|
||||
blob::BlobId, collection::Collection, id::Id, keyword::Keyword, property::Property,
|
||||
value::Value,
|
||||
},
|
||||
};
|
||||
use store::{
|
||||
query::Filter,
|
||||
write::{log::ChangeLogBuilder, now, BatchBuilder, F_BITMAP, F_CLEAR, F_VALUE},
|
||||
@@ -247,3 +253,12 @@ impl JMAP {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<IngestedEmail> for Object<Value> {
|
||||
fn from(email: IngestedEmail) -> Self {
|
||||
Object::with_capacity(3)
|
||||
.with_property(Property::Id, email.id)
|
||||
.with_property(Property::ThreadId, email.id.prefix_id())
|
||||
.with_property(Property::BlobId, BlobId::new(email.blob_hash))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use protocol::error::method::MethodError;
|
||||
use store::{fts::Language, Store};
|
||||
|
||||
pub mod api;
|
||||
pub mod blob;
|
||||
pub mod email;
|
||||
|
||||
pub struct JMAP {
|
||||
@@ -33,3 +35,15 @@ impl From<store::Error> for MaybeError {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MaybeError> for MethodError {
|
||||
fn from(value: MaybeError) -> Self {
|
||||
match value {
|
||||
MaybeError::Temporary(msg) => {
|
||||
let log = "true";
|
||||
MethodError::ServerPartialFail
|
||||
}
|
||||
MaybeError::Permanent(msg) => MethodError::InvalidArguments(msg),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user