use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use std::time::Duration;
#[cfg(feature = "encryption")]
use bonsaidb_core::document::KeyId;
use bonsaidb_core::permissions::Permissions;
use bonsaidb_core::schema::{Schema, SchemaName};
use sysinfo::{CpuRefreshKind, RefreshKind, System, SystemExt};
use crate::storage::{DatabaseOpener, StorageSchemaOpener};
#[cfg(feature = "encryption")]
use crate::vault::AnyVaultKeyStorage;
use crate::Error;
#[cfg(feature = "password-hashing")]
mod argon;
#[cfg(feature = "password-hashing")]
pub use argon::*;
#[derive(Clone)]
#[non_exhaustive]
pub struct StorageConfiguration {
pub path: Option<PathBuf>,
pub memory_only: bool,
pub unique_id: Option<u64>,
#[cfg(feature = "encryption")]
pub vault_key_storage: Option<Arc<dyn AnyVaultKeyStorage>>,
#[cfg(feature = "encryption")]
pub default_encryption_key: Option<KeyId>,
pub workers: Tasks,
pub views: Views,
pub key_value_persistence: KeyValuePersistence,
#[cfg(feature = "compression")]
pub default_compression: Option<Compression>,
pub authenticated_permissions: Permissions,
#[cfg(feature = "password-hashing")]
pub argon: ArgonConfiguration,
pub(crate) initial_schemas: HashMap<SchemaName, Arc<dyn DatabaseOpener>>,
}
impl Default for StorageConfiguration {
fn default() -> Self {
let system_specs = RefreshKind::new()
.with_cpu(CpuRefreshKind::new())
.with_memory();
let mut system = System::new_with_specifics(system_specs);
system.refresh_specifics(system_specs);
Self {
path: None,
memory_only: false,
unique_id: None,
#[cfg(feature = "encryption")]
vault_key_storage: None,
#[cfg(feature = "encryption")]
default_encryption_key: None,
#[cfg(feature = "compression")]
default_compression: None,
workers: Tasks::default_for(&system),
views: Views::default(),
key_value_persistence: KeyValuePersistence::default(),
authenticated_permissions: Permissions::default(),
#[cfg(feature = "password-hashing")]
argon: ArgonConfiguration::default_for(&system),
initial_schemas: HashMap::default(),
}
}
}
impl std::fmt::Debug for StorageConfiguration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut schemas = self.initial_schemas.keys().collect::<Vec<_>>();
schemas.sort();
let mut f = f.debug_struct("StorageConfiguration");
f.field("path", &self.path)
.field("memory_only", &self.memory_only)
.field("unique_id", &self.unique_id)
.field("workers", &self.workers)
.field("views", &self.views)
.field("key_value_persistence", &self.key_value_persistence)
.field("authenticated_permissions", &self.authenticated_permissions)
.field("initial_schemas", &schemas);
#[cfg(feature = "encryption")]
f.field("vault_key_storage", &self.vault_key_storage)
.field("default_encryption_key", &self.default_encryption_key);
#[cfg(feature = "compression")]
f.field("default_compression", &self.default_compression);
#[cfg(feature = "password-hashing")]
f.field("argon", &self.argon);
f.finish()
}
}
impl StorageConfiguration {
pub fn register_schema<S: Schema>(&mut self) -> Result<(), Error> {
self.initial_schemas
.insert(S::schema_name(), Arc::new(StorageSchemaOpener::<S>::new()?));
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct Tasks {
pub worker_count: usize,
pub parallelization: usize,
}
impl SystemDefault for Tasks {
fn default_for(system: &System) -> Self {
let num_cpus = system
.physical_core_count()
.unwrap_or(0)
.max(system.cpus().len())
.max(1);
Self {
worker_count: num_cpus * 2,
parallelization: num_cpus,
}
}
}
#[derive(Clone, Debug, Default)]
pub struct Views {
pub check_integrity_on_open: bool,
}
#[derive(Debug, Clone)]
#[must_use]
pub struct KeyValuePersistence(KeyValuePersistenceInner);
#[derive(Debug, Clone)]
enum KeyValuePersistenceInner {
Immediate,
Lazy(Vec<PersistenceThreshold>),
}
impl Default for KeyValuePersistence {
fn default() -> Self {
Self::immediate()
}
}
impl KeyValuePersistence {
pub const fn immediate() -> Self {
Self(KeyValuePersistenceInner::Immediate)
}
pub fn lazy<II>(rules: II) -> Self
where
II: IntoIterator<Item = PersistenceThreshold>,
{
let mut rules = rules.into_iter().collect::<Vec<_>>();
rules.sort_by(|a, b| a.number_of_changes.cmp(&b.number_of_changes));
Self(KeyValuePersistenceInner::Lazy(rules))
}
#[must_use]
pub fn should_commit(
&self,
number_of_changes: usize,
elapsed_since_last_commit: Duration,
) -> bool {
self.duration_until_next_commit(number_of_changes, elapsed_since_last_commit)
== Some(Duration::ZERO)
}
pub(crate) fn duration_until_next_commit(
&self,
number_of_changes: usize,
elapsed_since_last_commit: Duration,
) -> Option<Duration> {
if number_of_changes == 0 {
None
} else {
match &self.0 {
KeyValuePersistenceInner::Immediate => Some(Duration::ZERO),
KeyValuePersistenceInner::Lazy(rules) => {
let mut shortest_duration = Duration::MAX;
for rule in rules
.iter()
.take_while(|rule| rule.number_of_changes <= number_of_changes)
{
let remaining_time =
rule.duration.saturating_sub(elapsed_since_last_commit);
shortest_duration = shortest_duration.min(remaining_time);
if shortest_duration == Duration::ZERO {
break;
}
}
(shortest_duration < Duration::MAX).then_some(shortest_duration)
}
}
}
}
}
#[derive(Debug, Copy, Clone)]
#[must_use]
pub struct PersistenceThreshold {
pub number_of_changes: usize,
pub duration: Duration,
}
impl PersistenceThreshold {
pub const fn after_changes(number_of_changes: usize) -> Self {
Self {
number_of_changes,
duration: Duration::ZERO,
}
}
pub const fn and_duration(mut self, duration: Duration) -> Self {
self.duration = duration;
self
}
}
pub trait Builder: Sized {
#[must_use]
fn new<P: AsRef<Path>>(path: P) -> Self
where
Self: Default,
{
Self::default().path(path)
}
fn with_schema<S: Schema>(self) -> Result<Self, Error>;
#[must_use]
fn memory_only(self) -> Self;
#[must_use]
fn path<P: AsRef<Path>>(self, path: P) -> Self;
#[must_use]
fn unique_id(self, unique_id: u64) -> Self;
#[cfg(feature = "encryption")]
#[must_use]
fn vault_key_storage<VaultKeyStorage: AnyVaultKeyStorage>(
self,
key_storage: VaultKeyStorage,
) -> Self;
#[cfg(feature = "encryption")]
#[must_use]
fn default_encryption_key(self, key: KeyId) -> Self;
#[must_use]
fn tasks_worker_count(self, worker_count: usize) -> Self;
#[must_use]
fn tasks_parallelization(self, parallelization: usize) -> Self;
#[must_use]
fn check_view_integrity_on_open(self, check: bool) -> Self;
#[cfg(feature = "compression")]
#[must_use]
fn default_compression(self, compression: Compression) -> Self;
#[must_use]
fn key_value_persistence(self, persistence: KeyValuePersistence) -> Self;
#[must_use]
fn authenticated_permissions<P: Into<Permissions>>(self, authenticated_permissions: P) -> Self;
#[cfg(feature = "password-hashing")]
#[must_use]
fn argon(self, argon: ArgonConfiguration) -> Self;
}
impl Builder for StorageConfiguration {
fn with_schema<S: Schema>(mut self) -> Result<Self, Error> {
self.register_schema::<S>()?;
Ok(self)
}
fn memory_only(mut self) -> Self {
self.memory_only = true;
self
}
fn path<P: AsRef<Path>>(mut self, path: P) -> Self {
self.path = Some(path.as_ref().to_owned());
self
}
fn unique_id(mut self, unique_id: u64) -> Self {
self.unique_id = Some(unique_id);
self
}
#[cfg(feature = "encryption")]
fn vault_key_storage<VaultKeyStorage: AnyVaultKeyStorage>(
mut self,
key_storage: VaultKeyStorage,
) -> Self {
self.vault_key_storage = Some(Arc::new(key_storage));
self
}
#[cfg(feature = "encryption")]
fn default_encryption_key(mut self, key: KeyId) -> Self {
self.default_encryption_key = Some(key);
self
}
#[cfg(feature = "compression")]
fn default_compression(mut self, compression: Compression) -> Self {
self.default_compression = Some(compression);
self
}
fn tasks_worker_count(mut self, worker_count: usize) -> Self {
self.workers.worker_count = worker_count;
self
}
fn tasks_parallelization(mut self, parallelization: usize) -> Self {
self.workers.parallelization = parallelization;
self
}
fn check_view_integrity_on_open(mut self, check: bool) -> Self {
self.views.check_integrity_on_open = check;
self
}
fn key_value_persistence(mut self, persistence: KeyValuePersistence) -> Self {
self.key_value_persistence = persistence;
self
}
fn authenticated_permissions<P: Into<Permissions>>(
mut self,
authenticated_permissions: P,
) -> Self {
self.authenticated_permissions = authenticated_permissions.into();
self
}
#[cfg(feature = "password-hashing")]
fn argon(mut self, argon: ArgonConfiguration) -> Self {
self.argon = argon;
self
}
}
pub(crate) trait SystemDefault: Sized {
fn default_for(system: &System) -> Self;
fn default() -> Self {
let system_specs = RefreshKind::new()
.with_cpu(CpuRefreshKind::new())
.with_memory();
let mut system = System::new_with_specifics(system_specs);
system.refresh_specifics(system_specs);
Self::default_for(&system)
}
}
#[derive(Debug, Clone, Copy)]
pub enum Compression {
Lz4 = 1,
}
impl Compression {
#[must_use]
#[cfg(feature = "compression")]
pub(crate) fn from_u8(value: u8) -> Option<Self> {
match value {
1 => Some(Self::Lz4),
_ => None,
}
}
}