use_prelude!();
pub mod builder;
use std::{
env,
sync::{RwLock, Weak},
};
use crossbeam_utils::atomic::AtomicCell;
pub use ffi_sdk::CLogLevel as LogLevel;
use ffi_sdk::{BoxedDitto, FsComponent};
use tracing::error;
use uuid::Uuid;
use self::builder::DittoBuilder;
#[allow(deprecated)]
use crate::identity::auth::ValidityListener;
#[allow(deprecated)]
use crate::presence::observer::PeersObserver;
use crate::{
disk_usage::DiskUsage,
error::{DittoError, ErrorKind, LicenseTokenError},
identity::{DittoAuthenticator, SharedIdentity},
presence::Presence,
small_peer_info::SmallPeerInfo,
transport::{TransportConfig, TransportSync},
utils::prelude::*,
};
pub(crate) type DittoHandleWrapper = Arc<BoxedDitto>;
pub(crate) type WeakDittoHandleWrapper = std::sync::Weak<BoxedDitto>;
#[extension(pub(crate) trait TryUpgrade)]
impl WeakDittoHandleWrapper {
fn try_upgrade(&self) -> Result<DittoHandleWrapper, 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: DittoHandleWrapper,
identity: SharedIdentity,
auth: Option<DittoAuthenticator>,
#[allow(dead_code, deprecated)]
validity_listener: Option<Arc<ValidityListener>>, pub(crate) store: Store,
pub(crate) sync: crate::sync::Sync,
activated: AtomicCell<bool>,
#[allow(deprecated)]
site_id: SiteId,
transports: Arc<RwLock<TransportSync>>,
presence: Arc<Presence>,
disk_usage: DiskUsage,
small_peer_info: SmallPeerInfo,
ditto_root: Arc<dyn DittoRoot>,
#[cfg(feature = "experimental-bus")]
bus: Option<crate::experimental::bus::Bus>,
}
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> {
if self.activated.load().not() && self.identity.requires_offline_only_license_token() {
return Err(ErrorKind::NotActivated.into());
}
match self.transports.write() {
Ok(mut transports) => {
transports.start_sync();
Ok(())
}
Err(e) => {
let rust_error = format!("{:?}", e); Err(DittoError::new(ErrorKind::Internal, rust_error))
}
}
}
pub fn stop_sync(&self) {
if let Ok(mut transports) = self.transports.write() {
transports.stop_sync()
}
}
pub fn set_transport_config(&self, config: TransportConfig) {
if let Ok(mut transports) = self.transports.write() {
transports.set_transport_config(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> {
match self.transports.read() {
Ok(t) => Ok(t.current_config().clone()),
Err(e) => {
let msg = format!("transport config cannot be read {:?}", e);
Err(DittoError::new(ErrorKind::Internal, msg))
}
}
}
pub fn transport_config(&self) -> TransportConfig {
let transports = self.transports.read().expect("should read TransportConfig");
transports.current_config().clone()
}
#[cfg(test)]
pub fn effective_transport_config(&self) -> Result<TransportConfig, DittoError> {
match self.transports.read() {
Ok(t) => Ok(t.effective_config().clone()),
Err(e) => {
let msg = format!("transport config cannot be read {:?}", e);
Err(DittoError::new(ErrorKind::Internal, msg))
}
}
}
}
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.identity.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(c_license.as_ref(), Some(out_err_msg));
if res == LicenseVerificationResult::LicenseOk {
self.activated.store(true);
return Ok(());
}
self.activated.store(false);
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) -> Option<&crate::experimental::bus::Bus> {
self.bus.as_ref()
}
pub fn small_peer_info(&self) -> &SmallPeerInfo {
&self.small_peer_info
}
pub fn site_id(&self) -> u64 {
self.site_id
}
pub fn persistence_directory(&self) -> &Path {
self.ditto_root.root_path()
}
#[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());
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 persistence_directory instead")]
pub fn root_dir(&self) -> &Path {
self.persistence_directory()
}
#[doc(hidden)]
#[deprecated(note = "Use persistence_directory instead")]
pub fn data_dir(&self) -> &Path {
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.clone()
}
pub fn auth(&self) -> Option<DittoAuthenticator> {
self.auth.clone()
}
pub fn is_activated(&self) -> bool {
self.activated.load()
}
}
impl Ditto {
pub fn builder() -> DittoBuilder {
#[allow(deprecated)]
DittoBuilder::new()
}
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;