use crate::store::{OpenStore, StorageResult};
use crate::{MigrationReport, Store, StoreBuilder};
use std::fmt;
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"]
#[derive(Debug)]
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 {
try_init_global(self).unwrap_or_else(|why| {
panic!("amethystate: the global store was not put in place: {why:?}")
})
}
fn init_global_with_migration(self) -> (MigrationReport, GlobalStoreGuard) {
try_init_global_with_migration(self).unwrap_or_else(|why| {
panic!("amethystate: the global store was not put in place: {why:?}")
})
}
}
pub enum InitGlobal {
Open(OpenStore),
AlreadyInstalled,
}
impl fmt::Display for InitGlobal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Open(_) => f.write_str("the global store would not open"),
Self::AlreadyInstalled => f.write_str("a global store is in place already"),
}
}
}
impl fmt::Debug for InitGlobal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Open(why) => write!(f, "{self}: {why:?}"),
Self::AlreadyInstalled => fmt::Display::fmt(self, f),
}
}
}
impl std::error::Error for InitGlobal {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Open(why) => Some(why),
Self::AlreadyInstalled => None,
}
}
}
pub struct AlreadyInstalled {
pub store: Store,
}
impl fmt::Display for AlreadyInstalled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a global store is in place already")
}
}
impl fmt::Debug for AlreadyInstalled {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl std::error::Error for AlreadyInstalled {}
pub fn install_global(store: Store) -> Result<GlobalStoreGuard, AlreadyInstalled> {
GLOBAL_STORE
.set(store)
.map_err(|store| AlreadyInstalled { store })?;
Ok(GlobalStoreGuard { _private: () })
}
pub fn try_init_global<T: IntoGlobalStore>(source: T) -> Result<GlobalStoreGuard, InitGlobal> {
if GLOBAL_STORE.get().is_some() {
return Err(InitGlobal::AlreadyInstalled);
}
let store = source
.into_store_builder()
.build()
.map_err(InitGlobal::Open)?;
install_global(store).map_err(|_| InitGlobal::AlreadyInstalled)
}
pub fn try_init_global_with_migration<T: IntoGlobalStore>(
source: T,
) -> Result<(MigrationReport, GlobalStoreGuard), InitGlobal> {
if GLOBAL_STORE.get().is_some() {
return Err(InitGlobal::AlreadyInstalled);
}
let (store, report) = source
.into_store_builder()
.build_with_migration()
.map_err(InitGlobal::Open)?;
let guard = install_global(store).map_err(|_| InitGlobal::AlreadyInstalled)?;
Ok((report, guard))
}
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(()),
}
}