use_prelude!();
pub mod init;
use std::{
env,
sync::{Once, Weak},
};
pub use ffi_sdk::CLogLevel as LogLevel;
use ffi_sdk::{BoxedDitto, Platform};
use uuid::Uuid;
use crate::{
disk_usage::DiskUsage,
ditto::init::{config::ActualConfig, DittoConfig, DittoConfigConnect},
error::{DittoError, ErrorKind, LicenseTokenError},
identity::DittoAuthenticator,
presence::Presence,
small_peer_info::SmallPeerInfo,
transport::TransportConfig,
utils::{extension_traits::FfiResultIntoRustResult, prelude::*},
};
static SDK_VERSION_INIT: Once = Once::new();
#[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,
config: DittoConfig,
pub(crate) store: Store,
pub(crate) sync: crate::sync::Sync,
presence: Arc<Presence>,
disk_usage: DiskUsage,
small_peer_info: SmallPeerInfo,
}
impl Drop for Ditto {
fn drop(&mut self) {
if self.is_shut_down_able {
self.sync().stop();
ffi_sdk::ditto_shutdown(&self.ditto);
}
}
}
impl Ditto {
pub fn close(self) {
}
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);
}
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 set_offline_only_license_token(&self, license_token: &str) -> Result<(), DittoError> {
if matches!(
self.config.connect,
DittoConfigConnect::SmallPeersOnly { .. }
) {
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();
#[allow(deprecated)] {
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 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 = "preview-datastreams")]
pub fn datastreams(&self) -> crate::preview::datastreams::Endpoint {
ffi_sdk::dittoffi_get_preview_datastreams(&self.ditto)
}
pub fn small_peer_info(&self) -> &SmallPeerInfo {
&self.small_peer_info
}
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::<ActualConfig>(&config_cbor)
.expect("bug: should deserialize DittoConfig")
.customer_facing
}
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());
}
pub fn presence(&self) -> &Arc<Presence> {
&self.presence
}
pub fn disk_usage(&self) -> &DiskUsage {
&self.disk_usage
}
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)
}
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 = "tvos") => Platform::Tvos,
| _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() {
SDK_VERSION_INIT.call_once(|| {
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());
});
}
pub fn version() -> String {
Self::init_sdk_version();
ffi_sdk::dittoffi_get_sdk_semver().to_string()
}
}
impl Ditto {
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);
}
}
#[derive(Clone, Debug)]
pub struct DatabaseId(pub(crate) String);
impl DatabaseId {
pub fn generate() -> Self {
let uuid = uuid::Uuid::new_v4();
DatabaseId::from_uuid(uuid)
}
pub fn from_uuid(uuid: Uuid) -> Self {
let id_str = format!("{:x}", &uuid); DatabaseId(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(DatabaseId(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 DatabaseId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for DatabaseId {
type Err = DittoError;
fn from_str(s: &str) -> Result<DatabaseId, DittoError> {
Ok(DatabaseId(s.to_string()))
}
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;