forked from asklyphe-public/asklyphe
321 lines
No EOL
10 KiB
Rust
321 lines
No EOL
10 KiB
Rust
use crate::config::LypheDBConfig;
|
|
use log::*;
|
|
use lyphedb::PropagationStrategy;
|
|
use once_cell::sync::Lazy;
|
|
use std::borrow::Cow;
|
|
use std::collections::BTreeMap;
|
|
use std::path::PathBuf;
|
|
use std::sync::Arc;
|
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
use percent_encoding::{percent_decode_str, percent_encode, AsciiSet, CONTROLS};
|
|
use tokio::sync::RwLock;
|
|
pub const NOT_ALLOWED_ASCII: AsciiSet = CONTROLS.add(b' ').add(b'/').add(b'.').add(b'\\');
|
|
|
|
pub type LDBCache = BTreeMap<Arc<Vec<u8>>, Arc<Vec<u8>>>;
|
|
|
|
/// Reads will read from this cache
|
|
pub static PRIMARY_CACHE: Lazy<RwLock<LDBCache>> =
|
|
Lazy::new(|| RwLock::new(BTreeMap::new()));
|
|
/// Writes will be written to this cache, and then depending on the propagation method, the
|
|
/// Primary cache will be set to a copy
|
|
pub static SECONDARY_CACHE: Lazy<RwLock<LDBCache>> =
|
|
Lazy::new(|| RwLock::new(BTreeMap::new()));
|
|
/// how often are keys accessed? this will influence the garbage collector
|
|
pub static KEY_ACCESS_COUNTER: Lazy<RwLock<BTreeMap<Arc<Vec<u8>>, AtomicU64>>> =
|
|
Lazy::new(|| RwLock::new(BTreeMap::new()));
|
|
|
|
pub fn key_to_path(config: &LypheDBConfig, key: &[u8]) -> PathBuf {
|
|
let mut path = PathBuf::new();
|
|
path.push(&config.master);
|
|
|
|
let mut fnbuf = Vec::new();
|
|
for (i, byte) in key.iter().enumerate() {
|
|
if *byte == b'/' {
|
|
let encoded = percent_encode(&fnbuf, &NOT_ALLOWED_ASCII).to_string();
|
|
path.push(encoded);
|
|
fnbuf.clear();
|
|
} else {
|
|
fnbuf.push(*byte);
|
|
}
|
|
}
|
|
if !fnbuf.is_empty() {
|
|
let encoded = percent_encode(&fnbuf, &NOT_ALLOWED_ASCII).to_string();
|
|
path.push(encoded);
|
|
fnbuf.clear();
|
|
}
|
|
|
|
path.push(".self");
|
|
|
|
path
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum OperationError {
|
|
KeyCannotBeEmpty,
|
|
FilesystemPermissionError,
|
|
BadFilesystemEntry,
|
|
}
|
|
|
|
pub async fn set_key_disk(
|
|
config: &LypheDBConfig,
|
|
key: &[u8],
|
|
value: &[u8],
|
|
) -> Result<(), OperationError> {
|
|
if key.is_empty() {
|
|
return Err(OperationError::KeyCannotBeEmpty);
|
|
}
|
|
let path = key_to_path(config, key);
|
|
let directory = path.parent().ok_or(OperationError::KeyCannotBeEmpty)?;
|
|
if let Ok(directory_exists) = directory.try_exists() {
|
|
if !directory_exists {
|
|
std::fs::create_dir_all(directory).map_err(|e| {
|
|
warn!("couldn't create directory: {:?}", e);
|
|
OperationError::FilesystemPermissionError
|
|
})?;
|
|
}
|
|
} else {
|
|
return Err(OperationError::FilesystemPermissionError);
|
|
}
|
|
|
|
std::fs::write(path, value).map_err(|e| {
|
|
warn!("couldn't write file: {:?}", e);
|
|
OperationError::FilesystemPermissionError
|
|
})?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn delete_key_disk(
|
|
config: &LypheDBConfig,
|
|
key: &[u8],
|
|
) -> Result<(), OperationError> {
|
|
if key.is_empty() {
|
|
return Err(OperationError::KeyCannotBeEmpty);
|
|
}
|
|
let path = key_to_path(config, key);
|
|
if let Ok(exists) = path.try_exists() {
|
|
if !exists {
|
|
return Ok(());
|
|
}
|
|
}
|
|
std::fs::remove_file(path).map_err(|e| {
|
|
warn!("couldn't remove file: {:?}", e);
|
|
OperationError::FilesystemPermissionError
|
|
})
|
|
}
|
|
|
|
pub async fn get_key_disk(
|
|
config: &LypheDBConfig,
|
|
key: &[u8],
|
|
) -> Result<Option<Vec<u8>>, OperationError> {
|
|
if key.is_empty() {
|
|
return Err(OperationError::KeyCannotBeEmpty);
|
|
}
|
|
let path = key_to_path(config, key);
|
|
if let Ok(exists) = path.try_exists() {
|
|
if !exists {
|
|
return Ok(None);
|
|
}
|
|
}
|
|
let data = std::fs::read(path).map_err(|e| {
|
|
warn!("couldn't read file: {:?}", e);
|
|
OperationError::FilesystemPermissionError
|
|
})?;
|
|
Ok(Some(data))
|
|
}
|
|
|
|
/// this function allows empty keys, so you can get all keys under root by doing
|
|
/// get_keys_under_keydir_disk(..., b"")
|
|
pub async fn get_keys_under_keydir_disk(
|
|
config: &LypheDBConfig,
|
|
keydir: &[u8],
|
|
) -> Result<Vec<Vec<u8>>, OperationError> {
|
|
let path = key_to_path(config, keydir);
|
|
let path = path.parent().expect("bad master");
|
|
let mut keys = Vec::new();
|
|
for entry in std::fs::read_dir(path).map_err(|e| {
|
|
warn!("couldn't read directory: {:?}", e);
|
|
OperationError::FilesystemPermissionError
|
|
})? {
|
|
let entry = entry.map_err(|e| {
|
|
warn!("couldn't read directory entry: {:?}", e);
|
|
OperationError::FilesystemPermissionError
|
|
})?;
|
|
let path = entry.path();
|
|
let filename = path
|
|
.to_str()
|
|
.ok_or(OperationError::FilesystemPermissionError)?;
|
|
if filename.ends_with(".self") {
|
|
// this is a value file, ignore
|
|
continue;
|
|
}
|
|
let filename = filename.trim_start_matches(&config.master);
|
|
let key = percent_decode_str(filename).collect();
|
|
keys.push(key);
|
|
}
|
|
|
|
Ok(keys)
|
|
}
|
|
|
|
pub async fn set_key(
|
|
config: LypheDBConfig,
|
|
key: &[u8],
|
|
value: &[u8],
|
|
strat: &PropagationStrategy,
|
|
) -> Result<(), OperationError> {
|
|
let k1 = Arc::new(key.to_vec());
|
|
let v1 = Arc::new(value.to_vec());
|
|
let disk_task = {
|
|
let k1 = k1.clone();
|
|
let v1 = v1.clone();
|
|
tokio::spawn(async move { set_key_disk(&config, &k1, &v1).await })
|
|
};
|
|
|
|
let prop_task = match strat {
|
|
PropagationStrategy::Immediate => {
|
|
let k1 = k1.clone();
|
|
let v1 = v1.clone();
|
|
tokio::spawn(async move {
|
|
let mut secondary_cache = SECONDARY_CACHE.write().await;
|
|
secondary_cache.insert(k1, v1);
|
|
let mut primary_cache = PRIMARY_CACHE.write().await;
|
|
*primary_cache = secondary_cache.clone();
|
|
})
|
|
}
|
|
PropagationStrategy::Timeout => {
|
|
let k1 = k1.clone();
|
|
let v1 = v1.clone();
|
|
tokio::spawn(async move {
|
|
tokio::spawn(async move {
|
|
{
|
|
let mut secondary_cache = SECONDARY_CACHE.write().await;
|
|
secondary_cache.insert(k1, v1);
|
|
}
|
|
tokio::spawn(async move {
|
|
let start = std::time::Instant::now();
|
|
loop {
|
|
if start.elapsed().as_secs() > 60 {
|
|
break;
|
|
}
|
|
let pc = PRIMARY_CACHE.try_write();
|
|
if pc.is_err() {
|
|
tokio::time::sleep(std::time::Duration::from_secs(10)).await;
|
|
continue;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
let mut primary_cache = PRIMARY_CACHE.write().await;
|
|
let secondary_cache = SECONDARY_CACHE.read().await;
|
|
*primary_cache = secondary_cache.clone();
|
|
});
|
|
});
|
|
})
|
|
}
|
|
PropagationStrategy::OnRead => {
|
|
let k1 = k1.clone();
|
|
tokio::spawn(async move {
|
|
{
|
|
let mut secondary_cache = SECONDARY_CACHE.write().await;
|
|
secondary_cache.remove(&k1);
|
|
}
|
|
{
|
|
let mut primary_cache = PRIMARY_CACHE.write().await;
|
|
primary_cache.remove(&k1);
|
|
}
|
|
})
|
|
}
|
|
};
|
|
|
|
if let Ok(Err(e)) = disk_task.await {
|
|
error!("couldn't set key on disk: {:?} ({:?})", e, key);
|
|
// undo propagation
|
|
prop_task.abort();
|
|
{
|
|
let mut primary_cache = PRIMARY_CACHE.write().await;
|
|
primary_cache.remove(&k1);
|
|
}
|
|
{
|
|
let mut secondary_cache = SECONDARY_CACHE.write().await;
|
|
secondary_cache.remove(&k1);
|
|
}
|
|
return Err(e);
|
|
}
|
|
let _ = prop_task.await;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub async fn get_key(config: LypheDBConfig, key: &[u8]) -> Result<Option<Vec<u8>>, OperationError> {
|
|
let k1 = Arc::new(key.to_vec());
|
|
{
|
|
let k1 = k1.clone();
|
|
tokio::spawn(async move {
|
|
let mut access_counter = KEY_ACCESS_COUNTER.write().await;
|
|
let counter = access_counter.entry(k1.clone()).or_insert(AtomicU64::new(0));
|
|
if counter.load(Ordering::Relaxed) > 10000000 {
|
|
return;
|
|
}
|
|
counter.fetch_add(1, Ordering::SeqCst);
|
|
});
|
|
}
|
|
let disk_task = {
|
|
let k1 = k1.clone();
|
|
tokio::spawn(async move { get_key_disk(&config, &k1).await })
|
|
};
|
|
{
|
|
// read from cache
|
|
let cache = PRIMARY_CACHE.read().await;
|
|
if let Some(val) = cache.get(&k1) {
|
|
disk_task.abort();
|
|
return Ok(Some(val.to_vec()));
|
|
}
|
|
}
|
|
//debug!("cache miss");
|
|
if let Ok(result) = disk_task.await {
|
|
if let Ok(Some(val)) = &result {
|
|
let val = Arc::new(val.to_vec());
|
|
tokio::spawn(async move {
|
|
{
|
|
let mut cache = SECONDARY_CACHE.write().await;
|
|
cache.insert(k1.clone(), val.clone());
|
|
}
|
|
{
|
|
let secondary_cache = SECONDARY_CACHE.read().await;
|
|
let mut primary_cache = PRIMARY_CACHE.write().await;
|
|
*primary_cache = secondary_cache.clone();
|
|
}
|
|
//debug!("cache insert");
|
|
});
|
|
}
|
|
result
|
|
} else {
|
|
Err(OperationError::FilesystemPermissionError)
|
|
}
|
|
}
|
|
|
|
pub async fn delete_key(config: LypheDBConfig, key: &[u8]) -> Result<(), OperationError> {
|
|
let k1 = Arc::new(key.to_vec());
|
|
let disk_task = {
|
|
let k1 = k1.clone();
|
|
tokio::spawn(async move { delete_key_disk(&config, &k1).await })
|
|
};
|
|
let prop_task = {
|
|
let k1 = k1.clone();
|
|
tokio::spawn(async move {
|
|
{
|
|
let mut key_access_counter = KEY_ACCESS_COUNTER.write().await;
|
|
key_access_counter.remove(&k1);
|
|
}
|
|
{
|
|
let mut secondary_cache = SECONDARY_CACHE.write().await;
|
|
secondary_cache.remove(&k1);
|
|
}
|
|
let mut primary_cache = PRIMARY_CACHE.write().await;
|
|
let secondary_cache = SECONDARY_CACHE.read().await;
|
|
*primary_cache = secondary_cache.clone();
|
|
})
|
|
};
|
|
prop_task.await.expect("couldn't delete key");
|
|
disk_task.await.expect("couldn't delete key")
|
|
} |