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;