use crate::tools::time::TimeMillis;
use serde::de::DeserializeOwned;
use serde::Serialize;
use crate::tools::{compression, json};
use crate::tools::types::Id;
pub const BUCKET_CONFIG: &str = "config";
pub const BUCKET_CONFIG_KEY_META_POST_V1_PUBLIC: &str = "meta_post_v1_public";
pub const BUCKET_CONFIG_KEY_META_POST_V1_PRIVATE: &str = "meta_post_v1_private";
pub const BUCKET_PEER: &str = "peer";
pub const BUCKET_META_POST_PUBLIC: &str = "meta_post_public";
pub const BUCKET_POST_BUNDLE: &str = "post_bundle";
pub const BUCKET_POST_BUNDLE_FEEDBACK: &str = "post_bundle_feedback";
pub const BUCKETS: &[&str] = &[BUCKET_CONFIG, BUCKET_PEER, BUCKET_POST_BUNDLE, BUCKET_POST_BUNDLE_FEEDBACK, BUCKET_META_POST_PUBLIC];
pub const BUCKET_TRIMS: &[usize] = &[0, 1024, 512, 512, 2048];
pub fn config_key_for_user(id: Id, key: &str) -> String {
format!("{}.{}", id.to_hex_str(), key)
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait ClientStorage: Send + Sync {
async fn count(&self, bucket: &str) -> anyhow::Result<usize>;
async fn keys(&self, bucket: &str) -> anyhow::Result<Vec<String>>;
async fn get(&self, bucket: &str, key: &str, time_millis: TimeMillis) -> anyhow::Result<Option<Vec<u8>>>;
async fn put(&self, bucket: &str, key: &str, value: Vec<u8>, time_millis: TimeMillis) -> anyhow::Result<()>;
async fn remove(&self, bucket: &str, key: &str) -> anyhow::Result<()>;
async fn trim(&self, bucket: &str, max_count: usize) -> anyhow::Result<()>;
async fn reset(&self) -> anyhow::Result<()>;
}
pub async fn put_str(storage: &dyn ClientStorage, bucket: &str, key: &str, value: String, time_millis: TimeMillis) -> anyhow::Result<()> {
storage.put(bucket, key, value.into_bytes(), time_millis).await
}
pub async fn get_str(storage: &dyn ClientStorage, bucket: &str, key: &str, time_millis: TimeMillis) -> anyhow::Result<Option<String>> {
let result = storage.get(bucket, key, time_millis).await?;
match result {
Some(bytes) => Ok(Some(String::from_utf8(bytes)?)),
None => Ok(None),
}
}
pub async fn put_struct<T: Serialize>(storage: &dyn ClientStorage, bucket: &str, key: &str, value: &T, time_millis: TimeMillis) -> anyhow::Result<()> {
let bytes = json::struct_to_bytes(value)?;
let bytes_compressed = compression::compress_for_size(&bytes)?.to_bytes();
storage.put(bucket, key, bytes_compressed.to_vec(), time_millis).await
}
pub async fn get_struct<T: DeserializeOwned>(storage: &dyn ClientStorage, bucket: &str, key: &str, time_millis: TimeMillis) -> anyhow::Result<Option<T>> {
let result = storage.get(bucket, key, time_millis).await?;
match result {
Some(bytes_compressed) => {
let bytes = compression::decompress(&bytes_compressed)?.to_bytes();
let value = json::bytes_to_struct::<T>(&bytes)?;
Ok(Some(value))
}
None => Ok(None),
}
}
#[cfg(any(test, feature = "generic-tests"))]
pub mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn buckets_and_bucket_trims_have_equal_length() {
assert_eq!(BUCKETS.len(), BUCKET_TRIMS.len(), "BUCKETS has {} entries but BUCKET_TRIMS has {} entries", BUCKETS.len(), BUCKET_TRIMS.len());
}
pub async fn trim_test(cs: Arc<dyn ClientStorage>) {
let result: anyhow::Result<()> = try {
cs.reset().await?;
put_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key1", "val1".to_string(), TimeMillis(10)).await?;
put_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key2", "val2".to_string(), TimeMillis(20)).await?;
put_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key3", "val3".to_string(), TimeMillis(30)).await?;
put_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key4", "val4".to_string(), TimeMillis(40)).await?;
put_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key5", "val5".to_string(), TimeMillis(50)).await?;
assert_eq!(5, cs.count(BUCKET_POST_BUNDLE).await?);
cs.trim(BUCKET_POST_BUNDLE, 3).await?;
assert_eq!(3, cs.count(BUCKET_POST_BUNDLE).await?);
assert_eq!(None, get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key1", TimeMillis::zero()).await?);
assert_eq!(None, get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key2", TimeMillis::zero()).await?);
assert_eq!(Some("val3".to_string()), get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key3", TimeMillis::zero()).await?);
assert_eq!(Some("val4".to_string()), get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key4", TimeMillis::zero()).await?);
assert_eq!(Some("val5".to_string()), get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key5", TimeMillis::zero()).await?);
get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key3", TimeMillis(60)).await?;
cs.trim(BUCKET_POST_BUNDLE, 2).await?;
assert_eq!(2, cs.count(BUCKET_POST_BUNDLE).await?);
assert_eq!(None, get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key4", TimeMillis::zero()).await?);
assert_eq!(Some("val5".to_string()), get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key5", TimeMillis::zero()).await?);
assert_eq!(Some("val3".to_string()), get_str(cs.as_ref(), BUCKET_POST_BUNDLE, "key3", TimeMillis::zero()).await?);
cs.trim(BUCKET_POST_BUNDLE, 10).await?;
assert_eq!(2, cs.count(BUCKET_POST_BUNDLE).await?);
cs.trim(BUCKET_POST_BUNDLE, 0).await?;
assert_eq!(0, cs.count(BUCKET_POST_BUNDLE).await?);
cs.reset().await?;
};
if let Err(e) = result {
panic!("trim_test failed: {}", e);
}
}
pub async fn add_test(client_storage: Arc<dyn ClientStorage>) {
let result = try {
client_storage.reset().await?;
assert_eq!(0, client_storage.count(BUCKET_POST_BUNDLE).await?);
{
put_str(client_storage.as_ref(), BUCKET_POST_BUNDLE, "key1", "val1".to_string(), TimeMillis(0)).await?;
put_str(client_storage.as_ref(), BUCKET_POST_BUNDLE, "key2", "val2".to_string(), TimeMillis(1)).await?;
put_str(client_storage.as_ref(), BUCKET_POST_BUNDLE, "key3", "val3".to_string(), TimeMillis(2)).await?;
}
assert_eq!(3, client_storage.count(BUCKET_POST_BUNDLE).await?);
{
let x = get_str(client_storage.as_ref(), BUCKET_POST_BUNDLE, "key1", TimeMillis(3)).await?;
assert_eq!(Some("val1".to_string()), x);
}
put_str(client_storage.as_ref(), BUCKET_POST_BUNDLE, "key1", "val4".to_string(), TimeMillis(4)).await?;
assert_eq!(3, client_storage.count(BUCKET_POST_BUNDLE).await?);
{
let x = get_str(client_storage.as_ref(), BUCKET_POST_BUNDLE, "key1", TimeMillis(5)).await?;
assert_eq!(Some("val4".to_string()), x);
}
{
let x = get_str(client_storage.as_ref(), BUCKET_POST_BUNDLE, "key_none", TimeMillis(6)).await?;
assert_eq!(None, x);
}
{
client_storage.remove(BUCKET_POST_BUNDLE, "key1").await?;
assert_eq!(2, client_storage.count(BUCKET_POST_BUNDLE).await?);
}
client_storage.reset().await?;
assert_eq!(0, client_storage.count(BUCKET_POST_BUNDLE).await?);
};
if let Err(e) = result {
panic!("Test failed: {}", e);
}
}
}