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 will enable all peer-to-peer transport types.
136    /// The network configuration can be customized using [`Ditto::set_transport_config`].
137    ///
138    /// # Example
139    ///
140    /// ```
141    /// # use dittolive_ditto::Ditto;
142    /// # use dittolive_ditto::prelude::DittoError;
143    /// # fn main() -> Result<(), DittoError> {
144    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
145    /// ditto.sync().start()?;
146    /// # Ok(())
147    /// # }
148    /// ```
149    ///
150    /// # Errors
151    ///
152    /// Returns an error if the Ditto instance has not been activated with a valid license.
153    pub fn start(&self) -> Result<(), DittoError> {
154        let ditto = Ditto::upgrade(&self.ditto)?;
155        let result = ffi_sdk::dittoffi_ditto_try_start_sync(&ditto.ditto);
156        if let Some(error) = result.error {
157            if ffi_sdk::dittoffi_error_code(&*error)
158                == ffi_sdk::FfiErrorCode::ActivationNotActivated
159            {
160                return Err(crate::error::ErrorKind::NotActivated.into());
161            }
162            return Err(DittoError::from(error));
163        }
164        Ok(())
165    }
166
167    /// Stop syncing on all transports.
168    ///
169    /// You may continue to use the Ditto store locally but no data
170    /// will sync to or from other devices.
171    ///
172    /// # Example
173    ///
174    /// ```
175    /// # use dittolive_ditto::Ditto;
176    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
177    /// ditto.sync().stop();
178    /// ```
179    pub fn stop(&self) {
180        let ditto = Ditto::upgrade(&self.ditto).expect("Ditto went out of scope");
181        ffi_sdk::dittoffi_ditto_stop_sync(&ditto.ditto);
182    }
183
184    /// Returns `true` if sync is currently active, otherwise returns `false`.
185    ///
186    /// Use [`ditto.sync().start()`] to activate and [`ditto.sync().stop()`] to deactivate sync.
187    ///
188    /// [`ditto.sync().start()`]: Sync::start
189    /// [`ditto.sync().stop()`]: Sync::stop
190    ///
191    /// # Example
192    ///
193    /// ```
194    /// # use dittolive_ditto::Ditto;
195    /// # let (_root, ditto) = dittolive_ditto::doctest_helpers::doctest_ditto();
196    /// if ditto.sync().is_active() {
197    ///     println!("Sync is active");
198    /// }
199    /// ```
200    ///
201    /// [`ditto.sync().stop()`]: Sync::stop
202    pub fn is_active(&self) -> bool {
203        let ditto = Ditto::upgrade(&self.ditto).expect("Ditto went out of scope");
204        ffi_sdk::dittoffi_ditto_is_sync_active(&ditto.ditto)
205    }
206}