dittolive_ditto/ditto/
mod.rs

1//! # Entry point for the DittoSDK
2//!
3//! `Ditto` is a cross-platform peer-to-peer database that allows apps to sync with and even without
4//! internet connectivity.
5//!
6//! To manage your local data and connections to other peers in the mesh, `Ditto` gives you access
7//! to:
8//! * [`Store`], the entry point to the database.
9//! * [`TransportConfig`] to change the transport layers in use.
10//! * [`Presence`] to monitor peers in the mesh.
11//! * [`DiskUsage`] to monitor local Ditto disk usage.
12
13use_prelude!();
14
15pub mod builder;
16
17use std::{
18    env,
19    sync::{RwLock, Weak},
20};
21
22use crossbeam_utils::atomic::AtomicCell;
23/// The log levels that the Ditto SDK supports.
24pub use ffi_sdk::CLogLevel as LogLevel;
25use ffi_sdk::{BoxedDitto, FsComponent};
26use tracing::error;
27use uuid::Uuid;
28
29use self::builder::DittoBuilder;
30#[allow(deprecated)]
31use crate::identity::auth::ValidityListener;
32#[allow(deprecated)]
33use crate::presence::observer::PeersObserver;
34use crate::{
35    disk_usage::DiskUsage,
36    error::{DittoError, ErrorKind, LicenseTokenError},
37    identity::{DittoAuthenticator, SharedIdentity},
38    presence::Presence,
39    small_peer_info::SmallPeerInfo,
40    transport::{TransportConfig, TransportSync},
41    utils::prelude::*,
42};
43
44/// This strong shared reference to Ditto should only be shared within the Ditto struct.
45pub(crate) type DittoHandleWrapper = Arc<BoxedDitto>;
46/// This weak reference to Ditto is intended for objects that wouldn't be freed by Ditto::drop.
47pub(crate) type WeakDittoHandleWrapper = std::sync::Weak<BoxedDitto>;
48
49#[extension(pub(crate) trait TryUpgrade)]
50impl WeakDittoHandleWrapper {
51    fn try_upgrade(&self) -> Result<DittoHandleWrapper, ErrorKind> {
52        self.upgrade().ok_or(ErrorKind::ReleasedDittoInstance)
53    }
54}
55
56/// The entrypoint for accessing all Ditto functionality.
57///
58/// Use the `Ditto` object to access all other Ditto APIs, such as:
59///
60/// - [`ditto.store()`] to access the [`Store`] API and read and write data on this peer
61/// - [`ditto.sync()`] to access the [`Sync`] API and sync data with other peers
62/// - [`ditto.presence()`] to access the [`Presence`] API and inspect connected peers
63/// - [`ditto.small_peer_info()`] to access the [`SmallPeerInfo`] API and manage peer metadata
64/// - [`ditto.disk_usage()`] to access the [`DiskUsage`] API and inspect disk usage
65///
66/// [`ditto.store()`]: crate::Ditto::store
67/// [`ditto.sync()`]: crate::Ditto::sync
68/// [`ditto.presence()`]: crate::Ditto::presence
69/// [`ditto.small_peer_info()`]: crate::Ditto::small_peer_info
70/// [`ditto.disk_usage()`]: crate::Ditto::disk_usage
71pub struct Ditto {
72    pub(crate) fields: Arc<DittoFields>,
73    /// Always `true` except in `Auth::logout()`.
74    is_shut_down_able: bool,
75}
76
77impl std::ops::Deref for Ditto {
78    type Target = DittoFields;
79
80    #[inline]
81    fn deref(&'_ self) -> &'_ DittoFields {
82        &self.fields
83    }
84}
85
86impl Ditto {
87    pub(crate) fn upgrade(weak: &Weak<DittoFields>) -> Result<Ditto> {
88        let fields = weak.upgrade().ok_or(ErrorKind::ReleasedDittoInstance)?;
89        Ok(Ditto::new_temp(fields))
90    }
91}
92
93/// Inner fields for Ditto
94#[doc(hidden)]
95// TODO(pub_check)
96pub struct DittoFields {
97    // FIXME: (Ham & Daniel) - ideally we'd use this in the same way as we do
98    // with the `fields` on `Ditto` (only ever extracting weak references)
99    pub(crate) ditto: DittoHandleWrapper,
100    identity: SharedIdentity,
101    auth: Option<DittoAuthenticator>,
102    #[allow(dead_code, deprecated)]
103    validity_listener: Option<Arc<ValidityListener>>, // may not need to hang on to this
104    pub(crate) store: Store,
105    pub(crate) sync: crate::sync::Sync,
106    activated: AtomicCell<bool>,
107    #[allow(deprecated)]
108    site_id: SiteId,
109    transports: Arc<RwLock<TransportSync>>,
110    presence: Arc<Presence>,
111    disk_usage: DiskUsage,
112    small_peer_info: SmallPeerInfo,
113    // This field must go last since after it is dropped the backing fs will dangle.
114    ditto_root: Arc<dyn DittoRoot>, // Arc since Identity will need a copy
115
116    #[cfg(feature = "experimental-bus")]
117    bus: Option<crate::experimental::bus::Bus>,
118}
119
120// We use this pattern to ensure `self` is not used after `ManuallyDrop::take()`-ing its fields.
121impl Drop for Ditto {
122    fn drop(&mut self) {
123        if self.is_shut_down_able {
124            // stop all transports
125            self.stop_sync();
126            // Here, ditto is implicitly dropped using ditto_free if there is no strong reference to
127            // it anymore. We need to make sure that `ditto_shutdown` is called before
128            // `ditto_free` gets called though, as this will perform all the necessary
129            // pre-drop actions such as stopping TCP servers, etc.
130            ffi_sdk::ditto_shutdown(&self.ditto);
131        }
132    }
133}
134
135// Public interface for modifying Transport configuration.
136impl Ditto {
137    /// Clean shutdown of the Ditto instance
138    pub fn close(self) {
139        // take ownership of Ditto in order to drop it
140    }
141
142    /// Starts the network transports. Ditto will connect to other devices.
143    ///
144    /// By default, Ditto will enable all peer-to-peer transport types.
145    /// The network configuration can be customized using [`set_transport_config`].
146    ///
147    /// Performance of initial sync when bootstrapping a new peer can be improved
148    /// by calling [`disable_sync_with_v3`] before calling `start_sync`. Only
149    /// do this when all peers in the mesh are known to be running Ditto v4 or higher.
150    ///
151    /// [`set_transport_config`]: Self::set_transport_config
152    /// [`disable_sync_with_v3`]: Self::disable_sync_with_v3
153    pub fn start_sync(&self) -> Result<(), DittoError> {
154        // the License must be active or else sync will immediately fail
155        if self.activated.load().not() && self.identity.requires_offline_only_license_token() {
156            return Err(ErrorKind::NotActivated.into());
157        }
158
159        // We need a write lock because this will internally modify the transports by activating
160        // those configured but not currently active
161        match self.transports.write() {
162            Ok(mut transports) => {
163                transports.start_sync();
164                Ok(())
165            }
166            Err(e) => {
167                let rust_error = format!("{:?}", e); // needed as PoisonedLockError is not Send
168                Err(DittoError::new(ErrorKind::Internal, rust_error))
169            }
170        }
171    }
172
173    /// Stop syncing on all transports.
174    ///
175    /// You may continue to use the Ditto store locally but no data
176    /// will sync to or from other devices.
177    pub fn stop_sync(&self) {
178        if let Ok(mut transports) = self.transports.write() {
179            transports.stop_sync()
180        }
181    }
182
183    /// Set a new [`TransportConfig`] and begin syncing over these
184    /// transports. Any change to start or stop a specific transport should proceed via providing a
185    /// modified configuration to this method.
186    pub fn set_transport_config(&self, config: TransportConfig) {
187        if let Ok(mut transports) = self.transports.write() {
188            transports.set_transport_config(config);
189        }
190    }
191
192    /// Convenience method to update the current transport config of the receiver.
193    ///
194    /// Invokes the block with a copy of the current transport config which
195    /// you can alter to your liking. The updated transport config is then set
196    /// on the receiver.
197    ///
198    /// You may use this method to alter the configuration at any time.
199    /// Sync will not begin until [`ditto.start_sync()`] is invoked.
200    ///
201    /// # Example
202    ///
203    /// Edit the config by simply mutating the `&mut TransportConfig` passed
204    /// to your callback:
205    ///
206    /// ```
207    /// use dittolive_ditto::prelude::*;
208    /// # fn example(ditto: &Ditto) -> anyhow::Result<()> {
209    ///
210    /// // Enable the TCP listener on port 4000
211    /// ditto.update_transport_config(|config| {
212    ///     config.listen.tcp.enabled = true;
213    ///     config.listen.tcp.interface_ip = "0.0.0.0".to_string();
214    ///     config.listen.tcp.port = 4000;
215    /// });
216    /// # Ok(())
217    /// # }
218    /// ```
219    ///
220    /// [`ditto.start_sync()`]: crate::prelude::Ditto::start_sync
221    pub fn update_transport_config(&self, update: impl FnOnce(&mut TransportConfig)) {
222        let mut transport_config = self.transport_config();
223        update(&mut transport_config);
224        self.set_transport_config(transport_config);
225    }
226
227    /// Returns a snapshot of the currently configured transports.
228    #[doc(hidden)]
229    #[deprecated(note = "Use `.transport_config()` instead")]
230    pub fn current_transport_config(&self) -> Result<TransportConfig, DittoError> {
231        match self.transports.read() {
232            Ok(t) => Ok(t.current_config().clone()),
233            Err(e) => {
234                let msg = format!("transport config cannot be read {:?}", e);
235                Err(DittoError::new(ErrorKind::Internal, msg))
236            }
237        }
238    }
239
240    /// Returns a snapshot of the currently configured transports.
241    ///
242    /// # Example
243    ///
244    /// ```
245    /// # use dittolive_ditto::prelude::*;
246    /// # fn example(ditto: &Ditto) {
247    /// let transport_config = ditto.transport_config();
248    /// println!("Current transport config: {transport_config:#?}");
249    /// # }
250    /// ```
251    pub fn transport_config(&self) -> TransportConfig {
252        let transports = self.transports.read().expect("should read TransportConfig");
253        transports.current_config().clone()
254    }
255
256    /// Utils function for tests.
257    #[cfg(test)]
258    pub fn effective_transport_config(&self) -> Result<TransportConfig, DittoError> {
259        match self.transports.read() {
260            Ok(t) => Ok(t.effective_config().clone()),
261            Err(e) => {
262                let msg = format!("transport config cannot be read {:?}", e);
263                Err(DittoError::new(ErrorKind::Internal, msg))
264            }
265        }
266    }
267}
268
269impl Ditto {
270    /// Return the version of the SDK.
271    pub fn with_sdk_version<R>(ret: impl FnOnce(&'_ str) -> R) -> R {
272        ret(ffi_sdk::ditto_get_sdk_version().to_str())
273    }
274}
275
276#[doc(hidden)]
277impl Ditto {
278    #[deprecated(note = "Use `DittoLogger::set_logging_enabled()` instead")]
279    /// Enable or disable logging.
280    pub fn set_logging_enabled(enabled: bool) {
281        ffi_sdk::ditto_logger_enabled(enabled)
282    }
283
284    #[deprecated(note = "Use `DittoLogger::get_logging_enabled()` instead")]
285    /// Return true if logging is enabled.
286    pub fn get_logging_enabled() -> bool {
287        ffi_sdk::ditto_logger_enabled_get()
288    }
289
290    #[deprecated(note = "Use `DittoLogger::get_emoji_log_level_headings_enabled()` instead")]
291    /// Represent whether or not emojis should be used as the log level indicator in the logs.
292    pub fn get_emoji_log_level_headings_enabled() -> bool {
293        ffi_sdk::ditto_logger_emoji_headings_enabled_get()
294    }
295
296    #[deprecated(note = "Use `DittoLogger::set_emoji_log_level_headings_enabled()` instead")]
297    /// Set whether or not emojis should be used as the log level indicator in the logs.
298    pub fn set_emoji_log_level_headings_enabled(enabled: bool) {
299        ffi_sdk::ditto_logger_emoji_headings_enabled(enabled);
300    }
301
302    #[deprecated(note = "Use `DittoLogger::get_minimum_log_level()` instead")]
303    /// Get the current minimum log level.
304    pub fn get_minimum_log_level() -> LogLevel {
305        ffi_sdk::ditto_logger_minimum_log_level_get()
306    }
307
308    #[deprecated(note = "Use `DittoLogger::set_minimum_log_level()` instead")]
309    /// Set the current minimum log level.
310    pub fn set_minimum_log_level(log_level: LogLevel) {
311        ffi_sdk::ditto_logger_minimum_log_level(log_level);
312    }
313}
314
315impl Ditto {
316    /// Activate an offline [`Ditto`] instance by setting a license token.
317    ///
318    /// You cannot initiate sync on an offline
319    /// ([`OfflinePlayground`], [`Manual`], or [`SharedKey`])
320    /// [`Ditto`] instance before you have activated it.
321    pub fn set_offline_only_license_token(&self, license_token: &str) -> Result<(), DittoError> {
322        if self.identity.requires_offline_only_license_token() {
323            use ffi_sdk::LicenseVerificationResult;
324            use safer_ffi::prelude::{AsOut, ManuallyDropMut};
325            let c_license: char_p::Box = char_p::new(license_token);
326
327            let mut err_msg = None;
328            let out_err_msg = err_msg.manually_drop_mut().as_out();
329            let res = ffi_sdk::ditto_verify_license(c_license.as_ref(), Some(out_err_msg));
330
331            if res == LicenseVerificationResult::LicenseOk {
332                self.activated.store(true);
333                return Ok(());
334            }
335
336            self.activated.store(false);
337            let err_msg = err_msg.unwrap();
338
339            error!("{}", err_msg);
340
341            match res {
342                LicenseVerificationResult::LicenseExpired => {
343                    Err(DittoError::license(LicenseTokenError::Expired {
344                        message: err_msg.as_ref().to_string(),
345                    }))
346                }
347                LicenseVerificationResult::VerificationFailed => {
348                    Err(DittoError::license(LicenseTokenError::VerificationFailed {
349                        message: err_msg.as_ref().to_string(),
350                    }))
351                }
352                LicenseVerificationResult::UnsupportedFutureVersion => Err(DittoError::license(
353                    LicenseTokenError::UnsupportedFutureVersion {
354                        message: err_msg.as_ref().to_string(),
355                    },
356                )),
357                _ => panic!("Unexpected license verification result {:?}", res),
358            }
359        } else {
360            Err(DittoError::new(
361                ErrorKind::Internal,
362                "Offline license tokens should only be used for Manual, SharedKey or \
363                 OfflinePlayground identities",
364            ))
365        }
366    }
367
368    /// Look for a license token from a given environment variable.
369    pub fn set_license_from_env(&self, var_name: &str) -> Result<(), DittoError> {
370        match env::var(var_name) {
371            Ok(token) => self.set_offline_only_license_token(&token),
372            Err(env::VarError::NotPresent) => {
373                let msg = format!("No license token found for env var {}", &var_name);
374                Err(DittoError::from_str(ErrorKind::Config, msg))
375            }
376            Err(e) => Err(DittoError::new(ErrorKind::Config, e)),
377        }
378    }
379}
380
381impl Ditto {
382    /// Returns a reference to the underlying local data store.
383    pub fn store(&self) -> &Store {
384        &self.store
385    }
386
387    /// Entrypoint to Ditto's [`Sync`] API for syncing documents between peers.
388    ///
389    /// [`Sync`]: crate::sync::Sync
390    pub fn sync(&self) -> &crate::sync::Sync {
391        &self.sync
392    }
393
394    #[cfg(feature = "experimental-bus")]
395    /// Returns a handle to the bus controller. This can be used to send either individual messages
396    /// or open streams to remote peers.
397    /// NOTE: This API is experimental and may change in future releases.
398    pub fn bus(&self) -> Option<&crate::experimental::bus::Bus> {
399        self.bus.as_ref()
400    }
401
402    /// Return a reference to the [`SmallPeerInfo`] object.
403    pub fn small_peer_info(&self) -> &SmallPeerInfo {
404        &self.small_peer_info
405    }
406
407    /// Returns the site ID that the instance of Ditto is using as part of its identity.
408    pub fn site_id(&self) -> u64 {
409        self.site_id
410    }
411
412    /// Returns the Ditto persistence directory path.
413    pub fn persistence_directory(&self) -> &Path {
414        self.ditto_root.root_path()
415    }
416
417    /// Returns the application Id being used by this [`Ditto`] instance.
418    #[doc(hidden)]
419    #[deprecated(note = "Use `.app_id()` instead")]
420    pub fn application_id(&self) -> AppId {
421        AppId(ffi_sdk::ditto_auth_client_get_app_id(&self.ditto).into_string())
422    }
423
424    /// Returns the [`AppId`] being used by this [`Ditto`] instance.
425    pub fn app_id(&self) -> AppId {
426        AppId(ffi_sdk::ditto_auth_client_get_app_id(&self.ditto).into_string())
427    }
428
429    /// Set a custom identifier for the current device.
430    ///
431    /// When using [`observe_peers`](Ditto::observe_peers), each remote peer is represented by a
432    /// short UTF-8 “device name”. By default this will be a truncated version of the device’s
433    /// hostname. It does not need to be unique among peers. Configure the device name before
434    /// calling [`start_sync`](Ditto::start_sync). If it is too long it will be truncated.
435    pub fn set_device_name(&self, name: &str) {
436        let c_device_name: char_p::Box = char_p::new(name.to_owned());
437        ffi_sdk::ditto_set_device_name(&self.ditto, c_device_name.as_ref());
438    }
439
440    /// Request information about Ditto peers in range of this device.
441    ///
442    /// This method returns an observer which should be held as long as updates are required. A
443    /// newly registered observer will have a peers update delivered to it immediately. Then it will
444    /// be invoked repeatedly when Ditto devices come and go, or the active connections to them
445    /// change.
446    #[doc(hidden)]
447    #[deprecated(note = "Use `presence().observe()` instead")]
448    #[allow(deprecated)]
449    pub fn observe_peers<H>(&self, handler: H) -> PeersObserver
450    where
451        H: Fn(crate::transport::v2::V2Presence) + Send + Sync + 'static,
452    {
453        self.presence.add_observer(handler)
454    }
455
456    /// Return a handle to the [`Presence`] API to monitor peers' activity in the Ditto mesh.
457    ///
458    /// # Example
459    ///
460    /// Use [`ditto.presence().graph()`] to request a current [`PresenceGraph`] of connected peers:
461    ///
462    /// ```
463    /// use dittolive_ditto::prelude::*;
464    ///
465    /// # fn example(ditto: &Ditto) {
466    /// let presence_graph: PresenceGraph = ditto.presence().graph();
467    /// println!("Ditto mesh right now: {presence_graph:#?}");
468    /// # }
469    /// ```
470    ///
471    /// # Example
472    ///
473    /// Use [`ditto.presence().observe(...)`] to subscribe to changes in mesh presence:
474    ///
475    /// ```
476    /// use dittolive_ditto::prelude::*;
477    ///
478    /// # fn example(ditto: &Ditto) {
479    /// let _observer = ditto.presence().observe(|graph| {
480    ///     println!("Ditto mesh update! {graph:#?}");
481    /// });
482    ///
483    /// // The observer is cancelled when dropped.
484    /// // In a real application, hold onto it for as long as you need it alive.
485    /// drop(_observer);
486    /// # }
487    /// ```
488    ///
489    /// [`ditto.presence().graph()`]: crate::presence::Presence::graph
490    /// [`PresenceGraph`]: crate::presence::PresenceGraph
491    /// [`ditto.presence().observe(...)`]: crate::presence::Presence::observe
492    pub fn presence(&self) -> &Arc<Presence> {
493        &self.presence
494    }
495
496    /// Return a [`DiskUsage`] to monitor the disk usage of the Ditto
497    /// instance. It can be used to retrieve an immediate representation of the Ditto file system:
498    ///
499    /// ```
500    /// # use dittolive_ditto::prelude::Ditto;
501    /// # fn call_diskusage(ditto: Ditto) {
502    /// let fs_tree = ditto.disk_usage().exec();
503    /// # }
504    /// ```
505    /// Or to bind a callback to the changes :
506    /// ```
507    /// # use dittolive_ditto::prelude::Ditto;
508    /// # fn call_diskusage(ditto:Ditto) {
509    /// let handle = ditto.disk_usage().observe(|fs_tree| {
510    ///     // do something with the graph
511    /// });
512    /// // The handle must be kept to keep receiving updates on the file system.
513    /// // To stop receiving update, drop the handle.
514    /// # }
515    /// ```
516    pub fn disk_usage(&self) -> &DiskUsage {
517        &self.disk_usage
518    }
519
520    #[doc(hidden)]
521    #[deprecated(note = "Use persistence_directory instead")]
522    /// Returns the [`DittoRoot`] path.
523    pub fn root_dir(&self) -> &Path {
524        self.persistence_directory()
525    }
526
527    #[doc(hidden)]
528    #[deprecated(note = "Use persistence_directory instead")]
529    /// Returns the ditto persistence data directory path.
530    pub fn data_dir(&self) -> &Path {
531        self.persistence_directory()
532    }
533
534    #[cfg(test)]
535    /// Returns the [`DittoRoot`].
536    pub fn root(&self) -> Arc<dyn DittoRoot> {
537        self.ditto_root.retain()
538    }
539
540    /// Returns the current [`DittoAuthenticator`], if it exists.
541    #[doc(hidden)]
542    #[deprecated(note = "Use `.auth()` instead")]
543    pub fn authenticator(&self) -> Option<DittoAuthenticator> {
544        self.auth.clone()
545    }
546
547    /// Returns the current [`DittoAuthenticator`], if it exists.
548    ///
549    /// The [`DittoAuthenticator`] is available when using the [`OnlinePlayground`]
550    /// and [`OnlineWithAuthentication`] identities.
551    pub fn auth(&self) -> Option<DittoAuthenticator> {
552        self.auth.clone()
553    }
554
555    /// Returns `true` if this `Ditto` instance has been activated with a valid
556    /// license token.
557    pub fn is_activated(&self) -> bool {
558        self.activated.load()
559    }
560}
561
562// Constructors
563impl Ditto {
564    /// Entrypoint to constructing `Ditto` with the builder pattern.
565    ///
566    /// # Example
567    ///
568    /// ```no_run
569    /// # use std::sync::Arc;
570    /// use dittolive_ditto::prelude::*;
571    ///
572    /// fn main() -> anyhow::Result<()> {
573    ///     let app_id = AppId::from_env("DITTO_APP_ID")?;
574    ///     let playground_token = std::env::var("DITTO_PLAYGROUND_TOKEN")?;
575    ///     let cloud_sync = true;
576    ///     let custom_auth_url = None;
577    ///
578    ///     // Initialize Ditto
579    ///     let ditto = Ditto::builder()
580    ///         .with_root(Arc::new(PersistentRoot::from_current_exe()?))
581    ///         .with_identity(|ditto_root| {
582    ///             identity::OnlinePlayground::new(
583    ///                 ditto_root,
584    ///                 app_id,
585    ///                 playground_token,
586    ///                 cloud_sync,
587    ///                 custom_auth_url,
588    ///             )
589    ///         })?
590    ///         .build()?;
591    ///
592    ///     Ok(())
593    /// }
594    /// ```
595    ///
596    /// [See the crate docs for examples of what to do next using `Ditto`][0].
597    ///
598    /// [0]: crate#playground-quickstart
599    pub fn builder() -> DittoBuilder {
600        #[allow(deprecated)]
601        DittoBuilder::new()
602    }
603
604    /// Construct a [`Ditto`] instance with sensible defaults.
605    ///
606    /// This instance will still need to have a license set (if necessary) and sync functionality
607    /// manually started.
608    pub fn new(app_id: AppId) -> Ditto {
609        Ditto::builder()
610            .with_root(Arc::new(
611                PersistentRoot::from_current_exe().expect("Invalid Ditto Root"),
612            ))
613            .with_identity(|ditto_root| identity::OfflinePlayground::new(ditto_root, app_id))
614            .expect("Invalid Ditto Identity")
615            .with_minimum_log_level(LogLevel::Info)
616            .build()
617            .expect("Failed to build Ditto Instance")
618    }
619
620    // This isn't public to customers and is only used internally. It's only currently used when we
621    // have access to the `DittoFields` and want to create a `Ditto` instance for whatever reason,
622    // but we don't want `Ditto` to be shut down when this `Ditto` instance is dropped.
623    //
624    // This should definitely be considered a hack for now.
625    pub(crate) fn new_temp(fields: Arc<DittoFields>) -> Ditto {
626        Ditto {
627            fields,
628            is_shut_down_able: false,
629        }
630    }
631}
632
633impl Ditto {
634    /// Removes all sync metadata for any remote peers which aren't currently connected. This method
635    /// shouldn't usually be called. Manually running garbage collection often will result in slower
636    /// sync times. Ditto automatically runs a garbage a collection process in the background at
637    /// optimal times.
638    ///
639    /// Manually running garbage collection is typically only useful during testing if large amounts
640    /// of data are being generated. Alternatively, if an entire data set is to be evicted and it's
641    /// clear that maintaining this metadata isn't necessary, then garbage collection could be run
642    /// after evicting the old data.
643    pub fn run_garbage_collection(&self) {
644        ffi_sdk::ditto_run_garbage_collection(&self.ditto);
645    }
646
647    /// Disable sync with peers running version 3 or lower of the Ditto SDK.
648    ///
649    /// Required for the execution of mutating DQL statements.
650    ///
651    /// This setting spreads to other peers on connection. Those peers will in
652    /// turn spread it further until all peers in the mesh take on the same
653    /// setting. This is irreversible and will persist across restarts of the
654    /// Ditto instance.
655    ///
656    /// Calling this method before calling [`start_sync`] is recommended whenever
657    /// possible. This improves performance of initial sync when this peer has
658    /// never before connected to a Ditto mesh for which sync with v3 peers is
659    /// disabled.
660    ///
661    /// [`start_sync`]: Self::start_sync
662    pub fn disable_sync_with_v3(&self) -> Result<(), DittoError> {
663        let res = ffi_sdk::ditto_disable_sync_with_v3(&self.ditto);
664        if res != 0 {
665            return Err(DittoError::from_ffi(ErrorKind::Internal));
666        }
667        Ok(())
668    }
669}
670
671#[doc(hidden)] // Not implemented
672pub struct TransportDiagnostics;
673
674/// `SiteId`s are used to identity Ditto peers
675// NOTE(nimo): Ensure that after hiding this module, SiteId is still reachable
676/// ```rust,no_run
677/// // Ensure SiteId is still reachable
678/// use dittolive_ditto::ditto::SiteId;
679/// // Test that deprecation note is useful/accurate
680/// use dittolive_ditto::prelude::*;
681/// # fn example(ditto: &Ditto) {
682/// let peer_key = ditto
683///     .presence()
684///     .graph()
685///     .local_peer
686///     .peer_key_string
687///     .to_string();
688/// # }
689/// ```
690#[deprecated(note = "Use `ditto.presence().graph().local_peer.peer_key_string` instead")]
691pub type SiteId = u64;
692
693#[derive(Clone, Debug)]
694// pub struct AppId(uuid::Uuid); // Demo apps still use arbitrary strings
695/// The ID of this Ditto App, used to determine which peers to sync with
696pub struct AppId(pub(crate) String); // neither String nor Vec<u8> are Copy
697
698impl AppId {
699    /// Generate a random AppId from a UUIDv4
700    pub fn generate() -> Self {
701        let uuid = uuid::Uuid::new_v4();
702        AppId::from_uuid(uuid)
703    }
704
705    /// Generate an AppId from a given UUIDv4
706    pub fn from_uuid(uuid: Uuid) -> Self {
707        let id_str = format!("{:x}", &uuid); // lower-case with hypens
708        AppId(id_str)
709    }
710
711    /// Attempt to grab a specific AppId from some environment variable
712    pub fn from_env(var: &str) -> Result<Self, DittoError> {
713        let id_str = env::var(var).map_err(|err| DittoError::new(ErrorKind::Config, err))?;
714        Ok(AppId(id_str))
715    }
716
717    /// Return the corresponding string
718    pub fn as_str(&self) -> &str {
719        &self.0
720    }
721
722    /// Return the corresponding c string
723    pub fn to_c_string(&self) -> char_p::Box {
724        char_p::new(self.0.as_str())
725    }
726
727    /// Return the default auth URL associated with the app Id. This is of the form
728    /// `https://{app_id}.cloud.ditto.live/` by default.
729    pub fn default_auth_url(&self) -> String {
730        format!("https://{}.cloud.ditto.live", self.0)
731    }
732
733    /// Return the default WebSocket sync URL which is of the form
734    /// `wss://{app_id}.cloud.ditto.live/` by default.
735    pub fn default_sync_url(&self) -> String {
736        format!("wss://{}.cloud.ditto.live", self.0)
737    }
738}
739
740use std::{fmt, fmt::Display, str::FromStr};
741
742impl Display for AppId {
743    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
744        write!(f, "{}", self.0)
745    }
746}
747
748impl FromStr for AppId {
749    type Err = DittoError;
750    fn from_str(s: &str) -> Result<AppId, DittoError> {
751        // later s will need to be a valid UUIDv4
752        Ok(AppId(s.to_string()))
753    }
754}
755
756#[cfg(test)]
757#[path = "tests.rs"]
758mod tests;