use crate::store::StorageResult;
use crate::{MigrationReport, Store, StoreBuilder};
use std::path::Path;
use std::sync::OnceLock;
static GLOBAL_STORE: OnceLock<Store> = OnceLock::new();
#[must_use = "dropped here, the global store is closed here - bind it in `main` \
(`let _ame = ...`) so the last writes are flushed on the way out"]
pub struct GlobalStoreGuard {
_private: (),
}
impl GlobalStoreGuard {
#[allow(clippy::needless_doctest_main)]
pub fn close(self) -> StorageResult<()> {
let closing = shutdown();
std::mem::forget(self);
closing
}
}
impl Drop for GlobalStoreGuard {
fn drop(&mut self) {
if let Err(report) = shutdown() {
tracing::error!(
target: "amethystate",
error = ?report,
"the global store's closing flush failed: what it still held is not on disk",
);
}
}
}
pub trait IntoGlobalStore: Sized {
fn into_store_builder(self) -> StoreBuilder;
fn init_global(self) -> GlobalStoreGuard {
let store = self.into_store_builder().build().unwrap_or_else(|err| {
panic!(
"amethystate: Failed to build global StoreBackend.\n\
Ensure the database path is writable and not locked by another process.\n\
Details: {err:?}"
);
});
install(store)
}
fn init_global_with_migration(self) -> (MigrationReport, GlobalStoreGuard) {
let (store, report) = self
.into_store_builder()
.build_with_migration()
.unwrap_or_else(|err| {
panic!(
"amethystate: Failed to build global StoreBackend.\n\
Ensure the database path is writable and not locked by another process.\n\
Details: {err:?}"
);
});
(report, install(store))
}
}
fn install(store: Store) -> GlobalStoreGuard {
GLOBAL_STORE.set(store).unwrap_or_else(|_| {
panic!(
"amethystate: Global store is already initialized.\n\
Ensure `init_global` is called exactly once during application startup."
);
});
GlobalStoreGuard { _private: () }
}
impl IntoGlobalStore for StoreBuilder {
fn into_store_builder(self) -> StoreBuilder {
self
}
}
impl IntoGlobalStore for &str {
fn into_store_builder(self) -> StoreBuilder {
StoreBuilder::new(self)
}
}
impl IntoGlobalStore for &Path {
fn into_store_builder(self) -> StoreBuilder {
StoreBuilder::new(self)
}
}
#[allow(clippy::needless_doctest_main)]
pub fn init_global<T: IntoGlobalStore>(source: T) -> GlobalStoreGuard {
source.init_global()
}
pub fn init_global_with_migration<T: IntoGlobalStore>(
source: T,
) -> (MigrationReport, GlobalStoreGuard) {
source.init_global_with_migration()
}
pub fn global_store() -> Store {
GLOBAL_STORE
.get()
.expect(
"amethystate: the global store is not initialized.\n\
Call `init_global` or `init_global_with_migration` during startup, \
and keep the guard it returns alive for as long as the store is used.",
)
.clone()
}
pub fn shutdown() -> StorageResult<()> {
match GLOBAL_STORE.get() {
Some(store) => Ok(store.close()?),
None => Ok(()),
}
}