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}