use std::path::PathBuf;
use std::sync::Arc;
use anyhow::Result;
use hashiverse_lib::client::args::Args;
use hashiverse_lib::client::client_storage::sqlite_client_storage::SqliteClientStorage;
use hashiverse_lib::client::hashiverse_client::HashiverseClient;
use hashiverse_lib::client::key_locker::disk_key_locker::{DiskKeyLocker, DiskKeyLockerManager};
use hashiverse_lib::client::key_locker::key_locker::KeyLockerManager;
use hashiverse_lib::tools::pow_generator::native_parallel_pow_generator::NativeParallelPowGenerator;
use hashiverse_lib::tools::runtime_services::RuntimeServices;
use hashiverse_lib::tools::time_provider::time_provider::RealTimeProvider;
use hashiverse_lib::transport::bootstrap_provider::bootstrap_provider::BootstrapProvider;
use hashiverse_lib::transport::bootstrap_provider::dnssec_bootstrap_provider::DnssecBootstrapProvider;
use hashiverse_lib::transport::bootstrap_provider::manual_bootstrap_provider::ManualBootstrapProvider;
use hashiverse_lib::transport::partial_https_transport::PartialHttpsTransportFactory;
pub use hashiverse_lib;
pub struct HashiverseBuilder {
data_dir: PathBuf,
passphrase: String,
bootstrap_addresses: Option<Vec<String>>,
}
impl Default for HashiverseBuilder {
fn default() -> Self {
Self {
data_dir: default_data_dir(),
passphrase: String::new(),
bootstrap_addresses: None,
}
}
}
impl HashiverseBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn data_dir(mut self, dir: impl Into<PathBuf>) -> Self {
let dir = dir.into();
let expanded = shellexpand::tilde(&dir.to_string_lossy()).into_owned();
self.data_dir = PathBuf::from(expanded);
self
}
pub fn passphrase(mut self, passphrase: impl Into<String>) -> Self {
self.passphrase = passphrase.into();
self
}
pub fn bootstrap(mut self, addresses: Vec<String>) -> Self {
self.bootstrap_addresses = Some(addresses);
self
}
pub async fn build_with_keyphrase(self, key_phrase: impl Into<String>) -> Result<Hashiverse> {
std::fs::create_dir_all(&self.data_dir)?;
let key_locker_manager = DiskKeyLockerManager::with_data_dir(self.data_dir.clone(), self.passphrase.clone())?;
let key_locker: Arc<DiskKeyLocker> = key_locker_manager.create(key_phrase.into()).await?;
self.assemble(key_locker_manager, key_locker).await
}
pub async fn build_from_stored_key(self, client_id_hex: impl Into<String>) -> Result<Hashiverse> {
std::fs::create_dir_all(&self.data_dir)?;
let key_locker_manager = DiskKeyLockerManager::with_data_dir(self.data_dir.clone(), self.passphrase.clone())?;
let key_locker: Arc<DiskKeyLocker> = key_locker_manager.switch(client_id_hex.into()).await?;
self.assemble(key_locker_manager, key_locker).await
}
async fn assemble(
self,
key_locker_manager: Arc<DiskKeyLockerManager>,
key_locker: Arc<DiskKeyLocker>,
) -> Result<Hashiverse> {
let client_storage_dir = self.data_dir.join("client_storage");
std::fs::create_dir_all(&client_storage_dir)?;
let time_provider = Arc::new(RealTimeProvider);
let bootstrap_provider: Arc<dyn BootstrapProvider> = match self.bootstrap_addresses {
Some(addresses) => ManualBootstrapProvider::new(addresses),
None => Arc::new(DnssecBootstrapProvider::new()),
};
let transport_factory = Arc::new(PartialHttpsTransportFactory::new(bootstrap_provider));
let pow_generator = Arc::new(NativeParallelPowGenerator::new());
let runtime_services = Arc::new(RuntimeServices {
time_provider,
transport_factory,
pow_generator,
});
let client_storage = SqliteClientStorage::new(client_storage_dir).await?;
let hashiverse_client = HashiverseClient::new(
runtime_services,
client_storage,
key_locker,
Args::default(),
)
.await?;
Ok(Hashiverse {
client: Arc::new(hashiverse_client),
_key_locker_manager: key_locker_manager,
})
}
}
pub struct Hashiverse {
client: Arc<HashiverseClient>,
_key_locker_manager: Arc<DiskKeyLockerManager>,
}
impl Hashiverse {
pub fn client(&self) -> &Arc<HashiverseClient> {
&self.client
}
}
impl std::ops::Deref for Hashiverse {
type Target = HashiverseClient;
fn deref(&self) -> &HashiverseClient {
&self.client
}
}
fn default_data_dir() -> PathBuf {
dirs_next::data_dir().unwrap_or_else(std::env::temp_dir).join("hashiverse")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_defaults_resolve_to_a_data_dir() {
let builder = HashiverseBuilder::new();
assert!(builder.data_dir.ends_with("hashiverse"), "default data dir should end in 'hashiverse': {:?}", builder.data_dir);
assert!(builder.passphrase.is_empty());
assert!(builder.bootstrap_addresses.is_none());
}
#[test]
fn data_dir_tilde_expansion() {
let builder = HashiverseBuilder::new().data_dir("~/hashiverse-test");
let expanded = builder.data_dir.to_string_lossy();
assert!(!expanded.starts_with("~"), "tilde should be expanded: {}", expanded);
}
}