cargo format
This commit is contained in:
parent
54c9736a24
commit
7ac24ad913
12 changed files with 547 additions and 348 deletions
44
src/bayou.rs
44
src/bayou.rs
|
@ -9,9 +9,8 @@ use tokio::sync::{watch, Notify};
|
||||||
|
|
||||||
use crate::cryptoblob::*;
|
use crate::cryptoblob::*;
|
||||||
use crate::login::Credentials;
|
use crate::login::Credentials;
|
||||||
use crate::timestamp::*;
|
|
||||||
use crate::storage;
|
use crate::storage;
|
||||||
|
use crate::timestamp::*;
|
||||||
|
|
||||||
const KEEP_STATE_EVERY: usize = 64;
|
const KEEP_STATE_EVERY: usize = 64;
|
||||||
|
|
||||||
|
@ -94,7 +93,11 @@ impl<S: BayouState> Bayou<S> {
|
||||||
} else {
|
} else {
|
||||||
debug!("(sync) loading checkpoint: {}", key);
|
debug!("(sync) loading checkpoint: {}", key);
|
||||||
|
|
||||||
let buf = self.storage.blob_fetch(&storage::BlobRef(key.to_string())).await?.value;
|
let buf = self
|
||||||
|
.storage
|
||||||
|
.blob_fetch(&storage::BlobRef(key.to_string()))
|
||||||
|
.await?
|
||||||
|
.value;
|
||||||
debug!("(sync) checkpoint body length: {}", buf.len());
|
debug!("(sync) checkpoint body length: {}", buf.len());
|
||||||
|
|
||||||
let ck = open_deserialize::<S>(&buf, &self.key)?;
|
let ck = open_deserialize::<S>(&buf, &self.key)?;
|
||||||
|
@ -125,17 +128,22 @@ impl<S: BayouState> Bayou<S> {
|
||||||
// 3. List all operations starting from checkpoint
|
// 3. List all operations starting from checkpoint
|
||||||
let ts_ser = self.checkpoint.0.to_string();
|
let ts_ser = self.checkpoint.0.to_string();
|
||||||
debug!("(sync) looking up operations starting at {}", ts_ser);
|
debug!("(sync) looking up operations starting at {}", ts_ser);
|
||||||
let ops_map = self.storage.row_fetch(&storage::Selector::Range {
|
let ops_map = self
|
||||||
|
.storage
|
||||||
|
.row_fetch(&storage::Selector::Range {
|
||||||
shard: &self.path,
|
shard: &self.path,
|
||||||
sort_begin: &ts_ser,
|
sort_begin: &ts_ser,
|
||||||
sort_end: WATCH_SK
|
sort_end: WATCH_SK,
|
||||||
}).await?;
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
let mut ops = vec![];
|
let mut ops = vec![];
|
||||||
for row_value in ops_map {
|
for row_value in ops_map {
|
||||||
let row = row_value.row_ref;
|
let row = row_value.row_ref;
|
||||||
let sort_key = row.uid.sort;
|
let sort_key = row.uid.sort;
|
||||||
let ts = sort_key.parse::<Timestamp>().map_err(|_| anyhow!("Invalid operation timestamp: {}", sort_key))?;
|
let ts = sort_key
|
||||||
|
.parse::<Timestamp>()
|
||||||
|
.map_err(|_| anyhow!("Invalid operation timestamp: {}", sort_key))?;
|
||||||
|
|
||||||
let val = row_value.value;
|
let val = row_value.value;
|
||||||
if val.len() != 1 {
|
if val.len() != 1 {
|
||||||
|
@ -362,16 +370,20 @@ impl<S: BayouState> Bayou<S> {
|
||||||
// Delete blobs
|
// Delete blobs
|
||||||
for (_ts, key) in existing_checkpoints[..last_to_keep].iter() {
|
for (_ts, key) in existing_checkpoints[..last_to_keep].iter() {
|
||||||
debug!("(cp) drop old checkpoint {}", key);
|
debug!("(cp) drop old checkpoint {}", key);
|
||||||
self.storage.blob_rm(&storage::BlobRef(key.to_string())).await?;
|
self.storage
|
||||||
|
.blob_rm(&storage::BlobRef(key.to_string()))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete corresponding range of operations
|
// Delete corresponding range of operations
|
||||||
let ts_ser = existing_checkpoints[last_to_keep].0.to_string();
|
let ts_ser = existing_checkpoints[last_to_keep].0.to_string();
|
||||||
self.storage.row_rm(&storage::Selector::Range {
|
self.storage
|
||||||
|
.row_rm(&storage::Selector::Range {
|
||||||
shard: &self.path,
|
shard: &self.path,
|
||||||
sort_begin: "",
|
sort_begin: "",
|
||||||
sort_end: &ts_ser
|
sort_end: &ts_ser,
|
||||||
}).await?
|
})
|
||||||
|
.await?
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -426,11 +438,7 @@ impl K2vWatch {
|
||||||
|
|
||||||
let watch = Arc::new(K2vWatch { target, rx, notify });
|
let watch = Arc::new(K2vWatch { target, rx, notify });
|
||||||
|
|
||||||
tokio::spawn(Self::background_task(
|
tokio::spawn(Self::background_task(Arc::downgrade(&watch), storage, tx));
|
||||||
Arc::downgrade(&watch),
|
|
||||||
storage,
|
|
||||||
tx,
|
|
||||||
));
|
|
||||||
|
|
||||||
Ok(watch)
|
Ok(watch)
|
||||||
}
|
}
|
||||||
|
@ -444,8 +452,8 @@ impl K2vWatch {
|
||||||
Some(this) => this.target.clone(),
|
Some(this) => this.target.clone(),
|
||||||
None => {
|
None => {
|
||||||
error!("can't start loop");
|
error!("can't start loop");
|
||||||
return
|
return;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
while let Some(this) = Weak::upgrade(&self_weak) {
|
while let Some(this) = Weak::upgrade(&self_weak) {
|
||||||
|
|
|
@ -30,7 +30,10 @@ enum BucketSource {
|
||||||
|
|
||||||
enum StorageSpecific {
|
enum StorageSpecific {
|
||||||
InMemory,
|
InMemory,
|
||||||
Garage { from_config: LdapGarageConfig, bucket_source: BucketSource },
|
Garage {
|
||||||
|
from_config: LdapGarageConfig,
|
||||||
|
bucket_source: BucketSource,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LdapLoginProvider {
|
impl LdapLoginProvider {
|
||||||
|
@ -57,7 +60,8 @@ impl LdapLoginProvider {
|
||||||
let specific = match config.storage {
|
let specific = match config.storage {
|
||||||
LdapStorage::InMemory => StorageSpecific::InMemory,
|
LdapStorage::InMemory => StorageSpecific::InMemory,
|
||||||
LdapStorage::Garage(grgconf) => {
|
LdapStorage::Garage(grgconf) => {
|
||||||
let bucket_source = match (grgconf.default_bucket.clone(), grgconf.bucket_attr.clone()) {
|
let bucket_source =
|
||||||
|
match (grgconf.default_bucket.clone(), grgconf.bucket_attr.clone()) {
|
||||||
(Some(b), None) => BucketSource::Constant(b),
|
(Some(b), None) => BucketSource::Constant(b),
|
||||||
(None, Some(a)) => BucketSource::Attr(a),
|
(None, Some(a)) => BucketSource::Attr(a),
|
||||||
_ => bail!("Must set `bucket` or `bucket_attr`, but not both"),
|
_ => bail!("Must set `bucket` or `bucket_attr`, but not both"),
|
||||||
|
@ -67,12 +71,13 @@ impl LdapLoginProvider {
|
||||||
attrs_to_retrieve.push(a.clone());
|
attrs_to_retrieve.push(a.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
StorageSpecific::Garage { from_config: grgconf, bucket_source }
|
StorageSpecific::Garage {
|
||||||
},
|
from_config: grgconf,
|
||||||
|
bucket_source,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
ldap_server: config.ldap_server,
|
ldap_server: config.ldap_server,
|
||||||
pre_bind_on_login: config.pre_bind_on_login,
|
pre_bind_on_login: config.pre_bind_on_login,
|
||||||
|
@ -89,18 +94,23 @@ impl LdapLoginProvider {
|
||||||
|
|
||||||
async fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result<Builder> {
|
async fn storage_creds_from_ldap_user(&self, user: &SearchEntry) -> Result<Builder> {
|
||||||
let storage: Builder = match &self.storage_specific {
|
let storage: Builder = match &self.storage_specific {
|
||||||
StorageSpecific::InMemory => self.in_memory_store.builder(
|
StorageSpecific::InMemory => {
|
||||||
&get_attr(user, &self.username_attr)?
|
self.in_memory_store
|
||||||
).await,
|
.builder(&get_attr(user, &self.username_attr)?)
|
||||||
StorageSpecific::Garage { from_config, bucket_source } => {
|
.await
|
||||||
|
}
|
||||||
|
StorageSpecific::Garage {
|
||||||
|
from_config,
|
||||||
|
bucket_source,
|
||||||
|
} => {
|
||||||
let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?;
|
let aws_access_key_id = get_attr(user, &from_config.aws_access_key_id_attr)?;
|
||||||
let aws_secret_access_key = get_attr(user, &from_config.aws_secret_access_key_attr)?;
|
let aws_secret_access_key =
|
||||||
|
get_attr(user, &from_config.aws_secret_access_key_attr)?;
|
||||||
let bucket = match bucket_source {
|
let bucket = match bucket_source {
|
||||||
BucketSource::Constant(b) => b.clone(),
|
BucketSource::Constant(b) => b.clone(),
|
||||||
BucketSource::Attr(a) => get_attr(user, &a)?,
|
BucketSource::Attr(a) => get_attr(user, &a)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
||||||
region: from_config.aws_region.clone(),
|
region: from_config.aws_region.clone(),
|
||||||
s3_endpoint: from_config.s3_endpoint.clone(),
|
s3_endpoint: from_config.s3_endpoint.clone(),
|
||||||
|
@ -109,7 +119,7 @@ impl LdapLoginProvider {
|
||||||
aws_secret_access_key,
|
aws_secret_access_key,
|
||||||
bucket,
|
bucket,
|
||||||
})?
|
})?
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(storage)
|
Ok(storage)
|
||||||
|
@ -172,7 +182,6 @@ impl LoginProvider for LdapLoginProvider {
|
||||||
|
|
||||||
drop(ldap);
|
drop(ldap);
|
||||||
|
|
||||||
|
|
||||||
Ok(Credentials { storage, keys })
|
Ok(Credentials { storage, keys })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
pub mod ldap_provider;
|
pub mod ldap_provider;
|
||||||
pub mod static_provider;
|
pub mod static_provider;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Context, Result};
|
use anyhow::{anyhow, bail, Context, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -45,7 +45,7 @@ pub struct PublicCredentials {
|
||||||
pub public_key: PublicKey,
|
pub public_key: PublicKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
pub struct CryptoRoot(pub String);
|
pub struct CryptoRoot(pub String);
|
||||||
|
|
||||||
|
@ -73,47 +73,59 @@ impl CryptoRoot {
|
||||||
|
|
||||||
pub fn public_key(&self) -> Result<PublicKey> {
|
pub fn public_key(&self) -> Result<PublicKey> {
|
||||||
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
||||||
[ "aero", "cryptoroot", "pass", b64blob ] => {
|
["aero", "cryptoroot", "pass", b64blob] => {
|
||||||
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
||||||
if blob.len() < 32 {
|
if blob.len() < 32 {
|
||||||
bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len());
|
bail!(
|
||||||
|
"Decoded data is {} bytes long, expect at least 32 bytes",
|
||||||
|
blob.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
PublicKey::from_slice(&blob[..32]).context("must be a valid public key")
|
PublicKey::from_slice(&blob[..32]).context("must be a valid public key")
|
||||||
},
|
}
|
||||||
[ "aero", "cryptoroot", "cleartext", b64blob ] => {
|
["aero", "cryptoroot", "cleartext", b64blob] => {
|
||||||
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
||||||
Ok(CryptoKeys::deserialize(&blob)?.public)
|
Ok(CryptoKeys::deserialize(&blob)?.public)
|
||||||
},
|
}
|
||||||
[ "aero", "cryptoroot", "incoming", b64blob ] => {
|
["aero", "cryptoroot", "incoming", b64blob] => {
|
||||||
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
||||||
if blob.len() < 32 {
|
if blob.len() < 32 {
|
||||||
bail!("Decoded data is {} bytes long, expect at least 32 bytes", blob.len());
|
bail!(
|
||||||
|
"Decoded data is {} bytes long, expect at least 32 bytes",
|
||||||
|
blob.len()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
PublicKey::from_slice(&blob[..32]).context("must be a valid public key")
|
PublicKey::from_slice(&blob[..32]).context("must be a valid public key")
|
||||||
},
|
}
|
||||||
[ "aero", "cryptoroot", "keyring", _ ] => {
|
["aero", "cryptoroot", "keyring", _] => {
|
||||||
bail!("keyring is not yet implemented!")
|
bail!("keyring is not yet implemented!")
|
||||||
},
|
}
|
||||||
_ => bail!(format!("passed string '{}' is not a valid cryptoroot", self.0)),
|
_ => bail!(format!(
|
||||||
|
"passed string '{}' is not a valid cryptoroot",
|
||||||
|
self.0
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn crypto_keys(&self, password: &str) -> Result<CryptoKeys> {
|
pub fn crypto_keys(&self, password: &str) -> Result<CryptoKeys> {
|
||||||
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
match self.0.splitn(4, ':').collect::<Vec<&str>>()[..] {
|
||||||
[ "aero", "cryptoroot", "pass", b64blob ] => {
|
["aero", "cryptoroot", "pass", b64blob] => {
|
||||||
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
||||||
CryptoKeys::password_open(password, &blob)
|
CryptoKeys::password_open(password, &blob)
|
||||||
},
|
}
|
||||||
[ "aero", "cryptoroot", "cleartext", b64blob ] => {
|
["aero", "cryptoroot", "cleartext", b64blob] => {
|
||||||
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
let blob = base64::engine::general_purpose::STANDARD_NO_PAD.decode(b64blob)?;
|
||||||
CryptoKeys::deserialize(&blob)
|
CryptoKeys::deserialize(&blob)
|
||||||
},
|
}
|
||||||
[ "aero", "cryptoroot", "incoming", _ ] => {
|
["aero", "cryptoroot", "incoming", _] => {
|
||||||
bail!("incoming cryptoroot does not contain a crypto key!")
|
bail!("incoming cryptoroot does not contain a crypto key!")
|
||||||
},
|
}
|
||||||
[ "aero", "cryptoroot", "keyring", _ ] =>{
|
["aero", "cryptoroot", "keyring", _] => {
|
||||||
bail!("keyring is not yet implemented!")
|
bail!("keyring is not yet implemented!")
|
||||||
},
|
}
|
||||||
_ => bail!(format!("passed string '{}' is not a valid cryptoroot", self.0)),
|
_ => bail!(format!(
|
||||||
|
"passed string '{}' is not a valid cryptoroot",
|
||||||
|
self.0
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,9 +144,6 @@ pub struct CryptoKeys {
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl CryptoKeys {
|
impl CryptoKeys {
|
||||||
/// Initialize a new cryptography root
|
/// Initialize a new cryptography root
|
||||||
pub fn init() -> Self {
|
pub fn init() -> Self {
|
||||||
|
@ -202,7 +211,11 @@ fn derive_password_key(kdf_salt: &[u8], password: &str) -> Result<Key> {
|
||||||
Ok(Key::from_slice(&argon2_kdf(kdf_salt, password.as_bytes(), 32)?).unwrap())
|
Ok(Key::from_slice(&argon2_kdf(kdf_salt, password.as_bytes(), 32)?).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn try_open_encrypted_keys(kdf_salt: &[u8], password: &str, encrypted_keys: &[u8]) -> Result<Vec<u8>> {
|
fn try_open_encrypted_keys(
|
||||||
|
kdf_salt: &[u8],
|
||||||
|
password: &str,
|
||||||
|
encrypted_keys: &[u8],
|
||||||
|
) -> Result<Vec<u8>> {
|
||||||
let password_key = derive_password_key(kdf_salt, password)?;
|
let password_key = derive_password_key(kdf_salt, password)?;
|
||||||
open(encrypted_keys, &password_key)
|
open(encrypted_keys, &password_key)
|
||||||
}
|
}
|
||||||
|
@ -210,7 +223,7 @@ fn try_open_encrypted_keys(kdf_salt: &[u8], password: &str, encrypted_keys: &[u8
|
||||||
// ---- UTIL ----
|
// ---- UTIL ----
|
||||||
|
|
||||||
pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result<Vec<u8>> {
|
pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result<Vec<u8>> {
|
||||||
use argon2::{Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version, password_hash};
|
use argon2::{password_hash, Algorithm, Argon2, ParamsBuilder, PasswordHasher, Version};
|
||||||
|
|
||||||
let params = ParamsBuilder::new()
|
let params = ParamsBuilder::new()
|
||||||
.output_len(output_len)
|
.output_len(output_len)
|
||||||
|
@ -219,7 +232,8 @@ pub fn argon2_kdf(salt: &[u8], password: &[u8], output_len: usize) -> Result<Vec
|
||||||
let argon2 = Argon2::new(Algorithm::default(), Version::default(), params);
|
let argon2 = Argon2::new(Algorithm::default(), Version::default(), params);
|
||||||
|
|
||||||
let b64_salt = base64::engine::general_purpose::STANDARD_NO_PAD.encode(salt);
|
let b64_salt = base64::engine::general_purpose::STANDARD_NO_PAD.encode(salt);
|
||||||
let valid_salt = password_hash::Salt::from_b64(&b64_salt).map_err(|e| anyhow!("Invalid salt, error {}", e))?;
|
let valid_salt = password_hash::Salt::from_b64(&b64_salt)
|
||||||
|
.map_err(|e| anyhow!("Invalid salt, error {}", e))?;
|
||||||
let hash = argon2
|
let hash = argon2
|
||||||
.hash_password(password, valid_salt)
|
.hash_password(password, valid_salt)
|
||||||
.map_err(|e| anyhow!("Unable to hash: {}", e))?;
|
.map_err(|e| anyhow!("Unable to hash: {}", e))?;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::sync::watch;
|
use std::sync::Arc;
|
||||||
use tokio::signal::unix::{signal, SignalKind};
|
use tokio::signal::unix::{signal, SignalKind};
|
||||||
|
use tokio::sync::watch;
|
||||||
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{anyhow, bail, Result};
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
@ -28,7 +28,8 @@ pub struct StaticLoginProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn update_user_list(config: PathBuf, up: watch::Sender<UserDatabase>) -> Result<()> {
|
pub async fn update_user_list(config: PathBuf, up: watch::Sender<UserDatabase>) -> Result<()> {
|
||||||
let mut stream = signal(SignalKind::user_defined1()).expect("failed to install SIGUSR1 signal hander for reload");
|
let mut stream = signal(SignalKind::user_defined1())
|
||||||
|
.expect("failed to install SIGUSR1 signal hander for reload");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let ulist: UserList = match read_config(config.clone()) {
|
let ulist: UserList = match read_config(config.clone()) {
|
||||||
|
@ -42,7 +43,12 @@ pub async fn update_user_list(config: PathBuf, up: watch::Sender<UserDatabase>)
|
||||||
|
|
||||||
let users = ulist
|
let users = ulist
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(username, config)| (username.clone() , Arc::new(ContextualUserEntry { username, config })))
|
.map(|(username, config)| {
|
||||||
|
(
|
||||||
|
username.clone(),
|
||||||
|
Arc::new(ContextualUserEntry { username, config }),
|
||||||
|
)
|
||||||
|
})
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
|
|
||||||
let mut users_by_email = HashMap::new();
|
let mut users_by_email = HashMap::new();
|
||||||
|
@ -51,14 +57,18 @@ pub async fn update_user_list(config: PathBuf, up: watch::Sender<UserDatabase>)
|
||||||
if users_by_email.contains_key(m) {
|
if users_by_email.contains_key(m) {
|
||||||
tracing::warn!("Several users have the same email address: {}", m);
|
tracing::warn!("Several users have the same email address: {}", m);
|
||||||
stream.recv().await;
|
stream.recv().await;
|
||||||
continue
|
continue;
|
||||||
}
|
}
|
||||||
users_by_email.insert(m.clone(), u.clone());
|
users_by_email.insert(m.clone(), u.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing::info!("{} users loaded", users.len());
|
tracing::info!("{} users loaded", users.len());
|
||||||
up.send(UserDatabase { users, users_by_email }).context("update user db config")?;
|
up.send(UserDatabase {
|
||||||
|
users,
|
||||||
|
users_by_email,
|
||||||
|
})
|
||||||
|
.context("update user db config")?;
|
||||||
stream.recv().await;
|
stream.recv().await;
|
||||||
tracing::info!("Received SIGUSR1, reloading");
|
tracing::info!("Received SIGUSR1, reloading");
|
||||||
}
|
}
|
||||||
|
@ -71,7 +81,10 @@ impl StaticLoginProvider {
|
||||||
tokio::spawn(update_user_list(config.user_list, tx));
|
tokio::spawn(update_user_list(config.user_list, tx));
|
||||||
rx.changed().await?;
|
rx.changed().await?;
|
||||||
|
|
||||||
Ok(Self { user_db: rx, in_memory_store: storage::in_memory::MemDb::new() })
|
Ok(Self {
|
||||||
|
user_db: rx,
|
||||||
|
in_memory_store: storage::in_memory::MemDb::new(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,14 +108,16 @@ impl LoginProvider for StaticLoginProvider {
|
||||||
tracing::debug!(user=%username, "fetch keys");
|
tracing::debug!(user=%username, "fetch keys");
|
||||||
let storage: storage::Builder = match &user.config.storage {
|
let storage: storage::Builder = match &user.config.storage {
|
||||||
StaticStorage::InMemory => self.in_memory_store.builder(username).await,
|
StaticStorage::InMemory => self.in_memory_store.builder(username).await,
|
||||||
StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
StaticStorage::Garage(grgconf) => {
|
||||||
|
storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
||||||
region: grgconf.aws_region.clone(),
|
region: grgconf.aws_region.clone(),
|
||||||
k2v_endpoint: grgconf.k2v_endpoint.clone(),
|
k2v_endpoint: grgconf.k2v_endpoint.clone(),
|
||||||
s3_endpoint: grgconf.s3_endpoint.clone(),
|
s3_endpoint: grgconf.s3_endpoint.clone(),
|
||||||
aws_access_key_id: grgconf.aws_access_key_id.clone(),
|
aws_access_key_id: grgconf.aws_access_key_id.clone(),
|
||||||
aws_secret_access_key: grgconf.aws_secret_access_key.clone(),
|
aws_secret_access_key: grgconf.aws_secret_access_key.clone(),
|
||||||
bucket: grgconf.bucket.clone(),
|
bucket: grgconf.bucket.clone(),
|
||||||
})?,
|
})?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cr = CryptoRoot(user.config.crypto_root.clone());
|
let cr = CryptoRoot(user.config.crypto_root.clone());
|
||||||
|
@ -124,14 +139,16 @@ impl LoginProvider for StaticLoginProvider {
|
||||||
|
|
||||||
let storage: storage::Builder = match &user.config.storage {
|
let storage: storage::Builder = match &user.config.storage {
|
||||||
StaticStorage::InMemory => self.in_memory_store.builder(&user.username).await,
|
StaticStorage::InMemory => self.in_memory_store.builder(&user.username).await,
|
||||||
StaticStorage::Garage(grgconf) => storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
StaticStorage::Garage(grgconf) => {
|
||||||
|
storage::garage::GarageBuilder::new(storage::garage::GarageConf {
|
||||||
region: grgconf.aws_region.clone(),
|
region: grgconf.aws_region.clone(),
|
||||||
k2v_endpoint: grgconf.k2v_endpoint.clone(),
|
k2v_endpoint: grgconf.k2v_endpoint.clone(),
|
||||||
s3_endpoint: grgconf.s3_endpoint.clone(),
|
s3_endpoint: grgconf.s3_endpoint.clone(),
|
||||||
aws_access_key_id: grgconf.aws_access_key_id.clone(),
|
aws_access_key_id: grgconf.aws_access_key_id.clone(),
|
||||||
aws_secret_access_key: grgconf.aws_secret_access_key.clone(),
|
aws_secret_access_key: grgconf.aws_secret_access_key.clone(),
|
||||||
bucket: grgconf.bucket.clone(),
|
bucket: grgconf.bucket.clone(),
|
||||||
})?,
|
})?
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let cr = CryptoRoot(user.config.crypto_root.clone());
|
let cr = CryptoRoot(user.config.crypto_root.clone());
|
||||||
|
|
|
@ -51,7 +51,10 @@ async fn incoming_mail_watch_process_internal(
|
||||||
creds: Credentials,
|
creds: Credentials,
|
||||||
mut rx_inbox_id: watch::Receiver<Option<(UniqueIdent, ImapUidvalidity)>>,
|
mut rx_inbox_id: watch::Receiver<Option<(UniqueIdent, ImapUidvalidity)>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut lock_held = k2v_lock_loop(creds.storage.build().await?, storage::RowRef::new(INCOMING_PK, INCOMING_LOCK_SK));
|
let mut lock_held = k2v_lock_loop(
|
||||||
|
creds.storage.build().await?,
|
||||||
|
storage::RowRef::new(INCOMING_PK, INCOMING_LOCK_SK),
|
||||||
|
);
|
||||||
let storage = creds.storage.build().await?;
|
let storage = creds.storage.build().await?;
|
||||||
|
|
||||||
let mut inbox: Option<Arc<Mailbox>> = None;
|
let mut inbox: Option<Arc<Mailbox>> = None;
|
||||||
|
@ -63,8 +66,7 @@ async fn incoming_mail_watch_process_internal(
|
||||||
|
|
||||||
let wait_new_mail = async {
|
let wait_new_mail = async {
|
||||||
loop {
|
loop {
|
||||||
match storage.row_poll(&incoming_key).await
|
match storage.row_poll(&incoming_key).await {
|
||||||
{
|
|
||||||
Ok(row_val) => break row_val.row_ref,
|
Ok(row_val) => break row_val.row_ref,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error in wait_new_mail: {}", e);
|
error!("Error in wait_new_mail: {}", e);
|
||||||
|
@ -360,7 +362,10 @@ async fn k2v_lock_loop_internal(
|
||||||
Some(existing) => existing,
|
Some(existing) => existing,
|
||||||
None => row_ref.clone(),
|
None => row_ref.clone(),
|
||||||
};
|
};
|
||||||
if let Err(e) = storage.row_insert(vec![storage::RowVal::new(row, lock)]).await {
|
if let Err(e) = storage
|
||||||
|
.row_insert(vec![storage::RowVal::new(row, lock)])
|
||||||
|
.await
|
||||||
|
{
|
||||||
error!("Could not take lock: {}", e);
|
error!("Could not take lock: {}", e);
|
||||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||||
}
|
}
|
||||||
|
@ -428,14 +433,12 @@ impl EncryptedMessage {
|
||||||
let blob_val = storage::BlobVal::new(
|
let blob_val = storage::BlobVal::new(
|
||||||
storage::BlobRef(format!("incoming/{}", gen_ident())),
|
storage::BlobRef(format!("incoming/{}", gen_ident())),
|
||||||
self.encrypted_body.clone().into(),
|
self.encrypted_body.clone().into(),
|
||||||
).with_meta(MESSAGE_KEY.to_string(), key_header);
|
)
|
||||||
|
.with_meta(MESSAGE_KEY.to_string(), key_header);
|
||||||
storage.blob_insert(blob_val).await?;
|
storage.blob_insert(blob_val).await?;
|
||||||
|
|
||||||
// Update watch key to signal new mail
|
// Update watch key to signal new mail
|
||||||
let watch_val = storage::RowVal::new(
|
let watch_val = storage::RowVal::new(watch_ct.clone(), gen_ident().0.to_vec());
|
||||||
watch_ct.clone(),
|
|
||||||
gen_ident().0.to_vec(),
|
|
||||||
);
|
|
||||||
storage.row_insert(vec![watch_val]).await?;
|
storage.row_insert(vec![watch_val]).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use crate::login::Credentials;
|
||||||
use crate::mail::uidindex::*;
|
use crate::mail::uidindex::*;
|
||||||
use crate::mail::unique_ident::*;
|
use crate::mail::unique_ident::*;
|
||||||
use crate::mail::IMF;
|
use crate::mail::IMF;
|
||||||
use crate::storage::{Store, RowRef, RowVal, BlobRef, BlobVal, Selector, self};
|
use crate::storage::{self, BlobRef, BlobVal, RowRef, RowVal, Selector, Store};
|
||||||
use crate::timestamp::now_msec;
|
use crate::timestamp::now_msec;
|
||||||
|
|
||||||
pub struct Mailbox {
|
pub struct Mailbox {
|
||||||
|
@ -196,7 +196,10 @@ 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.iter().map(|id| RowRef::new(self.mail_path.as_str(), id.as_str())).collect::<Vec<_>>();
|
let ops = ids
|
||||||
|
.iter()
|
||||||
|
.map(|id| RowRef::new(self.mail_path.as_str(), id.as_str()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let res_vec = self.storage.row_fetch(&Selector::List(ops)).await?;
|
let res_vec = self.storage.row_fetch(&Selector::List(ops)).await?;
|
||||||
|
|
||||||
let mut meta_vec = vec![];
|
let mut meta_vec = vec![];
|
||||||
|
@ -231,7 +234,10 @@ 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 obj_res = self.storage.blob_fetch(&BlobRef(format!("{}/{}", self.mail_path, id))).await?;
|
let obj_res = self
|
||||||
|
.storage
|
||||||
|
.blob_fetch(&BlobRef(format!("{}/{}", self.mail_path, id)))
|
||||||
|
.await?;
|
||||||
let body = obj_res.value;
|
let body = obj_res.value;
|
||||||
cryptoblob::open(&body, message_key)
|
cryptoblob::open(&body, message_key)
|
||||||
}
|
}
|
||||||
|
@ -266,10 +272,12 @@ 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)?;
|
||||||
self.storage.blob_insert(BlobVal::new(
|
self.storage
|
||||||
|
.blob_insert(BlobVal::new(
|
||||||
BlobRef(format!("{}/{}", self.mail_path, ident)),
|
BlobRef(format!("{}/{}", self.mail_path, ident)),
|
||||||
message_blob,
|
message_blob,
|
||||||
)).await?;
|
))
|
||||||
|
.await?;
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
async {
|
async {
|
||||||
|
@ -281,10 +289,12 @@ 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.storage.row_insert(vec![RowVal::new(
|
self.storage
|
||||||
|
.row_insert(vec![RowVal::new(
|
||||||
RowRef::new(&self.mail_path, &ident.to_string()),
|
RowRef::new(&self.mail_path, &ident.to_string()),
|
||||||
meta_blob,
|
meta_blob,
|
||||||
)]).await?;
|
)])
|
||||||
|
.await?;
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
self.uid_index.opportunistic_sync()
|
self.uid_index.opportunistic_sync()
|
||||||
|
@ -328,10 +338,12 @@ 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.storage.row_insert(vec![RowVal::new(
|
self.storage
|
||||||
|
.row_insert(vec![RowVal::new(
|
||||||
RowRef::new(&self.mail_path, &ident.to_string()),
|
RowRef::new(&self.mail_path, &ident.to_string()),
|
||||||
meta_blob,
|
meta_blob,
|
||||||
)]).await?;
|
)])
|
||||||
|
.await?;
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
self.uid_index.opportunistic_sync()
|
self.uid_index.opportunistic_sync()
|
||||||
|
@ -355,17 +367,25 @@ impl MailboxInternal {
|
||||||
futures::try_join!(
|
futures::try_join!(
|
||||||
async {
|
async {
|
||||||
// Delete mail body from S3
|
// Delete mail body from S3
|
||||||
self.storage.blob_rm(&BlobRef(format!("{}/{}", self.mail_path, ident))).await?;
|
self.storage
|
||||||
|
.blob_rm(&BlobRef(format!("{}/{}", self.mail_path, ident)))
|
||||||
|
.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 res = self.storage
|
let res = self
|
||||||
.row_fetch(&storage::Selector::Single(&RowRef::new(&self.mail_path, &sk)))
|
.storage
|
||||||
|
.row_fetch(&storage::Selector::Single(&RowRef::new(
|
||||||
|
&self.mail_path,
|
||||||
|
&sk,
|
||||||
|
)))
|
||||||
.await?;
|
.await?;
|
||||||
if let Some(row_val) = res.into_iter().next() {
|
if let Some(row_val) = res.into_iter().next() {
|
||||||
self.storage.row_rm(&storage::Selector::Single(&row_val.row_ref)).await?;
|
self.storage
|
||||||
|
.row_rm(&storage::Selector::Single(&row_val.row_ref))
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
}
|
}
|
||||||
|
@ -421,10 +441,12 @@ impl MailboxInternal {
|
||||||
// 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.storage.row_insert(vec![RowVal::new(
|
self.storage
|
||||||
|
.row_insert(vec![RowVal::new(
|
||||||
RowRef::new(&self.mail_path, &new_id.to_string()),
|
RowRef::new(&self.mail_path, &new_id.to_string()),
|
||||||
meta_blob,
|
meta_blob,
|
||||||
)]).await?;
|
)])
|
||||||
|
.await?;
|
||||||
Ok::<_, anyhow::Error>(())
|
Ok::<_, anyhow::Error>(())
|
||||||
},
|
},
|
||||||
self.uid_index.opportunistic_sync(),
|
self.uid_index.opportunistic_sync(),
|
||||||
|
|
|
@ -226,7 +226,11 @@ impl User {
|
||||||
|
|
||||||
async fn load_mailbox_list(&self) -> Result<(MailboxList, Option<storage::RowRef>)> {
|
async fn load_mailbox_list(&self) -> Result<(MailboxList, Option<storage::RowRef>)> {
|
||||||
let row_ref = storage::RowRef::new(MAILBOX_LIST_PK, MAILBOX_LIST_SK);
|
let row_ref = storage::RowRef::new(MAILBOX_LIST_PK, MAILBOX_LIST_SK);
|
||||||
let (mut list, row) = match self.storage.row_fetch(&storage::Selector::Single(&row_ref)).await {
|
let (mut list, row) = match self
|
||||||
|
.storage
|
||||||
|
.row_fetch(&storage::Selector::Single(&row_ref))
|
||||||
|
.await
|
||||||
|
{
|
||||||
Err(storage::StorageError::NotFound) => (MailboxList::new(), None),
|
Err(storage::StorageError::NotFound) => (MailboxList::new(), None),
|
||||||
Err(e) => return Err(e.into()),
|
Err(e) => return Err(e.into()),
|
||||||
Ok(rv) => {
|
Ok(rv) => {
|
||||||
|
|
87
src/main.rs
87
src/main.rs
|
@ -1,6 +1,5 @@
|
||||||
#![feature(async_fn_in_trait)]
|
#![feature(async_fn_in_trait)]
|
||||||
|
|
||||||
mod timestamp;
|
|
||||||
mod bayou;
|
mod bayou;
|
||||||
mod config;
|
mod config;
|
||||||
mod cryptoblob;
|
mod cryptoblob;
|
||||||
|
@ -11,17 +10,18 @@ mod login;
|
||||||
mod mail;
|
mod mail;
|
||||||
mod server;
|
mod server;
|
||||||
mod storage;
|
mod storage;
|
||||||
|
mod timestamp;
|
||||||
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::{bail, Result, Context};
|
use anyhow::{bail, Context, Result};
|
||||||
use clap::{Parser, Subcommand};
|
use clap::{Parser, Subcommand};
|
||||||
use nix::{unistd::Pid, sys::signal};
|
use nix::{sys::signal, unistd::Pid};
|
||||||
|
|
||||||
use config::*;
|
use config::*;
|
||||||
use server::Server;
|
|
||||||
use login::{static_provider::*, *};
|
use login::{static_provider::*, *};
|
||||||
|
use server::Server;
|
||||||
|
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
@ -58,7 +58,7 @@ enum ToolsCommand {
|
||||||
PasswordHash {
|
PasswordHash {
|
||||||
#[clap(env = "AEROGRAMME_PASSWORD")]
|
#[clap(env = "AEROGRAMME_PASSWORD")]
|
||||||
maybe_password: Option<String>,
|
maybe_password: Option<String>,
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Subcommand, Debug)]
|
#[derive(Subcommand, Debug)]
|
||||||
|
@ -138,7 +138,7 @@ enum AccountManagement {
|
||||||
maybe_new_password: Option<String>,
|
maybe_new_password: Option<String>,
|
||||||
|
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
login: String
|
login: String,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -165,11 +165,11 @@ async fn main() -> Result<()> {
|
||||||
CompanionCommand::Daemon => {
|
CompanionCommand::Daemon => {
|
||||||
let server = Server::from_companion_config(config).await?;
|
let server = Server::from_companion_config(config).await?;
|
||||||
server.run().await?;
|
server.run().await?;
|
||||||
},
|
}
|
||||||
CompanionCommand::Reload { pid } => reload(*pid, config.pid)?,
|
CompanionCommand::Reload { pid } => reload(*pid, config.pid)?,
|
||||||
CompanionCommand::Wizard => {
|
CompanionCommand::Wizard => {
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
},
|
}
|
||||||
CompanionCommand::Account(cmd) => {
|
CompanionCommand::Account(cmd) => {
|
||||||
let user_file = config.users.user_list;
|
let user_file = config.users.user_list;
|
||||||
account_management(&args.command, cmd, user_file)?;
|
account_management(&args.command, cmd, user_file)?;
|
||||||
|
@ -179,22 +179,24 @@ async fn main() -> Result<()> {
|
||||||
ProviderCommand::Daemon => {
|
ProviderCommand::Daemon => {
|
||||||
let server = Server::from_provider_config(config).await?;
|
let server = Server::from_provider_config(config).await?;
|
||||||
server.run().await?;
|
server.run().await?;
|
||||||
},
|
}
|
||||||
ProviderCommand::Reload { pid } => reload(*pid, config.pid)?,
|
ProviderCommand::Reload { pid } => reload(*pid, config.pid)?,
|
||||||
ProviderCommand::Account(cmd) => {
|
ProviderCommand::Account(cmd) => {
|
||||||
let user_file = match config.users {
|
let user_file = match config.users {
|
||||||
UserManagement::Static(conf) => conf.user_list,
|
UserManagement::Static(conf) => conf.user_list,
|
||||||
UserManagement::Ldap(_) => panic!("LDAP account management is not supported from Aerogramme.")
|
UserManagement::Ldap(_) => {
|
||||||
|
panic!("LDAP account management is not supported from Aerogramme.")
|
||||||
|
}
|
||||||
};
|
};
|
||||||
account_management(&args.command, cmd, user_file)?;
|
account_management(&args.command, cmd, user_file)?;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(Command::Provider(_), AnyConfig::Companion(_)) => {
|
(Command::Provider(_), AnyConfig::Companion(_)) => {
|
||||||
bail!("Your want to run a 'Provider' command but your configuration file has role 'Companion'.");
|
bail!("Your want to run a 'Provider' command but your configuration file has role 'Companion'.");
|
||||||
},
|
}
|
||||||
(Command::Companion(_), AnyConfig::Provider(_)) => {
|
(Command::Companion(_), AnyConfig::Provider(_)) => {
|
||||||
bail!("Your want to run a 'Companion' command but your configuration file has role 'Provider'.");
|
bail!("Your want to run a 'Companion' command but your configuration file has role 'Provider'.");
|
||||||
},
|
}
|
||||||
(Command::Tools(subcommand), _) => match subcommand {
|
(Command::Tools(subcommand), _) => match subcommand {
|
||||||
ToolsCommand::PasswordHash { maybe_password } => {
|
ToolsCommand::PasswordHash { maybe_password } => {
|
||||||
let password = match maybe_password {
|
let password = match maybe_password {
|
||||||
|
@ -202,15 +204,15 @@ async fn main() -> Result<()> {
|
||||||
None => rpassword::prompt_password("Enter password: ")?,
|
None => rpassword::prompt_password("Enter password: ")?,
|
||||||
};
|
};
|
||||||
println!("{}", hash_password(&password)?);
|
println!("{}", hash_password(&password)?);
|
||||||
},
|
}
|
||||||
ToolsCommand::CryptoRoot(crcommand) => {
|
ToolsCommand::CryptoRoot(crcommand) => match crcommand {
|
||||||
match crcommand {
|
|
||||||
CryptoRootCommand::New { maybe_password } => {
|
CryptoRootCommand::New { maybe_password } => {
|
||||||
let password = match maybe_password {
|
let password = match maybe_password {
|
||||||
Some(pwd) => pwd.clone(),
|
Some(pwd) => pwd.clone(),
|
||||||
None => {
|
None => {
|
||||||
let password = rpassword::prompt_password("Enter password: ")?;
|
let password = rpassword::prompt_password("Enter password: ")?;
|
||||||
let password_confirm = rpassword::prompt_password("Confirm password: ")?;
|
let password_confirm =
|
||||||
|
rpassword::prompt_password("Confirm password: ")?;
|
||||||
if password != password_confirm {
|
if password != password_confirm {
|
||||||
bail!("Passwords don't match.");
|
bail!("Passwords don't match.");
|
||||||
}
|
}
|
||||||
|
@ -220,13 +222,17 @@ async fn main() -> Result<()> {
|
||||||
let crypto_keys = CryptoKeys::init();
|
let crypto_keys = CryptoKeys::init();
|
||||||
let cr = CryptoRoot::create_pass(&password, &crypto_keys)?;
|
let cr = CryptoRoot::create_pass(&password, &crypto_keys)?;
|
||||||
println!("{}", cr.0);
|
println!("{}", cr.0);
|
||||||
},
|
}
|
||||||
CryptoRootCommand::NewClearText => {
|
CryptoRootCommand::NewClearText => {
|
||||||
let crypto_keys = CryptoKeys::init();
|
let crypto_keys = CryptoKeys::init();
|
||||||
let cr = CryptoRoot::create_cleartext(&crypto_keys);
|
let cr = CryptoRoot::create_cleartext(&crypto_keys);
|
||||||
println!("{}", cr.0);
|
println!("{}", cr.0);
|
||||||
},
|
}
|
||||||
CryptoRootCommand::ChangePassword { maybe_old_password, maybe_new_password, crypto_root } => {
|
CryptoRootCommand::ChangePassword {
|
||||||
|
maybe_old_password,
|
||||||
|
maybe_new_password,
|
||||||
|
crypto_root,
|
||||||
|
} => {
|
||||||
let old_password = match maybe_old_password {
|
let old_password = match maybe_old_password {
|
||||||
Some(pwd) => pwd.to_string(),
|
Some(pwd) => pwd.to_string(),
|
||||||
None => rpassword::prompt_password("Enter old password: ")?,
|
None => rpassword::prompt_password("Enter old password: ")?,
|
||||||
|
@ -236,7 +242,8 @@ async fn main() -> Result<()> {
|
||||||
Some(pwd) => pwd.to_string(),
|
Some(pwd) => pwd.to_string(),
|
||||||
None => {
|
None => {
|
||||||
let password = rpassword::prompt_password("Enter new password: ")?;
|
let password = rpassword::prompt_password("Enter new password: ")?;
|
||||||
let password_confirm = rpassword::prompt_password("Confirm new password: ")?;
|
let password_confirm =
|
||||||
|
rpassword::prompt_password("Confirm new password: ")?;
|
||||||
if password != password_confirm {
|
if password != password_confirm {
|
||||||
bail!("Passwords don't match.");
|
bail!("Passwords don't match.");
|
||||||
}
|
}
|
||||||
|
@ -247,15 +254,14 @@ async fn main() -> Result<()> {
|
||||||
let keys = CryptoRoot(crypto_root.to_string()).crypto_keys(&old_password)?;
|
let keys = CryptoRoot(crypto_root.to_string()).crypto_keys(&old_password)?;
|
||||||
let cr = CryptoRoot::create_pass(&new_password, &keys)?;
|
let cr = CryptoRoot::create_pass(&new_password, &keys)?;
|
||||||
println!("{}", cr.0);
|
println!("{}", cr.0);
|
||||||
},
|
}
|
||||||
CryptoRootCommand::DeriveIncoming { crypto_root } => {
|
CryptoRootCommand::DeriveIncoming { crypto_root } => {
|
||||||
let pubkey = CryptoRoot(crypto_root.to_string()).public_key()?;
|
let pubkey = CryptoRoot(crypto_root.to_string()).public_key()?;
|
||||||
let cr = CryptoRoot::create_incoming(&pubkey);
|
let cr = CryptoRoot::create_incoming(&pubkey);
|
||||||
println!("{}", cr.0);
|
println!("{}", cr.0);
|
||||||
},
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -269,7 +275,7 @@ fn reload(pid: Option<i32>, pid_path: Option<PathBuf>) -> Result<()> {
|
||||||
let mut pidstr = String::new();
|
let mut pidstr = String::new();
|
||||||
f.read_to_string(&mut pidstr)?;
|
f.read_to_string(&mut pidstr)?;
|
||||||
pidstr.parse::<i32>()?
|
pidstr.parse::<i32>()?
|
||||||
},
|
}
|
||||||
_ => bail!("Unable to infer your daemon's PID"),
|
_ => bail!("Unable to infer your daemon's PID"),
|
||||||
};
|
};
|
||||||
let pid = Pid::from_raw(final_pid);
|
let pid = Pid::from_raw(final_pid);
|
||||||
|
@ -278,13 +284,15 @@ fn reload(pid: Option<i32>, pid_path: Option<PathBuf>) -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -> Result<()> {
|
fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -> Result<()> {
|
||||||
let mut ulist: UserList = read_config(users.clone()).context(format!("'{:?}' must be a user database", users))?;
|
let mut ulist: UserList =
|
||||||
|
read_config(users.clone()).context(format!("'{:?}' must be a user database", users))?;
|
||||||
|
|
||||||
match cmd {
|
match cmd {
|
||||||
AccountManagement::Add { login, setup } => {
|
AccountManagement::Add { login, setup } => {
|
||||||
tracing::debug!(user=login, "will-create");
|
tracing::debug!(user = login, "will-create");
|
||||||
let stp: SetupEntry = read_config(setup.clone()).context(format!("'{:?}' must be a setup file", setup))?;
|
let stp: SetupEntry = read_config(setup.clone())
|
||||||
tracing::debug!(user=login, "loaded setup entry");
|
.context(format!("'{:?}' must be a setup file", setup))?;
|
||||||
|
tracing::debug!(user = login, "loaded setup entry");
|
||||||
|
|
||||||
let password = match stp.clear_password {
|
let password = match stp.clear_password {
|
||||||
Some(pwd) => pwd,
|
Some(pwd) => pwd,
|
||||||
|
@ -307,21 +315,28 @@ fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -
|
||||||
|
|
||||||
let hash = hash_password(password.as_str()).context("unable to hash password")?;
|
let hash = hash_password(password.as_str()).context("unable to hash password")?;
|
||||||
|
|
||||||
ulist.insert(login.clone(), UserEntry {
|
ulist.insert(
|
||||||
|
login.clone(),
|
||||||
|
UserEntry {
|
||||||
email_addresses: stp.email_addresses,
|
email_addresses: stp.email_addresses,
|
||||||
password: hash,
|
password: hash,
|
||||||
crypto_root: crypto_root.0,
|
crypto_root: crypto_root.0,
|
||||||
storage: stp.storage,
|
storage: stp.storage,
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
write_config(users.clone(), &ulist)?;
|
write_config(users.clone(), &ulist)?;
|
||||||
},
|
}
|
||||||
AccountManagement::Delete { login } => {
|
AccountManagement::Delete { login } => {
|
||||||
tracing::debug!(user=login, "will-delete");
|
tracing::debug!(user = login, "will-delete");
|
||||||
ulist.remove(login);
|
ulist.remove(login);
|
||||||
write_config(users.clone(), &ulist)?;
|
write_config(users.clone(), &ulist)?;
|
||||||
},
|
}
|
||||||
AccountManagement::ChangePassword { maybe_old_password, maybe_new_password, login } => {
|
AccountManagement::ChangePassword {
|
||||||
|
maybe_old_password,
|
||||||
|
maybe_new_password,
|
||||||
|
login,
|
||||||
|
} => {
|
||||||
let mut user = ulist.remove(login).context("user must exist first")?;
|
let mut user = ulist.remove(login).context("user must exist first")?;
|
||||||
|
|
||||||
let old_password = match maybe_old_password {
|
let old_password = match maybe_old_password {
|
||||||
|
@ -354,7 +369,7 @@ fn account_management(root: &Command, cmd: &AccountManagement, users: PathBuf) -
|
||||||
|
|
||||||
ulist.insert(login.clone(), user);
|
ulist.insert(login.clone(), user);
|
||||||
write_config(users.clone(), &ulist)?;
|
write_config(users.clone(), &ulist)?;
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::sync::Arc;
|
|
||||||
use std::path::PathBuf;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use futures::try_join;
|
use futures::try_join;
|
||||||
|
@ -26,7 +26,11 @@ impl Server {
|
||||||
|
|
||||||
let lmtp_server = None;
|
let lmtp_server = None;
|
||||||
let imap_server = Some(imap::new(config.imap, login.clone()).await?);
|
let imap_server = Some(imap::new(config.imap, login.clone()).await?);
|
||||||
Ok(Self { lmtp_server, imap_server, pid_file: config.pid })
|
Ok(Self {
|
||||||
|
lmtp_server,
|
||||||
|
imap_server,
|
||||||
|
pid_file: config.pid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn from_provider_config(config: ProviderConfig) -> Result<Self> {
|
pub async fn from_provider_config(config: ProviderConfig) -> Result<Self> {
|
||||||
|
@ -39,12 +43,16 @@ impl Server {
|
||||||
let lmtp_server = Some(LmtpServer::new(config.lmtp, login.clone()));
|
let lmtp_server = Some(LmtpServer::new(config.lmtp, login.clone()));
|
||||||
let imap_server = Some(imap::new(config.imap, login.clone()).await?);
|
let imap_server = Some(imap::new(config.imap, login.clone()).await?);
|
||||||
|
|
||||||
Ok(Self { lmtp_server, imap_server, pid_file: config.pid })
|
Ok(Self {
|
||||||
|
lmtp_server,
|
||||||
|
imap_server,
|
||||||
|
pid_file: config.pid,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn run(self) -> Result<()> {
|
pub async fn run(self) -> Result<()> {
|
||||||
let pid = std::process::id();
|
let pid = std::process::id();
|
||||||
tracing::info!(pid=pid, "Starting main loops");
|
tracing::info!(pid = pid, "Starting main loops");
|
||||||
|
|
||||||
// write the pid file
|
// write the pid file
|
||||||
if let Some(pid_file) = self.pid_file {
|
if let Some(pid_file) = self.pid_file {
|
||||||
|
@ -57,7 +65,6 @@ impl Server {
|
||||||
drop(file);
|
drop(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let (exit_signal, provoke_exit) = watch_ctrl_c();
|
let (exit_signal, provoke_exit) = watch_ctrl_c();
|
||||||
let _exit_on_err = move |err: anyhow::Error| {
|
let _exit_on_err = move |err: anyhow::Error| {
|
||||||
error!("Error: {}", err);
|
error!("Error: {}", err);
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
use crate::storage::*;
|
use crate::storage::*;
|
||||||
|
use aws_sdk_s3::{self as s3, error::SdkError, operation::get_object::GetObjectError};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use aws_sdk_s3::{
|
|
||||||
self as s3,
|
|
||||||
error::SdkError,
|
|
||||||
operation::get_object::GetObjectError,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
pub struct GarageConf {
|
pub struct GarageConf {
|
||||||
|
@ -39,7 +35,7 @@ impl IBuilder for GarageBuilder {
|
||||||
self.conf.aws_secret_access_key.clone(),
|
self.conf.aws_secret_access_key.clone(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
"aerogramme"
|
"aerogramme",
|
||||||
);
|
);
|
||||||
|
|
||||||
let s3_config = aws_config::from_env()
|
let s3_config = aws_config::from_env()
|
||||||
|
@ -86,19 +82,30 @@ pub struct GarageStore {
|
||||||
|
|
||||||
fn causal_to_row_val(row_ref: RowRef, causal_value: k2v_client::CausalValue) -> RowVal {
|
fn causal_to_row_val(row_ref: RowRef, causal_value: k2v_client::CausalValue) -> RowVal {
|
||||||
let new_row_ref = row_ref.with_causality(causal_value.causality.into());
|
let new_row_ref = row_ref.with_causality(causal_value.causality.into());
|
||||||
let row_values = causal_value.value.into_iter().map(|k2v_value| match k2v_value {
|
let row_values = causal_value
|
||||||
|
.value
|
||||||
|
.into_iter()
|
||||||
|
.map(|k2v_value| match k2v_value {
|
||||||
k2v_client::K2vValue::Tombstone => Alternative::Tombstone,
|
k2v_client::K2vValue::Tombstone => Alternative::Tombstone,
|
||||||
k2v_client::K2vValue::Value(v) => Alternative::Value(v),
|
k2v_client::K2vValue::Value(v) => Alternative::Value(v),
|
||||||
}).collect::<Vec<_>>();
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
RowVal { row_ref: new_row_ref, value: row_values }
|
RowVal {
|
||||||
|
row_ref: new_row_ref,
|
||||||
|
value: row_values,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl IStore for GarageStore {
|
impl IStore for GarageStore {
|
||||||
async fn row_fetch<'a>(&self, select: &Selector<'a>) -> Result<Vec<RowVal>, StorageError> {
|
async fn row_fetch<'a>(&self, select: &Selector<'a>) -> Result<Vec<RowVal>, StorageError> {
|
||||||
let (pk_list, batch_op) = match select {
|
let (pk_list, batch_op) = match select {
|
||||||
Selector::Range { shard, sort_begin, sort_end } => (
|
Selector::Range {
|
||||||
|
shard,
|
||||||
|
sort_begin,
|
||||||
|
sort_end,
|
||||||
|
} => (
|
||||||
vec![shard.to_string()],
|
vec![shard.to_string()],
|
||||||
vec![k2v_client::BatchReadOp {
|
vec![k2v_client::BatchReadOp {
|
||||||
partition_key: shard,
|
partition_key: shard,
|
||||||
|
@ -108,11 +115,16 @@ impl IStore for GarageStore {
|
||||||
..k2v_client::Filter::default()
|
..k2v_client::Filter::default()
|
||||||
},
|
},
|
||||||
..k2v_client::BatchReadOp::default()
|
..k2v_client::BatchReadOp::default()
|
||||||
}]
|
}],
|
||||||
),
|
),
|
||||||
Selector::List(row_ref_list) => (
|
Selector::List(row_ref_list) => (
|
||||||
row_ref_list.iter().map(|row_ref| row_ref.uid.shard.to_string()).collect::<Vec<_>>(),
|
row_ref_list
|
||||||
row_ref_list.iter().map(|row_ref| k2v_client::BatchReadOp {
|
.iter()
|
||||||
|
.map(|row_ref| row_ref.uid.shard.to_string())
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
row_ref_list
|
||||||
|
.iter()
|
||||||
|
.map(|row_ref| k2v_client::BatchReadOp {
|
||||||
partition_key: &row_ref.uid.shard,
|
partition_key: &row_ref.uid.shard,
|
||||||
filter: k2v_client::Filter {
|
filter: k2v_client::Filter {
|
||||||
start: Some(&row_ref.uid.sort),
|
start: Some(&row_ref.uid.sort),
|
||||||
|
@ -120,7 +132,8 @@ impl IStore for GarageStore {
|
||||||
},
|
},
|
||||||
single_item: true,
|
single_item: true,
|
||||||
..k2v_client::BatchReadOp::default()
|
..k2v_client::BatchReadOp::default()
|
||||||
}).collect::<Vec<_>>()
|
})
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
),
|
),
|
||||||
Selector::Prefix { shard, sort_prefix } => (
|
Selector::Prefix { shard, sort_prefix } => (
|
||||||
vec![shard.to_string()],
|
vec![shard.to_string()],
|
||||||
|
@ -131,26 +144,42 @@ impl IStore for GarageStore {
|
||||||
..k2v_client::Filter::default()
|
..k2v_client::Filter::default()
|
||||||
},
|
},
|
||||||
..k2v_client::BatchReadOp::default()
|
..k2v_client::BatchReadOp::default()
|
||||||
}]),
|
}],
|
||||||
|
),
|
||||||
Selector::Single(row_ref) => {
|
Selector::Single(row_ref) => {
|
||||||
let causal_value = match self.k2v.read_item(&row_ref.uid.shard, &row_ref.uid.sort).await {
|
let causal_value = match self
|
||||||
|
.k2v
|
||||||
|
.read_item(&row_ref.uid.shard, &row_ref.uid.sort)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("K2V read item shard={}, sort={}, bucket={} failed: {}", row_ref.uid.shard, row_ref.uid.sort, self.bucket, e);
|
tracing::error!(
|
||||||
|
"K2V read item shard={}, sort={}, bucket={} failed: {}",
|
||||||
|
row_ref.uid.shard,
|
||||||
|
row_ref.uid.sort,
|
||||||
|
self.bucket,
|
||||||
|
e
|
||||||
|
);
|
||||||
return Err(StorageError::Internal);
|
return Err(StorageError::Internal);
|
||||||
},
|
}
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
};
|
};
|
||||||
|
|
||||||
let row_val = causal_to_row_val((*row_ref).clone(), causal_value);
|
let row_val = causal_to_row_val((*row_ref).clone(), causal_value);
|
||||||
return Ok(vec![row_val])
|
return Ok(vec![row_val]);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let all_raw_res = match self.k2v.read_batch(&batch_op).await {
|
let all_raw_res = match self.k2v.read_batch(&batch_op).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("k2v read batch failed for {:?}, bucket {} with err: {}", select, self.bucket, e);
|
tracing::error!(
|
||||||
|
"k2v read batch failed for {:?}, bucket {} with err: {}",
|
||||||
|
select,
|
||||||
|
self.bucket,
|
||||||
|
e
|
||||||
|
);
|
||||||
return Err(StorageError::Internal);
|
return Err(StorageError::Internal);
|
||||||
},
|
}
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,7 +198,11 @@ impl IStore for GarageStore {
|
||||||
}
|
}
|
||||||
async fn row_rm<'a>(&self, select: &Selector<'a>) -> Result<(), StorageError> {
|
async fn row_rm<'a>(&self, select: &Selector<'a>) -> Result<(), StorageError> {
|
||||||
let del_op = match select {
|
let del_op = match select {
|
||||||
Selector::Range { shard, sort_begin, sort_end } => vec![k2v_client::BatchDeleteOp {
|
Selector::Range {
|
||||||
|
shard,
|
||||||
|
sort_begin,
|
||||||
|
sort_end,
|
||||||
|
} => vec![k2v_client::BatchDeleteOp {
|
||||||
partition_key: shard,
|
partition_key: shard,
|
||||||
prefix: None,
|
prefix: None,
|
||||||
start: Some(sort_begin),
|
start: Some(sort_begin),
|
||||||
|
@ -178,21 +211,24 @@ impl IStore for GarageStore {
|
||||||
}],
|
}],
|
||||||
Selector::List(row_ref_list) => {
|
Selector::List(row_ref_list) => {
|
||||||
// Insert null values with causality token = delete
|
// Insert null values with causality token = delete
|
||||||
let batch_op = row_ref_list.iter().map(|v| k2v_client::BatchInsertOp {
|
let batch_op = row_ref_list
|
||||||
|
.iter()
|
||||||
|
.map(|v| k2v_client::BatchInsertOp {
|
||||||
partition_key: &v.uid.shard,
|
partition_key: &v.uid.shard,
|
||||||
sort_key: &v.uid.sort,
|
sort_key: &v.uid.sort,
|
||||||
causality: v.causality.clone().map(|ct| ct.into()),
|
causality: v.causality.clone().map(|ct| ct.into()),
|
||||||
value: k2v_client::K2vValue::Tombstone,
|
value: k2v_client::K2vValue::Tombstone,
|
||||||
}).collect::<Vec<_>>();
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
return match self.k2v.insert_batch(&batch_op).await {
|
return match self.k2v.insert_batch(&batch_op).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Unable to delete the list of values: {}", e);
|
tracing::error!("Unable to delete the list of values: {}", e);
|
||||||
Err(StorageError::Internal)
|
Err(StorageError::Internal)
|
||||||
},
|
}
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
Selector::Prefix { shard, sort_prefix } => vec![k2v_client::BatchDeleteOp {
|
Selector::Prefix { shard, sort_prefix } => vec![k2v_client::BatchDeleteOp {
|
||||||
partition_key: shard,
|
partition_key: shard,
|
||||||
prefix: Some(sort_prefix),
|
prefix: Some(sort_prefix),
|
||||||
|
@ -213,10 +249,10 @@ impl IStore for GarageStore {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Unable to delete the list of values: {}", e);
|
tracing::error!("Unable to delete the list of values: {}", e);
|
||||||
Err(StorageError::Internal)
|
Err(StorageError::Internal)
|
||||||
},
|
}
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
};
|
};
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Finally here we only have prefix & range
|
// Finally here we only have prefix & range
|
||||||
|
@ -224,34 +260,46 @@ impl IStore for GarageStore {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("delete batch error: {}", e);
|
tracing::error!("delete batch error: {}", e);
|
||||||
Err(StorageError::Internal)
|
Err(StorageError::Internal)
|
||||||
},
|
}
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn row_insert(&self, values: Vec<RowVal>) -> Result<(), StorageError> {
|
async fn row_insert(&self, values: Vec<RowVal>) -> Result<(), StorageError> {
|
||||||
let batch_ops = values.iter().map(|v| k2v_client::BatchInsertOp {
|
let batch_ops = values
|
||||||
|
.iter()
|
||||||
|
.map(|v| k2v_client::BatchInsertOp {
|
||||||
partition_key: &v.row_ref.uid.shard,
|
partition_key: &v.row_ref.uid.shard,
|
||||||
sort_key: &v.row_ref.uid.sort,
|
sort_key: &v.row_ref.uid.sort,
|
||||||
causality: v.row_ref.causality.clone().map(|ct| ct.into()),
|
causality: v.row_ref.causality.clone().map(|ct| ct.into()),
|
||||||
value: v.value.iter().next().map(|cv| match cv {
|
value: v
|
||||||
|
.value
|
||||||
|
.iter()
|
||||||
|
.next()
|
||||||
|
.map(|cv| match cv {
|
||||||
Alternative::Value(buff) => k2v_client::K2vValue::Value(buff.clone()),
|
Alternative::Value(buff) => k2v_client::K2vValue::Value(buff.clone()),
|
||||||
Alternative::Tombstone => k2v_client::K2vValue::Tombstone,
|
Alternative::Tombstone => k2v_client::K2vValue::Tombstone,
|
||||||
}).unwrap_or(k2v_client::K2vValue::Tombstone)
|
})
|
||||||
}).collect::<Vec<_>>();
|
.unwrap_or(k2v_client::K2vValue::Tombstone),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
match self.k2v.insert_batch(&batch_ops).await {
|
match self.k2v.insert_batch(&batch_ops).await {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("k2v can't insert some value: {}", e);
|
tracing::error!("k2v can't insert some value: {}", e);
|
||||||
Err(StorageError::Internal)
|
Err(StorageError::Internal)
|
||||||
},
|
}
|
||||||
Ok(v) => Ok(v),
|
Ok(v) => Ok(v),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn row_poll(&self, value: &RowRef) -> Result<RowVal, StorageError> {
|
async fn row_poll(&self, value: &RowRef) -> Result<RowVal, StorageError> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(ct) = &value.causality {
|
if let Some(ct) = &value.causality {
|
||||||
match self.k2v.poll_item(&value.uid.shard, &value.uid.sort, ct.clone().into(), None).await {
|
match self
|
||||||
|
.k2v
|
||||||
|
.poll_item(&value.uid.shard, &value.uid.sort, ct.clone().into(), None)
|
||||||
|
.await
|
||||||
|
{
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Unable to poll item: {}", e);
|
tracing::error!("Unable to poll item: {}", e);
|
||||||
return Err(StorageError::Internal);
|
return Err(StorageError::Internal);
|
||||||
|
@ -262,8 +310,7 @@ impl IStore for GarageStore {
|
||||||
} else {
|
} else {
|
||||||
match self.k2v.read_item(&value.uid.shard, &value.uid.sort).await {
|
match self.k2v.read_item(&value.uid.shard, &value.uid.sort).await {
|
||||||
Err(k2v_client::Error::NotFound) => {
|
Err(k2v_client::Error::NotFound) => {
|
||||||
self
|
self.k2v
|
||||||
.k2v
|
|
||||||
.insert_item(&value.uid.shard, &value.uid.sort, vec![0u8], None)
|
.insert_item(&value.uid.shard, &value.uid.sort, vec![0u8], None)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -273,8 +320,8 @@ impl IStore for GarageStore {
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("Unable to read item in polling logic: {}", e);
|
tracing::error!("Unable to read item in polling logic: {}", e);
|
||||||
return Err(StorageError::Internal)
|
return Err(StorageError::Internal);
|
||||||
},
|
}
|
||||||
Ok(cv) => return Ok(causal_to_row_val(value.clone(), cv)),
|
Ok(cv) => return Ok(causal_to_row_val(value.clone(), cv)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,7 +329,8 @@ impl IStore for GarageStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn blob_fetch(&self, blob_ref: &BlobRef) -> Result<BlobVal, StorageError> {
|
async fn blob_fetch(&self, blob_ref: &BlobRef) -> Result<BlobVal, StorageError> {
|
||||||
let maybe_out = self.s3
|
let maybe_out = self
|
||||||
|
.s3
|
||||||
.get_object()
|
.get_object()
|
||||||
.bucket(self.bucket.to_string())
|
.bucket(self.bucket.to_string())
|
||||||
.key(blob_ref.0.to_string())
|
.key(blob_ref.0.to_string())
|
||||||
|
@ -296,12 +344,12 @@ impl IStore for GarageStore {
|
||||||
e => {
|
e => {
|
||||||
tracing::warn!("Blob Fetch Error, Service Error: {}", e);
|
tracing::warn!("Blob Fetch Error, Service Error: {}", e);
|
||||||
return Err(StorageError::Internal);
|
return Err(StorageError::Internal);
|
||||||
},
|
}
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::warn!("Blob Fetch Error, {}", e);
|
tracing::warn!("Blob Fetch Error, {}", e);
|
||||||
return Err(StorageError::Internal);
|
return Err(StorageError::Internal);
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let buffer = match object_output.body.collect().await {
|
let buffer = match object_output.body.collect().await {
|
||||||
|
@ -318,7 +366,8 @@ impl IStore for GarageStore {
|
||||||
async fn blob_insert(&self, blob_val: BlobVal) -> Result<(), StorageError> {
|
async fn blob_insert(&self, blob_val: BlobVal) -> Result<(), StorageError> {
|
||||||
let streamable_value = s3::primitives::ByteStream::from(blob_val.value);
|
let streamable_value = s3::primitives::ByteStream::from(blob_val.value);
|
||||||
|
|
||||||
let maybe_send = self.s3
|
let maybe_send = self
|
||||||
|
.s3
|
||||||
.put_object()
|
.put_object()
|
||||||
.bucket(self.bucket.to_string())
|
.bucket(self.bucket.to_string())
|
||||||
.key(blob_val.blob_ref.0.to_string())
|
.key(blob_val.blob_ref.0.to_string())
|
||||||
|
@ -338,7 +387,8 @@ impl IStore for GarageStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn blob_copy(&self, src: &BlobRef, dst: &BlobRef) -> Result<(), StorageError> {
|
async fn blob_copy(&self, src: &BlobRef, dst: &BlobRef) -> Result<(), StorageError> {
|
||||||
let maybe_copy = self.s3
|
let maybe_copy = self
|
||||||
|
.s3
|
||||||
.copy_object()
|
.copy_object()
|
||||||
.bucket(self.bucket.to_string())
|
.bucket(self.bucket.to_string())
|
||||||
.key(dst.0.clone())
|
.key(dst.0.clone())
|
||||||
|
@ -348,18 +398,24 @@ impl IStore for GarageStore {
|
||||||
|
|
||||||
match maybe_copy {
|
match maybe_copy {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("unable to copy object {} to {} (bucket: {}), error: {}", src.0, dst.0, self.bucket, e);
|
tracing::error!(
|
||||||
|
"unable to copy object {} to {} (bucket: {}), error: {}",
|
||||||
|
src.0,
|
||||||
|
dst.0,
|
||||||
|
self.bucket,
|
||||||
|
e
|
||||||
|
);
|
||||||
Err(StorageError::Internal)
|
Err(StorageError::Internal)
|
||||||
},
|
}
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::debug!("copied {} to {} (bucket: {})", src.0, dst.0, self.bucket);
|
tracing::debug!("copied {} to {} (bucket: {})", src.0, dst.0, self.bucket);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
async fn blob_list(&self, prefix: &str) -> Result<Vec<BlobRef>, StorageError> {
|
async fn blob_list(&self, prefix: &str) -> Result<Vec<BlobRef>, StorageError> {
|
||||||
let maybe_list = self.s3
|
let maybe_list = self
|
||||||
|
.s3
|
||||||
.list_objects_v2()
|
.list_objects_v2()
|
||||||
.bucket(self.bucket.to_string())
|
.bucket(self.bucket.to_string())
|
||||||
.prefix(prefix)
|
.prefix(prefix)
|
||||||
|
@ -370,7 +426,12 @@ impl IStore for GarageStore {
|
||||||
|
|
||||||
match maybe_list {
|
match maybe_list {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("listing prefix {} on bucket {} failed: {}", prefix, self.bucket, e);
|
tracing::error!(
|
||||||
|
"listing prefix {} on bucket {} failed: {}",
|
||||||
|
prefix,
|
||||||
|
self.bucket,
|
||||||
|
e
|
||||||
|
);
|
||||||
Err(StorageError::Internal)
|
Err(StorageError::Internal)
|
||||||
}
|
}
|
||||||
Ok(pagin_list_out) => Ok(pagin_list_out
|
Ok(pagin_list_out) => Ok(pagin_list_out
|
||||||
|
@ -382,7 +443,8 @@ impl IStore for GarageStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async fn blob_rm(&self, blob_ref: &BlobRef) -> Result<(), StorageError> {
|
async fn blob_rm(&self, blob_ref: &BlobRef) -> Result<(), StorageError> {
|
||||||
let maybe_delete = self.s3
|
let maybe_delete = self
|
||||||
|
.s3
|
||||||
.delete_object()
|
.delete_object()
|
||||||
.bucket(self.bucket.to_string())
|
.bucket(self.bucket.to_string())
|
||||||
.key(blob_ref.0.clone())
|
.key(blob_ref.0.clone())
|
||||||
|
@ -391,9 +453,14 @@ impl IStore for GarageStore {
|
||||||
|
|
||||||
match maybe_delete {
|
match maybe_delete {
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("unable to delete {} (bucket: {}), error {}", blob_ref.0, self.bucket, e);
|
tracing::error!(
|
||||||
|
"unable to delete {} (bucket: {}), error {}",
|
||||||
|
blob_ref.0,
|
||||||
|
self.bucket,
|
||||||
|
e
|
||||||
|
);
|
||||||
Err(StorageError::Internal)
|
Err(StorageError::Internal)
|
||||||
},
|
}
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
tracing::debug!("deleted {} (bucket: {})", blob_ref.0, self.bucket);
|
tracing::debug!("deleted {} (bucket: {})", blob_ref.0, self.bucket);
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -401,4 +468,3 @@ impl IStore for GarageStore {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::storage::*;
|
use crate::storage::*;
|
||||||
use std::collections::{HashMap, BTreeMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::ops::Bound::{Included, Unbounded, Excluded, self};
|
use std::ops::Bound::{self, Excluded, Included, Unbounded};
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ impl InternalRowVal {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_row_val(&self, row_ref: RowRef) -> RowVal {
|
fn to_row_val(&self, row_ref: RowRef) -> RowVal {
|
||||||
RowVal{
|
RowVal {
|
||||||
row_ref: row_ref.with_causality(self.version.to_string()),
|
row_ref: row_ref.with_causality(self.version.to_string()),
|
||||||
value: self.concurrent_values(),
|
value: self.concurrent_values(),
|
||||||
}
|
}
|
||||||
|
@ -170,24 +170,32 @@ impl IStore for MemStore {
|
||||||
let store = self.row.read().or(Err(StorageError::Internal))?;
|
let store = self.row.read().or(Err(StorageError::Internal))?;
|
||||||
|
|
||||||
match select {
|
match select {
|
||||||
Selector::Range { shard, sort_begin, sort_end } => {
|
Selector::Range {
|
||||||
Ok(store
|
shard,
|
||||||
|
sort_begin,
|
||||||
|
sort_end,
|
||||||
|
} => Ok(store
|
||||||
.get(*shard)
|
.get(*shard)
|
||||||
.unwrap_or(&BTreeMap::new())
|
.unwrap_or(&BTreeMap::new())
|
||||||
.range((Included(sort_begin.to_string()), Excluded(sort_end.to_string())))
|
.range((
|
||||||
|
Included(sort_begin.to_string()),
|
||||||
|
Excluded(sort_end.to_string()),
|
||||||
|
))
|
||||||
.map(|(k, v)| v.to_row_val(RowRef::new(shard, k)))
|
.map(|(k, v)| v.to_row_val(RowRef::new(shard, k)))
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>()),
|
||||||
},
|
|
||||||
Selector::List(rlist) => {
|
Selector::List(rlist) => {
|
||||||
let mut acc = vec![];
|
let mut acc = vec![];
|
||||||
for row_ref in rlist {
|
for row_ref in rlist {
|
||||||
let maybe_intval = store.get(&row_ref.uid.shard).map(|v| v.get(&row_ref.uid.sort)).flatten();
|
let maybe_intval = store
|
||||||
|
.get(&row_ref.uid.shard)
|
||||||
|
.map(|v| v.get(&row_ref.uid.sort))
|
||||||
|
.flatten();
|
||||||
if let Some(intval) = maybe_intval {
|
if let Some(intval) = maybe_intval {
|
||||||
acc.push(intval.to_row_val(row_ref.clone()));
|
acc.push(intval.to_row_val(row_ref.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(acc)
|
Ok(acc)
|
||||||
},
|
}
|
||||||
Selector::Prefix { shard, sort_prefix } => {
|
Selector::Prefix { shard, sort_prefix } => {
|
||||||
let last_bound = prefix_last_bound(sort_prefix);
|
let last_bound = prefix_last_bound(sort_prefix);
|
||||||
|
|
||||||
|
@ -197,7 +205,7 @@ impl IStore for MemStore {
|
||||||
.range((Included(sort_prefix.to_string()), last_bound))
|
.range((Included(sort_prefix.to_string()), last_bound))
|
||||||
.map(|(k, v)| v.to_row_val(RowRef::new(shard, k)))
|
.map(|(k, v)| v.to_row_val(RowRef::new(shard, k)))
|
||||||
.collect::<Vec<_>>())
|
.collect::<Vec<_>>())
|
||||||
},
|
}
|
||||||
Selector::Single(row_ref) => {
|
Selector::Single(row_ref) => {
|
||||||
let intval = store
|
let intval = store
|
||||||
.get(&row_ref.uid.shard)
|
.get(&row_ref.uid.shard)
|
||||||
|
@ -213,7 +221,12 @@ impl IStore for MemStore {
|
||||||
tracing::trace!(select=%select, command="row_rm");
|
tracing::trace!(select=%select, command="row_rm");
|
||||||
|
|
||||||
let values = match select {
|
let values = match select {
|
||||||
Selector::Range { .. } | Selector::Prefix { .. } => self.row_fetch(select).await?.into_iter().map(|rv| rv.row_ref).collect::<Vec<_>>(),
|
Selector::Range { .. } | Selector::Prefix { .. } => self
|
||||||
|
.row_fetch(select)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|rv| rv.row_ref)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
Selector::List(rlist) => rlist.clone(),
|
Selector::List(rlist) => rlist.clone(),
|
||||||
Selector::Single(row_ref) => vec![(*row_ref).clone()],
|
Selector::Single(row_ref) => vec![(*row_ref).clone()],
|
||||||
};
|
};
|
||||||
|
@ -282,7 +295,10 @@ impl IStore for MemStore {
|
||||||
async fn blob_fetch(&self, blob_ref: &BlobRef) -> Result<BlobVal, StorageError> {
|
async fn blob_fetch(&self, blob_ref: &BlobRef) -> Result<BlobVal, StorageError> {
|
||||||
tracing::trace!(entry=%blob_ref, command="blob_fetch");
|
tracing::trace!(entry=%blob_ref, command="blob_fetch");
|
||||||
let store = self.blob.read().or(Err(StorageError::Internal))?;
|
let store = self.blob.read().or(Err(StorageError::Internal))?;
|
||||||
store.get(&blob_ref.0).ok_or(StorageError::NotFound).map(|v| v.to_blob_val(blob_ref))
|
store
|
||||||
|
.get(&blob_ref.0)
|
||||||
|
.ok_or(StorageError::NotFound)
|
||||||
|
.map(|v| v.to_blob_val(blob_ref))
|
||||||
}
|
}
|
||||||
async fn blob_insert(&self, blob_val: BlobVal) -> Result<(), StorageError> {
|
async fn blob_insert(&self, blob_val: BlobVal) -> Result<(), StorageError> {
|
||||||
tracing::trace!(entry=%blob_val.blob_ref, command="blob_insert");
|
tracing::trace!(entry=%blob_val.blob_ref, command="blob_insert");
|
||||||
|
@ -300,10 +316,13 @@ impl IStore for MemStore {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn blob_list(&self, prefix: &str) -> Result<Vec<BlobRef>, StorageError> {
|
async fn blob_list(&self, prefix: &str) -> Result<Vec<BlobRef>, StorageError> {
|
||||||
tracing::trace!(prefix=prefix, command="blob_list");
|
tracing::trace!(prefix = prefix, command = "blob_list");
|
||||||
let store = self.blob.read().or(Err(StorageError::Internal))?;
|
let store = self.blob.read().or(Err(StorageError::Internal))?;
|
||||||
let last_bound = prefix_last_bound(prefix);
|
let last_bound = prefix_last_bound(prefix);
|
||||||
let blist = store.range((Included(prefix.to_string()), last_bound)).map(|(k, _)| BlobRef(k.to_string())).collect::<Vec<_>>();
|
let blist = store
|
||||||
|
.range((Included(prefix.to_string()), last_bound))
|
||||||
|
.map(|(k, _)| BlobRef(k.to_string()))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
Ok(blist)
|
Ok(blist)
|
||||||
}
|
}
|
||||||
async fn blob_rm(&self, blob_ref: &BlobRef) -> Result<(), StorageError> {
|
async fn blob_rm(&self, blob_ref: &BlobRef) -> Result<(), StorageError> {
|
||||||
|
|
|
@ -8,13 +8,13 @@
|
||||||
* into the object system so it is not exposed.
|
* into the object system so it is not exposed.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
pub mod in_memory;
|
|
||||||
pub mod garage;
|
pub mod garage;
|
||||||
|
pub mod in_memory;
|
||||||
|
|
||||||
use std::sync::Arc;
|
|
||||||
use std::hash::Hash;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Alternative {
|
pub enum Alternative {
|
||||||
|
@ -52,7 +52,11 @@ pub struct RowRef {
|
||||||
}
|
}
|
||||||
impl std::fmt::Display for RowRef {
|
impl std::fmt::Display for RowRef {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "RowRef({}, {}, {:?})", self.uid.shard, self.uid.sort, self.causality)
|
write!(
|
||||||
|
f,
|
||||||
|
"RowRef({}, {}, {:?})",
|
||||||
|
self.uid.shard, self.uid.sort, self.causality
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,7 +91,6 @@ impl RowVal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct BlobRef(pub String);
|
pub struct BlobRef(pub String);
|
||||||
impl std::fmt::Display for BlobRef {
|
impl std::fmt::Display for BlobRef {
|
||||||
|
@ -105,7 +108,8 @@ pub struct BlobVal {
|
||||||
impl BlobVal {
|
impl BlobVal {
|
||||||
pub fn new(blob_ref: BlobRef, value: Vec<u8>) -> Self {
|
pub fn new(blob_ref: BlobRef, value: Vec<u8>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
blob_ref, value,
|
blob_ref,
|
||||||
|
value,
|
||||||
meta: HashMap::new(),
|
meta: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,16 +122,27 @@ impl BlobVal {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Selector<'a> {
|
pub enum Selector<'a> {
|
||||||
Range { shard: &'a str, sort_begin: &'a str, sort_end: &'a str },
|
Range {
|
||||||
List (Vec<RowRef>), // list of (shard_key, sort_key)
|
shard: &'a str,
|
||||||
|
sort_begin: &'a str,
|
||||||
|
sort_end: &'a str,
|
||||||
|
},
|
||||||
|
List(Vec<RowRef>), // list of (shard_key, sort_key)
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
Prefix { shard: &'a str, sort_prefix: &'a str },
|
Prefix {
|
||||||
|
shard: &'a str,
|
||||||
|
sort_prefix: &'a str,
|
||||||
|
},
|
||||||
Single(&'a RowRef),
|
Single(&'a RowRef),
|
||||||
}
|
}
|
||||||
impl<'a> std::fmt::Display for Selector<'a> {
|
impl<'a> std::fmt::Display for Selector<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
Self::Range { shard, sort_begin, sort_end } => write!(f, "Range({}, [{}, {}[)", shard, sort_begin, sort_end),
|
Self::Range {
|
||||||
|
shard,
|
||||||
|
sort_begin,
|
||||||
|
sort_end,
|
||||||
|
} => write!(f, "Range({}, [{}, {}[)", shard, sort_begin, sort_end),
|
||||||
Self::List(list) => write!(f, "List({:?})", list),
|
Self::List(list) => write!(f, "List({:?})", list),
|
||||||
Self::Prefix { shard, sort_prefix } => write!(f, "Prefix({}, {})", shard, sort_prefix),
|
Self::Prefix { shard, sort_prefix } => write!(f, "Prefix({}, {})", shard, sort_prefix),
|
||||||
Self::Single(row_ref) => write!(f, "Single({})", row_ref),
|
Self::Single(row_ref) => write!(f, "Single({})", row_ref),
|
||||||
|
@ -149,7 +164,7 @@ pub trait IStore {
|
||||||
async fn blob_rm(&self, blob_ref: &BlobRef) -> Result<(), StorageError>;
|
async fn blob_rm(&self, blob_ref: &BlobRef) -> Result<(), StorageError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,Debug,PartialEq,Eq,Hash)]
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||||
pub struct UnicityBuffer(Vec<u8>);
|
pub struct UnicityBuffer(Vec<u8>);
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
|
Loading…
Reference in a new issue