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 {
self.into_store_builder()
.build_global()
.unwrap_or_else(|why| {
panic!("amethystate: the global store was not put in place: {why:?}")
})
}
}
impl StoreBuilder {
pub fn build_global(self) -> Result<GlobalStoreGuard, InitGlobal> {
refuse_a_second()?;
let store = self.build().map_err(InitGlobal::Open)?;
installed(store)
}
pub fn migrate_global(self) -> Result<(GlobalStoreGuard, MigrationReport), InitGlobal> {
refuse_a_second()?;
let (store, report) = self.migrate().map_err(InitGlobal::Open)?;
Ok((installed(store)?, report))
}
}
impl crate::store::builder::WithSteps {
pub fn migrate_global(self) -> Result<(GlobalStoreGuard, MigrationReport), InitGlobal> {
self.into_builder().migrate_global()
}
}
#[cfg(feature = "memory")]
impl crate::store::builder::OrInMemory<crate::store::builder::WithSteps> {
pub fn migrate_global(
self,
) -> Result<(GlobalStoreGuard, MigrationReport, crate::store::Persistence), InitGlobal> {
refuse_a_second()?;
let (store, report, persistence) = self.migrate();
Ok((installed(store)?, report, persistence))
}
}
#[cfg(feature = "memory")]
impl crate::store::builder::OrInMemory {
pub fn build_global(self) -> Result<(GlobalStoreGuard, crate::store::Persistence), InitGlobal> {
refuse_a_second()?;
let (store, persistence) = self.build();
Ok((installed(store)?, persistence))
}
pub fn migrate_global(
self,
) -> Result<(GlobalStoreGuard, MigrationReport, crate::store::Persistence), InitGlobal> {
refuse_a_second()?;
let (store, report, persistence) = self.migrate();
Ok((installed(store)?, report, persistence))
}
}
fn refuse_a_second() -> Result<(), InitGlobal> {
match GLOBAL_STORE.get() {
Some(_) => Err(InitGlobal::AlreadyInstalled),
None => Ok(()),
}
}
fn installed(store: Store) -> Result<GlobalStoreGuard, InitGlobal> {
install_global(store).map_err(|_| InitGlobal::AlreadyInstalled)
}
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: () })
}
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 global_store() -> Store {
GLOBAL_STORE
.get()
.expect(
"amethystate: the global store is not initialized.\n\
Call `init_global`, `build_global` or `migrate_global` 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(()),
}
}