parent
bf67935c54
commit
652da6efd3
2 changed files with 32 additions and 88 deletions
|
@ -1,11 +1,5 @@
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use k2v_client::K2vClient;
|
|
||||||
use k2v_client::{BatchReadOp, Filter, K2vValue};
|
|
||||||
use rusoto_s3::{
|
|
||||||
CopyObjectRequest, DeleteObjectRequest, GetObjectRequest, PutObjectRequest, S3Client, S3,
|
|
||||||
};
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::io::AsyncReadExt;
|
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
|
|
||||||
use crate::bayou::Bayou;
|
use crate::bayou::Bayou;
|
||||||
|
@ -206,35 +200,18 @@ impl MailboxInternal {
|
||||||
|
|
||||||
async fn fetch_meta(&self, ids: &[UniqueIdent]) -> Result<Vec<MailMeta>> {
|
async fn fetch_meta(&self, ids: &[UniqueIdent]) -> Result<Vec<MailMeta>> {
|
||||||
let ids = ids.iter().map(|x| x.to_string()).collect::<Vec<_>>();
|
let ids = ids.iter().map(|x| x.to_string()).collect::<Vec<_>>();
|
||||||
let ops = ids
|
let ops = ids.iter().map(|id| (self.mail_path.as_str(), id.as_str())).collect::<Vec<_>>();
|
||||||
.iter()
|
let res_vec = self.k2v.select(storage::Selector::List(ops)).await?;
|
||||||
.map(|id| BatchReadOp {
|
|
||||||
partition_key: &self.mail_path,
|
|
||||||
filter: Filter {
|
|
||||||
start: Some(id),
|
|
||||||
end: None,
|
|
||||||
prefix: None,
|
|
||||||
limit: None,
|
|
||||||
reverse: false,
|
|
||||||
},
|
|
||||||
single_item: true,
|
|
||||||
conflicts_only: false,
|
|
||||||
tombstones: false,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
let res_vec = self.k2v.read_batch(&ops).await?;
|
|
||||||
|
|
||||||
let mut meta_vec = vec![];
|
let mut meta_vec = vec![];
|
||||||
for (op, res) in ops.iter().zip(res_vec.into_iter()) {
|
for res in res_vec.into_iter() {
|
||||||
if res.items.len() != 1 {
|
|
||||||
bail!("Expected 1 item, got {}", res.items.len());
|
|
||||||
}
|
|
||||||
let (_, cv) = res.items.iter().next().unwrap();
|
|
||||||
let mut meta_opt = None;
|
let mut meta_opt = None;
|
||||||
for v in cv.value.iter() {
|
|
||||||
|
// Resolve conflicts
|
||||||
|
for v in res.content().iter() {
|
||||||
match v {
|
match v {
|
||||||
K2vValue::Tombstone => (),
|
storage::Alternative::Tombstone => (),
|
||||||
K2vValue::Value(v) => {
|
storage::Alternative::Value(v) => {
|
||||||
let meta = open_deserialize::<MailMeta>(v, &self.encryption_key)?;
|
let meta = open_deserialize::<MailMeta>(v, &self.encryption_key)?;
|
||||||
match meta_opt.as_mut() {
|
match meta_opt.as_mut() {
|
||||||
None => {
|
None => {
|
||||||
|
@ -250,7 +227,7 @@ impl MailboxInternal {
|
||||||
if let Some(meta) = meta_opt {
|
if let Some(meta) = meta_opt {
|
||||||
meta_vec.push(meta);
|
meta_vec.push(meta);
|
||||||
} else {
|
} else {
|
||||||
bail!("No valid meta value in k2v for {:?}", op.filter.start);
|
bail!("No valid meta value in k2v for {:?}", res.to_ref().sk());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -258,19 +235,9 @@ impl MailboxInternal {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn fetch_full(&self, id: UniqueIdent, message_key: &Key) -> Result<Vec<u8>> {
|
async fn fetch_full(&self, id: UniqueIdent, message_key: &Key) -> Result<Vec<u8>> {
|
||||||
let gor = GetObjectRequest {
|
let obj_res = self.s3.blob(&format!("{}/{}", self.mail_path, id)).fetch().await?;
|
||||||
bucket: self.bucket.clone(),
|
let body = obj_res.content().ok_or(anyhow!("missing body"))?;
|
||||||
key: format!("{}/{}", self.mail_path, id),
|
cryptoblob::open(body, message_key)
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
let obj_res = self.s3.get_object(gor).await?;
|
|
||||||
|
|
||||||
let obj_body = obj_res.body.ok_or(anyhow!("Missing object body"))?;
|
|
||||||
let mut buf = Vec::with_capacity(obj_res.content_length.unwrap_or(128) as usize);
|
|
||||||
obj_body.into_async_read().read_to_end(&mut buf).await?;
|
|
||||||
|
|
||||||
cryptoblob::open(&buf, message_key)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- Functions for changing the mailbox ----
|
// ---- Functions for changing the mailbox ----
|
||||||
|
@ -303,13 +270,7 @@ impl MailboxInternal {
|
||||||
async {
|
async {
|
||||||
// Encrypt and save mail body
|
// Encrypt and save mail body
|
||||||
let message_blob = cryptoblob::seal(mail.raw, &message_key)?;
|
let message_blob = cryptoblob::seal(mail.raw, &message_key)?;
|
||||||
let por = PutObjectRequest {
|
self.s3.blob(&format!("{}/{}", self.mail_path, ident)).set_value(message_blob).push().await?;
|
||||||
bucket: self.bucket.clone(),
|
|
||||||
key: format!("{}/{}", self.mail_path, ident),
|
|
||||||
body: Some(message_blob.into()),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
self.s3.put_object(por).await?;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
|
@ -321,9 +282,7 @@ impl MailboxInternal {
|
||||||
rfc822_size: mail.raw.len(),
|
rfc822_size: mail.raw.len(),
|
||||||
};
|
};
|
||||||
let meta_blob = seal_serialize(&meta, &self.encryption_key)?;
|
let meta_blob = seal_serialize(&meta, &self.encryption_key)?;
|
||||||
self.k2v
|
self.k2v.row(&self.mail_path, &ident.to_string()).set_value(meta_blob).push().await?;
|
||||||
.insert_item(&self.mail_path, &ident.to_string(), meta_blob, None)
|
|
||||||
.await?;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
self.uid_index.opportunistic_sync()
|
self.uid_index.opportunistic_sync()
|
||||||
|
@ -354,8 +313,8 @@ impl MailboxInternal {
|
||||||
futures::try_join!(
|
futures::try_join!(
|
||||||
async {
|
async {
|
||||||
// Copy mail body from previous location
|
// Copy mail body from previous location
|
||||||
let dst = self.s3.blob(format!("{}/{}", self.mail_path, ident));
|
let dst = self.s3.blob(&format!("{}/{}", self.mail_path, ident));
|
||||||
blob_ref.copy(dst).await?;
|
blob_ref.copy(&dst).await?;
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
|
@ -367,9 +326,7 @@ impl MailboxInternal {
|
||||||
rfc822_size: mail.raw.len(),
|
rfc822_size: mail.raw.len(),
|
||||||
};
|
};
|
||||||
let meta_blob = seal_serialize(&meta, &self.encryption_key)?;
|
let meta_blob = seal_serialize(&meta, &self.encryption_key)?;
|
||||||
self.k2v
|
self.k2v.row(&self.mail_path, &ident.to_string()).set_value(meta_blob).push().await?;
|
||||||
.insert_item(&self.mail_path, &ident.to_string(), meta_blob, None)
|
|
||||||
.await?;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
self.uid_index.opportunistic_sync()
|
self.uid_index.opportunistic_sync()
|
||||||
|
@ -393,21 +350,13 @@ impl MailboxInternal {
|
||||||
futures::try_join!(
|
futures::try_join!(
|
||||||
async {
|
async {
|
||||||
// Delete mail body from S3
|
// Delete mail body from S3
|
||||||
let dor = DeleteObjectRequest {
|
self.s3.blob(&format!("{}/{}", self.mail_path, ident)).rm().await?;
|
||||||
bucket: self.bucket.clone(),
|
|
||||||
key: format!("{}/{}", self.mail_path, ident),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
self.s3.delete_object(dor).await?;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
// Delete mail meta from K2V
|
// Delete mail meta from K2V
|
||||||
let sk = ident.to_string();
|
let sk = ident.to_string();
|
||||||
let v = self.k2v.read_item(&self.mail_path, &sk).await?;
|
self.k2v.row(&self.mail_path, &sk).fetch().await?.to_ref().rm().await?;
|
||||||
self.k2v
|
|
||||||
.delete_item(&self.mail_path, &sk, v.causality)
|
|
||||||
.await?;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
}
|
}
|
||||||
)?;
|
)?;
|
||||||
|
@ -438,7 +387,7 @@ impl MailboxInternal {
|
||||||
source_id: UniqueIdent,
|
source_id: UniqueIdent,
|
||||||
new_id: UniqueIdent,
|
new_id: UniqueIdent,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
if self.bucket != from.bucket || self.encryption_key != from.encryption_key {
|
if self.encryption_key != from.encryption_key {
|
||||||
bail!("Message to be copied/moved does not belong to same account.");
|
bail!("Message to be copied/moved does not belong to same account.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -453,24 +402,15 @@ impl MailboxInternal {
|
||||||
|
|
||||||
futures::try_join!(
|
futures::try_join!(
|
||||||
async {
|
async {
|
||||||
// Copy mail body from S3
|
let dst = self.s3.blob(&format!("{}/{}", self.mail_path, new_id));
|
||||||
let cor = CopyObjectRequest {
|
self.s3.blob(&format!("{}/{}", from.mail_path, source_id)).copy(&dst).await?;
|
||||||
bucket: self.bucket.clone(),
|
|
||||||
key: format!("{}/{}", self.mail_path, new_id),
|
|
||||||
copy_source: format!("{}/{}/{}", from.bucket, from.mail_path, source_id),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
self.s3.copy_object(cor).await?;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
// Copy mail meta in K2V
|
// Copy mail meta in K2V
|
||||||
let meta = &from.fetch_meta(&[source_id]).await?[0];
|
let meta = &from.fetch_meta(&[source_id]).await?[0];
|
||||||
let meta_blob = seal_serialize(meta, &self.encryption_key)?;
|
let meta_blob = seal_serialize(meta, &self.encryption_key)?;
|
||||||
self.k2v
|
self.k2v.row(&self.mail_path, &new_id.to_string()).set_value(meta_blob).push().await?;
|
||||||
.insert_item(&self.mail_path, &new_id.to_string(), meta_blob, None)
|
|
||||||
.await?;
|
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
self.uid_index.opportunistic_sync(),
|
self.uid_index.opportunistic_sync(),
|
||||||
|
|
|
@ -14,17 +14,18 @@ use futures::future::BoxFuture;
|
||||||
pub mod in_memory;
|
pub mod in_memory;
|
||||||
pub mod garage;
|
pub mod garage;
|
||||||
|
|
||||||
pub enum Selector<'a> {
|
|
||||||
Range{ begin: &'a str, end: &'a str },
|
|
||||||
Filter(u64),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Alternative {
|
pub enum Alternative {
|
||||||
Tombstone,
|
Tombstone,
|
||||||
Value(Vec<u8>),
|
Value(Vec<u8>),
|
||||||
}
|
}
|
||||||
type ConcurrentValues = Vec<Alternative>;
|
type ConcurrentValues = Vec<Alternative>;
|
||||||
|
|
||||||
|
pub enum Selector<'a> {
|
||||||
|
Range { begin: &'a str, end: &'a str },
|
||||||
|
List (Vec<(&'a str, &'a str)>),
|
||||||
|
Prefix (&'a str),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum StorageError {
|
pub enum StorageError {
|
||||||
NotFound,
|
NotFound,
|
||||||
|
@ -78,12 +79,15 @@ impl Hash for Builders {
|
||||||
pub trait IRowStore
|
pub trait IRowStore
|
||||||
{
|
{
|
||||||
fn row(&self, partition: &str, sort: &str) -> RowRef;
|
fn row(&self, partition: &str, sort: &str) -> RowRef;
|
||||||
|
fn select(&self, selector: Selector) -> AsyncResult<Vec<RowValue>>;
|
||||||
}
|
}
|
||||||
pub type RowStore = Box<dyn IRowStore + Sync + Send>;
|
pub type RowStore = Box<dyn IRowStore + Sync + Send>;
|
||||||
|
|
||||||
pub trait IRowRef
|
pub trait IRowRef
|
||||||
{
|
{
|
||||||
fn clone_boxed(&self) -> RowRef;
|
fn clone_boxed(&self) -> RowRef;
|
||||||
|
fn pk(&self) -> &str;
|
||||||
|
fn sk(&self) -> &str;
|
||||||
fn set_value(&self, content: Vec<u8>) -> RowValue;
|
fn set_value(&self, content: Vec<u8>) -> RowValue;
|
||||||
fn fetch(&self) -> AsyncResult<RowValue>;
|
fn fetch(&self) -> AsyncResult<RowValue>;
|
||||||
fn rm(&self) -> AsyncResult<()>;
|
fn rm(&self) -> AsyncResult<()>;
|
||||||
|
|
Loading…
Reference in a new issue