pub(crate) use backend::{clear, load, save};
#[cfg(all(feature = "mobile", not(feature = "server")))]
mod backend {
use redb::{Database, ReadableDatabase, TableDefinition};
use std::{path::PathBuf, sync::OnceLock};
const ENTRIES: TableDefinition<&str, &str> = TableDefinition::new("entries");
fn database() -> Option<&'static Database> {
static DATABASE: OnceLock<Option<Database>> = OnceLock::new();
DATABASE
.get_or_init(|| {
let dir = cache_dir()?;
std::fs::create_dir_all(&dir).ok()?;
let file = format!("{}.redb", super::super::config().name);
Database::create(dir.join(file)).ok()
})
.as_ref()
}
#[cfg(target_os = "android")]
fn cache_dir() -> Option<PathBuf> {
use jni::objects::{JObject, JString};
let context = ndk_context::android_context();
let vm = unsafe { jni::JavaVM::from_raw(context.vm().cast()) }.ok()?;
let mut env = vm.attach_current_thread().ok()?;
let activity = unsafe { JObject::from_raw(context.context().cast()) };
let dir = env
.call_method(&activity, "getCacheDir", "()Ljava/io/File;", &[])
.ok()?
.l()
.ok()?;
let path = env
.call_method(&dir, "getAbsolutePath", "()Ljava/lang/String;", &[])
.ok()?
.l()
.ok()?;
let path: String = env.get_string(&JString::from(path)).ok()?.into();
Some(PathBuf::from(path))
}
#[cfg(target_os = "ios")]
fn cache_dir() -> Option<PathBuf> {
Some(PathBuf::from(std::env::var_os("HOME")?).join("Library/Caches"))
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn cache_dir() -> Option<PathBuf> {
Some(std::env::temp_dir().join("g3-kit"))
}
pub(crate) async fn load(key: &str) -> Option<String> {
let read = database()?.begin_read().ok()?;
let table = read.open_table(ENTRIES).ok()?;
let value = table.get(key).ok()??;
Some(value.value().to_string())
}
pub(crate) async fn save(key: &str, value: &str) {
let Some(database) = database() else { return };
let Ok(write) = database.begin_write() else {
return;
};
if let Ok(mut table) = write.open_table(ENTRIES) {
let _ = table.insert(key, value);
}
let _ = write.commit();
}
pub(crate) async fn clear() {
let Some(database) = database() else { return };
let Ok(write) = database.begin_write() else {
return;
};
let _ = write.delete_table(ENTRIES);
let _ = write.commit();
}
}
#[cfg(all(
feature = "web",
target_arch = "wasm32",
not(feature = "mobile"),
not(feature = "server")
))]
mod backend {
use rexie::{ObjectStore, Rexie, TransactionMode};
use wasm_bindgen::JsValue;
const ENTRIES: &str = "entries";
async fn open() -> Option<Rexie> {
Rexie::builder(&super::super::config().name)
.version(1)
.add_object_store(ObjectStore::new(ENTRIES))
.build()
.await
.ok()
}
pub(crate) async fn load(key: &str) -> Option<String> {
let database = open().await?;
let transaction = database
.transaction(&[ENTRIES], TransactionMode::ReadOnly)
.ok()?;
let value = transaction
.store(ENTRIES)
.ok()?
.get(JsValue::from_str(key))
.await
.ok()?;
let _ = transaction.done().await;
value?.as_string()
}
pub(crate) async fn save(key: &str, value: &str) {
let Some(database) = open().await else { return };
let Ok(transaction) = database.transaction(&[ENTRIES], TransactionMode::ReadWrite) else {
return;
};
if let Ok(store) = transaction.store(ENTRIES) {
let _ = store
.put(&JsValue::from_str(value), Some(&JsValue::from_str(key)))
.await;
}
let _ = transaction.done().await;
}
pub(crate) async fn clear() {
let Some(database) = open().await else { return };
let Ok(transaction) = database.transaction(&[ENTRIES], TransactionMode::ReadWrite) else {
return;
};
if let Ok(store) = transaction.store(ENTRIES) {
let _ = store.clear().await;
}
let _ = transaction.done().await;
}
}
#[cfg(not(any(
all(feature = "mobile", not(feature = "server")),
all(
feature = "web",
target_arch = "wasm32",
not(feature = "mobile"),
not(feature = "server")
)
)))]
mod backend {
pub(crate) async fn load(_key: &str) -> Option<String> {
None
}
pub(crate) async fn save(_key: &str, _value: &str) {}
pub(crate) async fn clear() {}
}