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