use_prelude!();
pub mod builder;
pub mod init;
use std::{env, sync::Weak};
pub use ffi_sdk::CLogLevel as LogLevel;
use ffi_sdk::{BoxedDitto, FsComponent};
use uuid::Uuid;
#[allow(deprecated)]
use self::builder::DittoBuilder;
#[allow(deprecated)]
use crate::presence::observer_legacy::PeersObserver;
use crate::{
disk_usage::DiskUsage,
ditto::init::ConfigOrIdentity,
error::{DittoError, ErrorKind, LicenseTokenError},
identity::DittoAuthenticator,
presence::Presence,
small_peer_info::SmallPeerInfo,
transport::TransportConfig,
utils::{extension_traits::FfiResultIntoRustResult, prelude::*},
};
#[extension(pub(crate) trait TryUpgrade)]
impl std::sync::Weak<BoxedDitto> {
fn try_upgrade(&self) -> Result<Arc<BoxedDitto>, ErrorKind> {
self.upgrade().ok_or(ErrorKind::ReleasedDittoInstance)
}
}
pub struct Ditto {
pub(crate) fields: Arc<DittoFields>,
is_shut_down_able: bool,
}
impl std::ops::Deref for Ditto {
type Target = DittoFields;
#[inline]
fn deref(&'_ self) -> &'_ DittoFields {
&self.fields
}
}
impl Ditto {
pub(crate) fn upgrade(weak: &Weak<DittoFields>) -> Result<Ditto> {
let fields = weak.upgrade().ok_or(ErrorKind::ReleasedDittoInstance)?;
Ok(Ditto::new_temp(fields))
}
}
#[doc(hidden)]
pub struct DittoFields {
pub(crate) ditto: Arc<ffi_sdk::BoxedDitto>,
has_auth: bool,
state: ConfigOrIdentity,
pub(crate) store: Store,
pub(crate) sync: crate::sync::Sync,
#[allow(deprecated)]
site_id: SiteId,
presence: Arc<Presence>,
disk_usage: DiskUsage,
small_peer_info: SmallPeerInfo,
ditto_root: Arc<dyn DittoRoot>, }
impl Drop for Ditto {
fn drop(&mut self) {
if self.is_shut_down_able {
self.stop_sync();
ffi_sdk::ditto_shutdown(&self.ditto);
}
}
}
impl Ditto {
pub fn close(self) {
}
pub fn start_sync(&self) -> Result<(), DittoError> {
let result = ffi_sdk::dittoffi_ditto_try_start_sync(&self.ditto);
if let Some(error) = result.error {
if ffi_sdk::dittoffi_error_code(&*error)
== ffi_sdk::FfiErrorCode::ActivationNotActivated
{
return Err(ErrorKind::NotActivated.into());
}
return Err(DittoError::from(error));
}
Ok(())
}
pub fn stop_sync(&self) {
ffi_sdk::dittoffi_ditto_stop_sync(&self.ditto);
}
pub fn is_sync_active(&self) -> bool {
ffi_sdk::dittoffi_ditto_is_sync_active(&self.ditto)
}
pub fn set_transport_config(&self, config: TransportConfig) {
let cbor = serde_cbor::to_vec(&config).expect("bug: failed to serialize TransportConfig");
ffi_sdk::dittoffi_ditto_try_set_transport_config(&self.ditto, (&*cbor).into(), false)
.into_rust_result()
.expect("bug: core failed to set transport config");
}
pub fn update_transport_config(&self, update: impl FnOnce(&mut TransportConfig)) {
let mut transport_config = self.transport_config();
update(&mut transport_config);
self.set_transport_config(transport_config);
}
#[doc(hidden)]
#[deprecated(note = "Use `.transport_config()` instead")]
pub fn current_transport_config(&self) -> Result<TransportConfig, DittoError> {
let transport_config_cbor = ffi_sdk::dittoffi_ditto_transport_config(&self.ditto);
let transport_config =
serde_cbor::from_slice::<TransportConfig>(transport_config_cbor.as_slice())?;
Ok(transport_config)
}
pub fn transport_config(&self) -> TransportConfig {
let transport_config_cbor = ffi_sdk::dittoffi_ditto_transport_config(&self.ditto);
serde_cbor::from_slice::<TransportConfig>(transport_config_cbor.as_slice())
.expect("bug: failed to deserialize TransportConfig from core")
}
}
impl Ditto {
pub fn with_sdk_version<R>(ret: impl FnOnce(&'_ str) -> R) -> R {
ret(ffi_sdk::ditto_get_sdk_version().to_str())
}
}
#[doc(hidden)]
impl Ditto {
#[deprecated(note = "Use `DittoLogger::set_logging_enabled()` instead")]
pub fn set_logging_enabled(enabled: bool) {
ffi_sdk::ditto_logger_enabled(enabled)
}
#[deprecated(note = "Use `DittoLogger::get_logging_enabled()` instead")]
pub fn get_logging_enabled() -> bool {
ffi_sdk::ditto_logger_enabled_get()
}
#[deprecated(note = "Use `DittoLogger::get_emoji_log_level_headings_enabled()` instead")]
pub fn get_emoji_log_level_headings_enabled() -> bool {
ffi_sdk::ditto_logger_emoji_headings_enabled_get()
}
#[deprecated(note = "Use `DittoLogger::set_emoji_log_level_headings_enabled()` instead")]
pub fn set_emoji_log_level_headings_enabled(enabled: bool) {
ffi_sdk::ditto_logger_emoji_headings_enabled(enabled);
}
#[deprecated(note = "Use `DittoLogger::get_minimum_log_level()` instead")]
pub fn get_minimum_log_level() -> LogLevel {
ffi_sdk::ditto_logger_minimum_log_level_get()
}
#[deprecated(note = "Use `DittoLogger::set_minimum_log_level()` instead")]
pub fn set_minimum_log_level(log_level: LogLevel) {
ffi_sdk::ditto_logger_minimum_log_level(log_level);
}
}
impl Ditto {
pub fn set_offline_only_license_token(&self, license_token: &str) -> Result<(), DittoError> {
if self.state.requires_offline_only_license_token() {
use ffi_sdk::LicenseVerificationResult;
use safer_ffi::prelude::{AsOut, ManuallyDropMut};
let c_license: char_p::Box = char_p::new(license_token);
let mut err_msg = None;
let out_err_msg = err_msg.manually_drop_mut().as_out();
let res =
ffi_sdk::ditto_verify_license(&self.ditto, c_license.as_ref(), Some(out_err_msg));
if res == LicenseVerificationResult::LicenseOk {
return Ok(());
}
let err_msg = err_msg.unwrap();
error!("{err_msg}");
match res {
LicenseVerificationResult::LicenseExpired => {
Err(DittoError::license(LicenseTokenError::Expired {
message: err_msg.as_ref().to_string(),
}))
}
LicenseVerificationResult::VerificationFailed => {
Err(DittoError::license(LicenseTokenError::VerificationFailed {
message: err_msg.as_ref().to_string(),
}))
}
LicenseVerificationResult::UnsupportedFutureVersion => Err(DittoError::license(
LicenseTokenError::UnsupportedFutureVersion {
message: err_msg.as_ref().to_string(),
},
)),
_ => panic!("Unexpected license verification result {:?}", res),
}
} else {
Err(DittoError::new(
ErrorKind::Internal,
"Offline license tokens should only be used for Manual, SharedKey or \
OfflinePlayground identities",
))
}
}
pub fn set_license_from_env(&self, var_name: &str) -> Result<(), DittoError> {
match env::var(var_name) {
Ok(token) => self.set_offline_only_license_token(&token),
Err(env::VarError::NotPresent) => {
let msg = format!("No license token found for env var {}", &var_name);
Err(DittoError::from_str(ErrorKind::Config, msg))
}
Err(e) => Err(DittoError::new(ErrorKind::Config, e)),
}
}
}
impl Ditto {
pub fn store(&self) -> &Store {
&self.store
}
pub fn sync(&self) -> &crate::sync::Sync {
&self.sync
}
#[cfg(feature = "experimental-bus")]
pub fn bus(&self) -> crate::experimental::bus::Bus {
ffi_sdk::dittoffi_get_experimental_bus(&self.ditto)
}
pub fn small_peer_info(&self) -> &SmallPeerInfo {
&self.small_peer_info
}
#[doc(hidden)]
#[deprecated(note = "Use `ditto.presence().graph().local_peer.peer_key_string` instead")]
pub fn site_id(&self) -> u64 {
self.site_id
}
#[doc(hidden)]
#[deprecated(note = "Use `ditto.absolute_persistence_directory()` instead")]
pub fn persistence_directory(&self) -> &Path {
self.ditto_root.root_path()
}
pub fn absolute_persistence_directory(&self) -> PathBuf {
let path = ffi_sdk::dittoffi_ditto_absolute_persistence_directory(&self.ditto);
PathBuf::from(path.to_str())
}
pub fn config(&self) -> DittoConfig {
let config_cbor = ffi_sdk::dittoffi_ditto_config(&self.ditto);
serde_cbor::from_slice(&config_cbor).expect("bug: should deserialize DittoConfig")
}
#[doc(hidden)]
#[deprecated(note = "Use `.app_id()` instead")]
pub fn application_id(&self) -> AppId {
AppId(ffi_sdk::ditto_auth_client_get_app_id(&self.ditto).into_string())
}
pub fn app_id(&self) -> AppId {
AppId(ffi_sdk::ditto_auth_client_get_app_id(&self.ditto).into_string())
}
pub fn set_device_name(&self, name: &str) {
let c_device_name: char_p::Box = char_p::new(name.to_owned());
let _ = ffi_sdk::ditto_set_device_name(&self.ditto, c_device_name.as_ref());
}
#[doc(hidden)]
#[deprecated(note = "Use `presence().observe()` instead")]
#[allow(deprecated)]
pub fn observe_peers<H>(&self, handler: H) -> PeersObserver
where
H: Fn(crate::transport::v2::V2Presence) + Send + Sync + 'static,
{
self.presence.add_observer(handler)
}
pub fn presence(&self) -> &Arc<Presence> {
&self.presence
}
pub fn disk_usage(&self) -> &DiskUsage {
&self.disk_usage
}
#[doc(hidden)]
#[deprecated(note = "Use `ditto.absolute_persistence_directory()` instead")]
pub fn root_dir(&self) -> &Path {
#[allow(deprecated)]
self.persistence_directory()
}
#[doc(hidden)]
#[deprecated(note = "Use `ditto.absolute_persistence_directory()` instead")]
pub fn data_dir(&self) -> &Path {
#[allow(deprecated)]
self.persistence_directory()
}
#[cfg(test)]
pub fn root(&self) -> Arc<dyn DittoRoot> {
self.ditto_root.retain()
}
#[doc(hidden)]
#[deprecated(note = "Use `.auth()` instead")]
pub fn authenticator(&self) -> Option<DittoAuthenticator> {
self.auth()
}
pub fn auth(&self) -> Option<DittoAuthenticator> {
self.fields.has_auth.then(|| DittoAuthenticator {
ditto_fields: Arc::downgrade(&self.fields),
})
}
pub fn is_activated(&self) -> bool {
ffi_sdk::dittoffi_ditto_is_activated(&self.ditto)
}
}
impl Ditto {
pub fn builder() -> DittoBuilder {
#[allow(deprecated)]
DittoBuilder::new()
}
#[doc(hidden)]
#[deprecated(note = "Use `Ditto::open(...)` or `Ditto::open_sync(...)` instead")]
pub fn new(app_id: AppId) -> Ditto {
Ditto::builder()
.with_root(Arc::new(
PersistentRoot::from_current_exe().expect("Invalid Ditto Root"),
))
.with_identity(|ditto_root| identity::OfflinePlayground::new(ditto_root, app_id))
.expect("Invalid Ditto Identity")
.with_minimum_log_level(LogLevel::Info)
.build()
.expect("Failed to build Ditto Instance")
}
pub(crate) fn new_temp(fields: Arc<DittoFields>) -> Ditto {
Ditto {
fields,
is_shut_down_able: false,
}
}
}
impl Ditto {
pub fn run_garbage_collection(&self) {
ffi_sdk::ditto_run_garbage_collection(&self.ditto);
}
pub fn disable_sync_with_v3(&self) -> Result<(), DittoError> {
let res = ffi_sdk::ditto_disable_sync_with_v3(&self.ditto);
if res != 0 {
return Err(DittoError::from_ffi(ErrorKind::Internal));
}
Ok(())
}
}
#[doc(hidden)] pub struct TransportDiagnostics;
#[deprecated(note = "Use `ditto.presence().graph().local_peer.peer_key_string` instead")]
pub type SiteId = u64;
#[derive(Clone, Debug)]
pub struct AppId(pub(crate) String);
impl AppId {
pub fn generate() -> Self {
let uuid = uuid::Uuid::new_v4();
AppId::from_uuid(uuid)
}
pub fn from_uuid(uuid: Uuid) -> Self {
let id_str = format!("{:x}", &uuid); AppId(id_str)
}
pub fn from_env(var: &str) -> Result<Self, DittoError> {
let id_str = env::var(var).map_err(|err| DittoError::new(ErrorKind::Config, err))?;
Ok(AppId(id_str))
}
pub fn as_str(&self) -> &str {
&self.0
}
pub fn to_c_string(&self) -> char_p::Box {
char_p::new(self.0.as_str())
}
pub fn default_auth_url(&self) -> String {
format!("https://{}.cloud.ditto.live", self.0)
}
pub fn default_sync_url(&self) -> String {
format!("wss://{}.cloud.ditto.live", self.0)
}
}
use std::{fmt, fmt::Display, str::FromStr};
impl Display for AppId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for AppId {
type Err = DittoError;
fn from_str(s: &str) -> Result<AppId, DittoError> {
Ok(AppId(s.to_string()))
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;