vstorage/sync/plan/
mod.rs1mod collection;
10pub mod incremental;
11pub(crate) mod items;
12mod mapping;
13
14use std::{pin::Pin, sync::Arc, task::Context, task::Poll};
15
16use futures_util::Stream;
17use tokio::sync::mpsc;
18
19use crate::{
20 Href,
21 sync::{
22 Side,
23 declare::StoragePair,
24 mapping::ResolvedMapping,
25 operation::Operation,
26 ordering::CompletionDroppedError,
27 plan::{
28 collection::{GenerateError, generate_collection_operations},
29 mapping::create_mappings_for_pair,
30 },
31 status::{FindStaleMappingsError, MappingUid, StatusDatabase, StatusError},
32 },
33};
34
35pub use incremental::IncrementalPlan;
36
37#[derive(thiserror::Error, Debug)]
39pub enum PlanError {
40 #[error("Conflicting mappings on side {0} for href {1}.")]
46 ConflictingMappings(Side, Href),
47
48 #[error("Discovery failed for storage A: {0}")]
50 DiscoveryFailedA(#[source] crate::Error),
51
52 #[error("Discovery failed for storage B: {0}")]
54 DiscoveryFailedB(#[source] crate::Error),
55
56 #[error("Interacting with underlying storage: {0}")]
58 Storage(#[from] crate::Error),
59
60 #[error("Querying status database: {0}")]
62 StatusDb(#[from] StatusError),
63
64 #[error("Finding stale mappings: {0}")]
66 FindStaleMappings(#[from] FindStaleMappingsError),
67
68 #[error("Completion handle dropped before signaling: {0}")]
70 CompletionDropped(#[from] CompletionDroppedError),
71}
72
73pub struct Plan(mpsc::Receiver<Result<Operation, PlanError>>);
88
89impl Plan {
90 pub async fn new(
96 pair: StoragePair,
97 status: Option<Arc<StatusDatabase>>,
98 ) -> Result<Plan, PlanError> {
99 let mappings = create_mappings_for_pair(&pair).await?;
100 let rx = Plan::receiver(pair, status, mappings)?;
101 Ok(Plan(rx))
102 }
103
104 pub(self) fn receiver(
105 pair: StoragePair,
106 status: Option<Arc<StatusDatabase>>,
107 mappings: Vec<Arc<ResolvedMapping>>,
108 ) -> Result<mpsc::Receiver<Result<Operation, PlanError>>, PlanError> {
109 let mut stale_mappings = None;
110 if let Some(ref status) = status {
111 let mut active_uids = Vec::new();
112 for mapping in &mappings {
113 if let Ok(Some(uid)) =
114 status.get_mapping_uid(mapping.a().href(), mapping.b().href())
115 {
116 active_uids.push(uid);
117 }
118 }
119
120 let stale = status.find_stale_mappings(active_uids.into_iter())?;
121 if !stale.is_empty() {
122 stale_mappings = Some(stale);
123 }
124 }
125
126 let (tx, rx) = mpsc::channel(4);
127
128 tokio::spawn(run_plan_generator(
129 tx,
130 pair,
131 status,
132 mappings,
133 stale_mappings,
134 ));
135
136 Ok(rx)
137 }
138}
139
140async fn run_plan_generator(
142 tx: mpsc::Sender<Result<Operation, PlanError>>,
143 pair: StoragePair,
144 status: Option<Arc<StatusDatabase>>,
145 mappings: Vec<Arc<ResolvedMapping>>,
146 stale_mappings: Option<Vec<MappingUid>>,
147) {
148 if let Some(stale_uids) = stale_mappings {
149 let op = Operation::FlushStaleMappings { stale_uids };
150 if tx.send(Ok(op)).await.is_err() {
151 return;
152 }
153 }
154
155 for mapping in mappings {
156 match generate_collection_operations(&tx, &pair, mapping, status.as_deref()).await {
157 Ok(()) => {} Err(GenerateError::Plan(e)) => {
159 if tx.send(Err(e)).await.is_err() {
160 return;
161 }
162 }
163 Err(GenerateError::ChannelClosed) => return,
164 }
165 }
166}
167
168impl Stream for Plan {
169 type Item = Result<Operation, PlanError>;
170
171 fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
172 self.0.poll_recv(cx)
173 }
174}
175
176#[cfg(test)]
177mod test {
178 use std::{str::FromStr, sync::Arc};
179
180 use tempfile::Builder;
181
182 use crate::{
183 CollectionId, ItemKind,
184 base::Storage,
185 sync::declare::{CollectionDescription, StoragePair, SyncedCollection},
186 vdir::VdirStorage,
187 };
188
189 use super::{PlanError, create_mappings_for_pair};
190
191 #[tokio::test]
192 async fn test_plan_duplicate_mapping() {
193 let dir_a = Builder::new().prefix("vstorage").tempdir().unwrap();
194 let dir_b = Builder::new().prefix("vstorage").tempdir().unwrap();
195
196 let storage_a = Arc::new(
197 VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
198 .unwrap()
199 .build(ItemKind::Calendar),
200 );
201 let storage_b = Arc::from(
202 VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
203 .unwrap()
204 .build(ItemKind::Calendar),
205 );
206
207 let collection = CollectionId::from_str("test").unwrap();
209 let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
210 .with_mapping(SyncedCollection::direct(collection.clone()))
211 .with_mapping(SyncedCollection::direct(collection));
212
213 let err = create_mappings_for_pair(&pair).await.unwrap_err();
214 assert!(matches!(err, PlanError::ConflictingMappings(..)));
215 }
216
217 #[tokio::test]
218 async fn test_plan_conflicting_mapping() {
219 let dir_a = Builder::new().prefix("vstorage").tempdir().unwrap();
220 let dir_b = Builder::new().prefix("vstorage").tempdir().unwrap();
221
222 let storage_a = Arc::new(
223 VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
224 .unwrap()
225 .build(ItemKind::Calendar),
226 );
227 let storage_b = Arc::from(
228 VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
229 .unwrap()
230 .build(ItemKind::Calendar),
231 );
232 let collection = CollectionId::from_str("test").unwrap();
234 let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
235 .with_mapping(SyncedCollection::direct(collection.clone()))
236 .with_mapping(SyncedCollection::mapped(
237 "test".to_string(),
238 CollectionDescription::Id { id: collection },
239 CollectionDescription::Id {
240 id: CollectionId::from_str("test_2").unwrap(),
241 },
242 ));
243
244 let err = create_mappings_for_pair(&pair).await.unwrap_err();
245 assert!(matches!(err, PlanError::ConflictingMappings(..)));
246 }
247
248 #[tokio::test]
249 async fn test_plan_same_from_both_sides() {
250 let dir_a = Builder::new().prefix("vstorage").tempdir().unwrap();
251 let dir_b = Builder::new().prefix("vstorage").tempdir().unwrap();
252
253 let storage_a = Arc::new(
254 VdirStorage::builder(dir_a.path().to_path_buf().try_into().unwrap())
255 .unwrap()
256 .build(ItemKind::Calendar),
257 );
258 let storage_b = Arc::from(
259 VdirStorage::builder(dir_b.path().to_path_buf().try_into().unwrap())
260 .unwrap()
261 .build(ItemKind::Calendar),
262 );
263 std::fs::create_dir(dir_a.path().join("one")).unwrap();
266 std::fs::create_dir(dir_b.path().join("one")).unwrap();
267
268 let disco = storage_a.discover_collections().await.unwrap();
269 assert_eq!(disco.collections().len(), 1);
270
271 let pair = StoragePair::new(storage_a.clone(), storage_b.clone())
272 .with_all_from_a()
273 .with_all_from_b();
274
275 let mappings = create_mappings_for_pair(&pair).await.unwrap();
276 assert_eq!(mappings.len(), 1);
279 }
280}