#[cfg(any(test, feature = "testing"))]
use crate::MockDataDir;
use crate::{
persistence::{
files::{FileIoError, FileService},
lmdb::LmDB,
},
ConfigToml, DataDir, PersistentDataDir,
};
use pkarr::Keypair;
use std::{sync::Arc, time::Duration};
#[derive(Debug, thiserror::Error)]
pub enum AppContextConversionError {
#[error("Failed to ensure data directory exists and is writable: {0}")]
DataDir(anyhow::Error),
#[error("Failed to read or create config file: {0}")]
Config(anyhow::Error),
#[error("Failed to read or create keypair: {0}")]
Keypair(anyhow::Error),
#[error("Failed to open LMDB: {0}")]
LmDB(anyhow::Error),
#[error("Failed to build storage operator: {0}")]
Storage(FileIoError),
#[error("Failed to build pkarr client: {0}")]
Pkarr(pkarr::errors::BuildError),
}
#[derive(Debug, Clone)]
pub struct AppContext {
pub(crate) db: LmDB,
pub(crate) file_service: FileService,
pub(crate) config_toml: ConfigToml,
pub(crate) data_dir: Arc<dyn DataDir>,
pub(crate) keypair: Keypair,
pub(crate) pkarr_client: pkarr::Client,
pub(crate) pkarr_builder: pkarr::ClientBuilder,
}
impl AppContext {
#[cfg(any(test, feature = "testing"))]
pub fn test() -> Self {
let data_dir = MockDataDir::test();
Self::try_from(data_dir).expect("failed to build AppContext from DataDirMock")
}
}
impl TryFrom<Arc<dyn DataDir>> for AppContext {
type Error = AppContextConversionError;
fn try_from(dir: Arc<dyn DataDir>) -> Result<Self, Self::Error> {
dir.ensure_data_dir_exists_and_is_writable()
.map_err(AppContextConversionError::DataDir)?;
let conf = dir
.read_or_create_config_file()
.map_err(AppContextConversionError::Config)?;
let keypair = dir
.read_or_create_keypair()
.map_err(AppContextConversionError::Keypair)?;
let db_path = dir.path().join("data/lmdb");
let db = unsafe { LmDB::open(&db_path).map_err(AppContextConversionError::LmDB)? };
let file_service = FileService::new_from_config(&conf, dir.path(), db.clone())
.map_err(AppContextConversionError::Storage)?;
let pkarr_builder = Self::build_pkarr_builder_from_config(&conf);
Ok(Self {
db,
pkarr_client: pkarr_builder
.clone()
.build()
.map_err(AppContextConversionError::Pkarr)?,
file_service,
pkarr_builder,
config_toml: conf,
keypair,
data_dir: dir,
})
}
}
impl TryFrom<PersistentDataDir> for AppContext {
type Error = AppContextConversionError;
fn try_from(dir: PersistentDataDir) -> Result<Self, Self::Error> {
let arc_dir: Arc<dyn DataDir> = Arc::new(dir);
Self::try_from(arc_dir)
}
}
#[cfg(any(test, feature = "testing"))]
impl TryFrom<MockDataDir> for AppContext {
type Error = AppContextConversionError;
fn try_from(dir: MockDataDir) -> Result<Self, Self::Error> {
let arc_dir: Arc<dyn DataDir> = Arc::new(dir);
Self::try_from(arc_dir)
}
}
impl AppContext {
fn build_pkarr_builder_from_config(config_toml: &ConfigToml) -> pkarr::ClientBuilder {
let mut builder = pkarr::ClientBuilder::default();
if let Some(bootstrap_nodes) = &config_toml.pkdns.dht_bootstrap_nodes {
let nodes = bootstrap_nodes
.iter()
.map(|node| node.to_string())
.collect::<Vec<String>>();
builder.bootstrap(&nodes);
builder.no_relays();
}
if let Some(relays) = &config_toml.pkdns.dht_relay_nodes {
builder
.relays(relays)
.expect("parameters are already urls and therefore valid.");
}
if let Some(request_timeout) = &config_toml.pkdns.dht_request_timeout_ms {
let duration = Duration::from_millis(request_timeout.get());
builder.request_timeout(duration);
}
builder
}
}