dittolive_ditto/sync/
mod.rs

1//! Use [`ditto.sync()`] to access the [`Sync`] API of Ditto.
2//!
3//! The [`Sync`] API can be used to request documents to be synchronized with
4//! other peers. Use [`ditto.sync().register_subscription(...)`] and provide
5//! a DQL query to let Ditto know which documents you're interested in syncing.
6//!
7//! The returned [`SyncSubscription`] handle can be used to cancel the
8//! subscription with [`.cancel()`], at which point Ditto will stop syncing
9//! data for that subscription.
10//!
11//! # Example
12//!
13//! ```
14//! use dittolive_ditto::prelude::*;
15//! # fn main() -> anyhow::Result<()> {
16//! # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
17//!
18//! let sync_subscription = ditto
19//!     .sync()
20//!     .register_subscription("SELECT * FROM cars WHERE color = 'blue'")?;
21//!
22//! // To cancel the sync subscription, use .cancel()
23//! sync_subscription.cancel();
24//! # Ok(())
25//! # }
26//! ```
27//!
28//! [`ditto.sync()`]: Ditto::sync
29//! [`ditto.sync().register_subscription(...)`]: Sync::register_subscription
30//! [`.cancel()`]: SyncSubscription::cancel
31
32use std::{
33    collections::HashSet,
34    sync::{Arc, Weak},
35};
36
37use serde::Serialize;
38
39pub use self::sync_subscription::SyncSubscription;
40
41mod sync_subscription;
42
43use crate::{
44    ditto::{Ditto, DittoFields},
45    dql::query::IntoQuery,
46    error::DittoError,
47};
48
49/// Ditto's `Sync` API, obtained via [`ditto.sync()`].
50///
51/// [See the `sync` module documentation for more details][0].
52///
53/// [`ditto.sync()`]: crate::prelude::Ditto::sync
54/// [0]: crate::sync
55pub struct Sync {
56    ditto: Weak<DittoFields>,
57}
58
59impl Sync {
60    pub(crate) fn new(ditto: Weak<DittoFields>) -> Self {
61        Self { ditto }
62    }
63
64    /// Returns a snapshot of handles to all active [`SyncSubscription`]s.
65    ///
66    /// Adding or removing [`SyncSubscription`]s from this set will not cause the
67    /// underlying subscriptions to be updated.
68    ///
69    /// # Example
70    ///
71    /// ```
72    /// # use dittolive_ditto::Ditto;
73    /// # fn main() -> anyhow::Result<()> {
74    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
75    /// let subscription = ditto.sync().register_subscription("SELECT * FROM cars")?;
76    /// let subscriptions = ditto.sync().subscriptions();
77    /// assert!(subscriptions.contains(&subscription));
78    /// # Ok(())
79    /// # }
80    /// ```
81    pub fn subscriptions(&self) -> HashSet<SyncSubscription> {
82        let ditto = Ditto::upgrade(&self.ditto).expect("Ditto went out of scope");
83        let sync_subscriptions = ffi_sdk::dittoffi_sync_subscriptions(&ditto.ditto);
84        let sync_subscriptions: Vec<_> = sync_subscriptions.into();
85
86        sync_subscriptions
87            .into_iter()
88            .map(|handle| SyncSubscription { handle })
89            .collect::<HashSet<_>>()
90    }
91
92    /// Use a DQL query to subscribe to data on other Ditto peers.
93    ///
94    /// While the subscription is active, data matching this query on other
95    /// peers will be synced to the local peer's data store.
96    ///
97    /// Note that dropping the `SyncSubscription` won't cancel it. To do that
98    /// be sure to use [`sync_subscription.cancel()`].
99    ///
100    /// # Example
101    ///
102    /// ```
103    /// use dittolive_ditto::prelude::*;
104    /// # fn main() -> anyhow::Result<()> {
105    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
106    /// let query = (
107    ///     "SELECT * FROM cars WHERE color = :color",
108    ///     serde_json::json!({"color": "blue"}),
109    /// );
110    ///
111    /// let sync_subscription = ditto.sync().register_subscription(query)?;
112    ///
113    /// // Cancel the subscription with `.cancel()`
114    /// sync_subscription.cancel();
115    /// # Ok(())
116    /// # }
117    /// ```
118    ///
119    /// [`sync_subscription.cancel()`]: crate::sync::SyncSubscription::cancel
120    pub fn register_subscription<Q>(&self, query: Q) -> Result<Arc<SyncSubscription>, DittoError>
121    where
122        Q: IntoQuery,
123        Q::Args: Serialize,
124    {
125        let ditto = Ditto::upgrade(&self.ditto)?;
126        let query = query.into_query()?;
127        let subscription =
128            SyncSubscription::new(&ditto, &query.string, query.args_cbor.as_deref())?;
129        let subscription = Arc::new(subscription);
130        Ok(subscription)
131    }
132
133    /// Start syncing on all transports.
134    ///
135    /// By default, Ditto enables the stable peer-to-peer transports supported on
136    /// the current platform. Reliable UDP multicast is excluded from this
137    /// default and must be enabled explicitly. The network configuration can be
138    /// customized using [`Ditto::set_transport_config`].
139    ///
140    /// # Example
141    ///
142    /// ```
143    /// # use dittolive_ditto::Ditto;
144    /// # use dittolive_ditto::prelude::DittoError;
145    /// # fn main() -> Result<(), DittoError> {
146    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
147    /// ditto.sync().start()?;
148    /// # Ok(())
149    /// # }
150    /// ```
151    ///
152    /// # Errors
153    ///
154    /// Returns an error if the Ditto instance has not been activated with a valid license.
155    pub fn start(&self) -> Result<(), DittoError> {
156        let ditto = Ditto::upgrade(&self.ditto)?;
157        let result = ffi_sdk::dittoffi_ditto_try_start_sync(&ditto.ditto);
158        if let Some(error) = result.error {
159            if ffi_sdk::dittoffi_error_code(&*error)
160                == ffi_sdk::FfiErrorCode::ActivationNotActivated
161            {
162                return Err(crate::error::ErrorKind::NotActivated.into());
163            }
164            return Err(DittoError::from(error));
165        }
166        Ok(())
167    }
168
169    /// Stop syncing on all transports.
170    ///
171    /// You may continue to use the Ditto store locally but no data
172    /// will sync to or from other devices.
173    ///
174    /// # Example
175    ///
176    /// ```
177    /// # use dittolive_ditto::Ditto;
178    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
179    /// ditto.sync().stop();
180    /// ```
181    pub fn stop(&self) {
182        let ditto = Ditto::upgrade(&self.ditto).expect("Ditto went out of scope");
183        ffi_sdk::dittoffi_ditto_stop_sync(&ditto.ditto);
184    }
185
186    /// Returns `true` if sync is currently active, otherwise returns `false`.
187    ///
188    /// Use [`ditto.sync().start()`] to activate and [`ditto.sync().stop()`] to deactivate sync.
189    ///
190    /// [`ditto.sync().start()`]: Sync::start
191    /// [`ditto.sync().stop()`]: Sync::stop
192    ///
193    /// # Example
194    ///
195    /// ```
196    /// # use dittolive_ditto::Ditto;
197    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
198    /// if ditto.sync().is_active() {
199    ///     println!("Sync is active");
200    /// }
201    /// ```
202    ///
203    /// [`ditto.sync().stop()`]: Sync::stop
204    pub fn is_active(&self) -> bool {
205        let ditto = Ditto::upgrade(&self.ditto).expect("Ditto went out of scope");
206        ffi_sdk::dittoffi_ditto_is_sync_active(&ditto.ditto)
207    }
208}