use super::*;
use std::collections::{HashMap, HashSet};
use std::sync::Mutex;
pub fn canonicalize_cache_key(key: &str) -> String {
key.trim().to_lowercase()
}
pub(crate) fn matches_pattern(pattern: &str, text: &str) -> bool {
let p: Vec<char> = pattern.chars().collect();
let t: Vec<char> = text.chars().collect();
let mut pi = 0usize;
let mut ti = 0usize;
let mut star_pi: Option<usize> = None;
let mut star_ti = 0usize;
while ti < t.len() {
if pi < p.len() && (p[pi] == t[ti] || p[pi] == '?') {
pi += 1;
ti += 1;
} else if pi < p.len() && p[pi] == '*' {
star_pi = Some(pi);
star_ti = ti;
pi += 1;
} else if let Some(spi) = star_pi {
pi = spi + 1;
star_ti += 1;
ti = star_ti;
} else {
return false;
}
}
while pi < p.len() && p[pi] == '*' {
pi += 1;
}
pi == p.len()
}
impl OxcacheSyncCache {
pub fn new() -> Self {
Self {
backend: oxcache::backend::DashMapMemoryBackend::new(),
key_index: Mutex::new(HashSet::new()),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
backend: oxcache::backend::DashMapMemoryBackend::builder()
.capacity(capacity)
.build(),
key_index: Mutex::new(HashSet::with_capacity(capacity)),
}
}
pub fn inner(&self) -> &oxcache::backend::DashMapMemoryBackend {
&self.backend
}
}
impl Default for OxcacheSyncCache {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for OxcacheSyncCache {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let len = self.key_index.lock().map(|idx| idx.len()).unwrap_or(0);
f.debug_struct("OxcacheSyncCache")
.field("backend", &"DashMapMemoryBackend")
.field("key_count", &len)
.finish()
}
}
impl SyncCache for OxcacheSyncCache {
fn get(&self, key: &str) -> Option<Vec<u8>> {
use oxcache::backend::SyncCacheReader;
self.backend.get(key).ok().flatten()
}
fn set(&self, key: &str, value: Vec<u8>) {
use oxcache::backend::SyncCacheWriter;
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!(
"cache key_index poisoned; set falling back to backend-only for key={:?}",
key
);
if let Err(e) = self.backend.set(key, value, None) {
log::warn!("cache backend set failed for key={:?}: {}", key, e);
}
return;
}
};
if let Err(e) = self.backend.set(key, value, None) {
log::warn!("cache backend set failed for key={:?}: {}", key, e);
return;
}
idx.insert(key.to_string());
}
fn delete(&self, key: &str) -> bool {
use oxcache::backend::{SyncCacheReader, SyncCacheWriter};
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!(
"cache key_index poisoned; delete falling back to backend-only for key={:?}",
key
);
let existed = self.backend.exists(key).unwrap_or(false);
if existed && let Err(e) = self.backend.delete(key) {
log::warn!("cache backend delete failed for key={:?}: {}", key, e);
}
return existed;
}
};
let existed = self.backend.exists(key).unwrap_or(false);
if existed {
if let Err(e) = self.backend.delete(key) {
log::warn!("cache backend delete failed for key={:?}: {}", key, e);
return existed;
}
idx.remove(key);
}
existed
}
fn contains(&self, key: &str) -> bool {
use oxcache::backend::SyncCacheReader;
self.backend.exists(key).unwrap_or(false)
}
fn clear(&self) {
use oxcache::backend::SyncCacheWriter;
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!("cache key_index poisoned; clear falling back to backend-only");
if let Err(e) = self.backend.clear() {
log::warn!("cache backend clear failed: {}", e);
}
return;
}
};
if let Err(e) = self.backend.clear() {
log::warn!("cache backend clear failed: {}", e);
return;
}
idx.clear();
}
fn len(&self) -> usize {
use oxcache::backend::SyncCacheReader;
self.backend.len().unwrap_or(0) as usize
}
fn is_empty(&self) -> bool {
self.len() == 0
}
fn find_keys_by_pattern(&self, pattern: &str) -> Vec<String> {
use oxcache::backend::SyncCacheReader;
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => return Vec::new(),
};
let mut result = Vec::new();
let mut stale_keys = Vec::new();
for k in idx.iter() {
if !matches_pattern(pattern, k) {
continue;
}
if self.backend.exists(k).unwrap_or(false) {
result.push(k.clone());
} else {
stale_keys.push(k.clone());
}
}
for k in stale_keys {
idx.remove(&k);
}
result
}
fn get_many(&self, keys: &[&str]) -> HashMap<String, Vec<u8>> {
use oxcache::backend::SyncCacheReader;
let mut results = HashMap::with_capacity(keys.len());
for &key in keys {
if let Ok(Some(value)) = self.backend.get(key) {
results.insert(key.to_string(), value);
}
}
results
}
fn set_many(&self, items: &[(String, Vec<u8>)]) {
use oxcache::backend::SyncCacheWriter;
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!(
"cache key_index poisoned; set_many falling back to backend-only for {} items",
items.len()
);
for (key, value) in items {
if let Err(e) = self.backend.set(key, value.clone(), None) {
log::warn!("cache backend set failed for key={:?}: {}", key, e);
}
}
return;
}
};
let mut succeeded: Vec<&String> = Vec::with_capacity(items.len());
for (key, value) in items {
if let Err(e) = self.backend.set(key, value.clone(), None) {
log::warn!("cache backend set failed for key={:?}: {}", key, e);
} else {
succeeded.push(key);
}
}
for key in succeeded {
idx.insert(key.clone());
}
}
fn delete_many(&self, keys: &[&str]) -> usize {
use oxcache::backend::{SyncCacheReader, SyncCacheWriter};
let mut idx = match self.key_index.lock() {
Ok(idx) => idx,
Err(_) => {
log::warn!(
"cache key_index poisoned; delete_many falling back to backend-only for {} keys",
keys.len()
);
let mut deleted = 0usize;
for &key in keys {
let existed = self.backend.exists(key).unwrap_or(false);
if existed {
if let Err(e) = self.backend.delete(key) {
log::warn!("cache backend delete failed for key={:?}: {}", key, e);
} else {
deleted += 1;
}
}
}
return deleted;
}
};
let mut deleted = 0usize;
for &key in keys {
let existed = self.backend.exists(key).unwrap_or(false);
if existed {
if let Err(e) = self.backend.delete(key) {
log::warn!("cache backend delete failed for key={:?}: {}", key, e);
} else {
idx.remove(key);
deleted += 1;
}
}
}
deleted
}
fn get_stats(&self) -> HashMap<String, u64> {
use oxcache::backend::SyncCacheReader;
let mut stats = HashMap::new();
let len = self.backend.len().unwrap_or(0);
stats.insert("total_keys".to_string(), len);
stats.insert(
"capacity".to_string(),
SyncCacheReader::capacity(&self.backend).unwrap_or(0),
);
if let Ok(backend_stats) = self.backend.stats() {
for (k, v) in backend_stats {
if let Ok(n) = v.parse::<u64>() {
stats.entry(k).or_insert(n);
} else if let Ok(f) = v.parse::<f64>() {
let lower = k.to_lowercase();
let converted = if lower.contains("rate")
|| lower.contains("ratio")
|| lower.contains("pct")
{
(f * 100.0).round() as u64
} else {
f.round() as u64
};
log::debug!(
"cache stat {:?} parsed as f64={} converted to u64={}",
k,
f,
converted
);
stats.entry(k).or_insert(converted);
} else {
log::warn!(
"cache stat {:?} value {:?} could not be parsed as u64 or f64; dropped",
k,
v
);
}
}
}
stats
}
}