use ffi_sdk::{
ffi_utils::{DynDrop, DynExecutor},
Platform,
};
use super::*;
use crate::identity::Identity;
pub struct DittoBuilder {
ditto_root: Option<Arc<dyn DittoRoot>>,
identity: Option<Arc<dyn Identity>>,
minimum_log_level: LogLevel,
transport_config: Option<TransportConfig>,
}
impl DittoBuilder {
pub fn new() -> DittoBuilder {
DittoBuilder {
ditto_root: None,
identity: None,
minimum_log_level: LogLevel::Info,
transport_config: None,
}
}
pub fn with_root(mut self, ditto_root: Arc<dyn DittoRoot>) -> Self {
self.ditto_root = Some(ditto_root);
self
}
pub fn with_minimum_log_level(mut self, log_level: LogLevel) -> Self {
self.minimum_log_level = log_level;
self
}
pub fn with_temp_dir(mut self) -> Self {
let root = TempRoot::new();
self.ditto_root = Some(Arc::new(root));
self
}
fn platform() -> Platform {
using!(match () {
use ffi_sdk::Platform;
| _case if cfg!(target_os = "windows") => Platform::Windows,
| _case if cfg!(target_os = "android") => Platform::Android,
| _case if cfg!(target_os = "macos") => Platform::Mac,
| _case if cfg!(target_os = "ios") => Platform::Ios,
| _case if cfg!(target_os = "linux") => Platform::Linux,
| _default => Platform::Unknown,
})
}
fn sdk_version() -> String {
let sdk_semver = env!("CARGO_PKG_VERSION");
sdk_semver.to_string()
}
fn init_sdk_version() {
let platform = Self::platform();
let sdk_semver = Self::sdk_version();
let c_version = char_p::new(sdk_semver);
ffi_sdk::ditto_init_sdk_version(platform, ffi_sdk::Language::Rust, c_version.as_ref());
}
fn init_logging(&self) {
ffi_sdk::ditto_logger_init();
ffi_sdk::ditto_logger_minimum_log_level(self.minimum_log_level);
ffi_sdk::ditto_logger_enabled(true);
}
pub fn with_identity<F, I>(mut self, factory: F) -> Result<Self, DittoError>
where
F: FnOnce(Arc<dyn DittoRoot>) -> Result<I, DittoError>,
I: Identity + 'static, {
match &self.ditto_root {
Some(root) => {
let identity = factory(root.retain())?;
self.identity = Some(Arc::new(identity));
}
None => {
let msg = "A valid DittoRoot directory must be provided before configuring the \
Identity"
.to_string();
return Err(DittoError::new(ErrorKind::Config, msg));
}
};
Ok(self)
}
pub fn with_transport_config<T>(mut self, factory: T) -> Result<Self, DittoError>
where
T: FnOnce(Arc<dyn Identity>) -> TransportConfig,
{
match &self.identity {
Some(id) => {
let config = factory(id.retain());
self.transport_config = Some(config)
}
None => {
let msg = "A DittoRoot directory and Identity must first be specified before \
providing a custom TransportConfig"
.to_string();
return Err(DittoError::new(ErrorKind::Config, msg));
}
}
Ok(self)
}
pub fn build(self) -> Result<Ditto, DittoError> {
self.init_logging();
Self::init_sdk_version();
let ditto_root = self.ditto_root.ok_or_else(|| {
DittoError::new(ErrorKind::Config, "No Ditto Root Directory provided")
})?;
crate::fs::drain_ditto_data_dir(&ditto_root);
let c_root_dir = ditto_root.root_dir_to_c_str()?;
let identity = self
.identity
.ok_or_else(|| DittoError::new(ErrorKind::Config, "No Identity specified"))?;
let executor = make_executor();
let uninit_ditto =
ffi_sdk::uninitialized_ditto_make_with_executor(c_root_dir.as_ref(), executor);
let identity_config = identity
.identity_config()
.expect("identity config to be Some");
let boxed_ditto = ffi_sdk::ditto_make(
uninit_ditto,
identity_config,
ffi_sdk::HistoryTracking::Disabled,
);
let ditto: DittoHandleWrapper = Arc::new(boxed_ditto);
let site_id: SiteId = ffi_sdk::ditto_auth_client_get_site_id(&ditto);
let store = Store::new(ditto.retain());
let transport_config = self.transport_config.unwrap_or_else(|| {
let mut config = TransportConfig::new();
config.enable_all_peer_to_peer();
config
});
let transports: Arc<RwLock<TransportSync>> = Arc::new(RwLock::new(
TransportSync::from_config(transport_config, ditto.retain(), identity.retain()),
));
let auth = identity.authenticator();
let validity_listener = Some(ValidityListener::new(Arc::downgrade(&transports), &ditto));
let presence = Arc::new(Presence::new(ditto.retain()));
let disk_usage = DiskUsage::new(ditto.retain(), FsComponent::Root);
let fields_from_auth = |auth| DittoFields {
ditto: ditto.retain(),
auth,
identity: identity.retain(),
store,
activated: identity.requires_offline_only_license_token().not().into(),
site_id,
transports,
ditto_root,
validity_listener,
presence,
disk_usage,
};
let fields = if let Some(mut auth) = auth {
Arc::new_cyclic(|weak_fields: &arc::Weak<_>| {
auth.ditto_fields = weak_fields.clone();
fields_from_auth(Some(auth))
})
} else {
Arc::new(fields_from_auth(None))
};
identity.set_login_provider(fields.auth.as_ref().map(|a| a.retain()));
let sdk_ditto = Ditto {
fields,
is_shut_down_able: true,
};
Ok(sdk_ditto)
}
}
impl Default for DittoBuilder {
fn default() -> Self {
Self::new()
}
}
fn make_executor() -> DynExecutor {
idempotent_register_executor_functions();
if let Ok(handle) = ::tokio::runtime::Handle::try_current() {
DynExecutor {
handle: Arc::new(handle).into(),
_runtime: DynDrop::new(()),
}
} else {
DynExecutor::new().unwrap()
}
}
fn idempotent_register_executor_functions() {
#![allow(improper_ctypes_definitions)]
static ONCE: ::std::sync::Once = ::std::sync::Once::new();
ONCE.call_once(|| {
ffi_sdk::register_executor_functions({
use ::ffi_sdk::*;
extern "C" fn rust_sdk_get_current_handle() -> FfiHandle {
Arc::new(::tokio::runtime::Handle::current()).into()
}
extern "C" fn rust_sdk_block_in_place(
func: ::safer_ffi::prelude::VirtualPtr<dyn '_ + FfiFnMut>,
) {
::tokio::task::block_in_place(move || { func }.call())
}
ExecutorFunctions {
get_current_handle: rust_sdk_get_current_handle,
block_in_place: BlockInPlace(rust_sdk_block_in_place),
}
})
});
}