1#[path = "file.rs"]
4mod file;
5
6#[path = "storage_credentials.rs"]
7mod credentials;
8
9#[path = "handle_sync.rs"]
10mod sync_control;
11
12#[path = "handle_sync_status.rs"]
13mod sync_status;
14
15#[path = "member_keys.rs"]
16mod member_keys;
17
18#[path = "handle_delete.rs"]
19mod deletion;
20pub use deletion::IfUnsent;
21
22#[path = "handle_folders.rs"]
23mod folders;
24
25#[path = "handle_file_cleanup.rs"]
26mod file_cleanup;
27
28#[path = "handle_discarded_devices.rs"]
29mod discarded_devices;
30
31#[path = "handle_local_bytes.rs"]
32mod local_bytes;
33
34#[path = "handle_device_only.rs"]
35mod device_only;
36
37#[path = "handle_transfers.rs"]
38mod transfers;
39
40#[path = "handle_uploads.rs"]
41mod uploads;
42
43#[path = "handle_writes.rs"]
44mod writes;
45
46#[path = "circles.rs"]
47mod circles;
48pub use circles::Circles;
49
50use crate::*;
51use coven_crypto::custody::{StoreCustody, StoreKeychain};
52use coven_database::Database;
53use coven_foundation::locks::lock_unpoisoned;
54use std::sync::{Arc, Mutex};
55
56#[derive(Clone)]
61pub struct CovenHandle {
62 _scope: Arc<HandleScope>,
65 database: Database,
66 files: coven_sync::Files,
67 steps: Arc<coven_sync::OperationSteps>,
69 waits: Arc<coven_sync::RetryWaits>,
70 state: Arc<coven_sync::SequenceState>,
71 codes: coven_sync::RestoreCodes,
73 network: Arc<coven_sync::NetworkFollow>,
75 storage: Arc<crate::storage::StorageConnections>,
76 custody: Arc<Mutex<Option<StoreCustody>>>,
77 directory: StoreDir,
78 keychain: Arc<StoreKeychain>,
79 device: DeviceId,
80}
81
82struct HandleScope(Database);
86
87impl Drop for HandleScope {
88 fn drop(&mut self) {
89 self.0.begin_close();
90 }
91}
92
93impl std::fmt::Debug for CovenHandle {
94 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95 f.debug_struct("CovenHandle").finish_non_exhaustive()
96 }
97}
98
99impl CovenHandle {
100 pub(crate) fn new(
101 database: Database,
102 custody: StoreCustody,
103 files: coven_sync::Files,
104 steps: Arc<coven_sync::OperationSteps>,
105 waits: Arc<coven_sync::RetryWaits>,
106 state: Arc<coven_sync::SequenceState>,
107 codes: coven_sync::RestoreCodes,
108 network: Arc<coven_sync::NetworkFollow>,
109 storage: Arc<crate::storage::StorageConnections>,
110 directory: StoreDir,
111 keychain: Arc<StoreKeychain>,
112 device: DeviceId,
113 ) -> Self {
114 Self {
115 _scope: Arc::new(HandleScope(database.clone())),
116 database,
117 files,
118 steps,
119 waits,
120 state,
121 codes,
122 network,
123 storage,
124 custody: Arc::new(Mutex::new(Some(custody))),
125 directory,
126 keychain,
127 device,
128 }
129 }
130
131 pub fn store_id(&self) -> StoreId {
133 self.directory.id()
134 }
135
136 pub async fn store_name(&self) -> CovenResult<String> {
138 self.database.store_name().await.map_err(CovenError::from)
139 }
140
141 pub fn subscribe_store_name(&self) -> LiveQuery<String, CovenError> {
143 self.database.subscribe_store_name()
144 }
145
146 pub async fn joined_invitee(&self) -> CovenResult<Option<String>> {
152 self.database.joined_invitee().await.map_err(CovenError::from)
153 }
154
155 pub async fn rename_store(&self, name: &str) -> Result<Option<OperationId>, SyncError> {
157 let name = name.to_owned();
158 self.write(move |write| write.rename_store(&name)).await
159 }
160
161 pub fn device_id(&self) -> DeviceId {
163 self.device
164 }
165
166 pub async fn storage_config(&self) -> Result<Option<StorageConfig>, SyncError> {
169 self.storage.storage_config().await
170 }
171
172 pub fn transfer_limits(&self) -> TransferLimits {
175 self.files.transfer_limits()
176 }
177 pub fn set_transfer_limits(&self, limits: TransferLimits) {
179 self.files.set_transfer_limits(limits);
180 }
181
182 pub async fn setup_s3_storage(
187 &self,
188 storage: StorageConfig,
189 device_name: &str,
190 access_key_id: String,
191 secret_access_key: SecretText,
192 on_progress: impl Fn(Progress<StorageSetupStep>),
193 ) -> Result<StorageSetup, StorageSetupError> {
194 let (progress, watched) = tokio::sync::watch::channel(None);
195 let setup = self.setup_s3_reporting(
196 storage,
197 device_name,
198 access_key_id,
199 secret_access_key,
200 progress,
201 );
202 coven_foundation::progress::forward(watched, setup, on_progress).await
203 }
204
205 pub(crate) async fn setup_s3_reporting(
208 &self,
209 storage: StorageConfig,
210 device_name: &str,
211 access_key_id: String,
212 secret_access_key: SecretText,
213 progress: crate::storage::SetupProgress,
214 ) -> Result<StorageSetup, StorageSetupError> {
215 self.storage
216 .setup_s3(storage, device_name, access_key_id, secret_access_key, progress)
217 .await
218 }
219 pub async fn detect_s3_settings(
225 &self,
226 storage: StorageConfig,
227 access_key_id: String,
228 secret_access_key: SecretText,
229 ) -> Result<S3Detection, StorageSetupError> {
230 self.storage.detect_s3(storage, access_key_id, secret_access_key).await
231 }
232 pub async fn authenticate(&self, access: OAuthAccess) -> Result<(), StorageSetupError> {
236 self.storage.authenticate(access).await
237 }
238 pub async fn setup_oauth_storage(
241 &self,
242 storage: StorageConfig,
243 device_name: &str,
244 on_progress: impl Fn(Progress<StorageSetupStep>),
245 ) -> Result<StorageSetup, StorageSetupError> {
246 let (progress, watched) = tokio::sync::watch::channel(None);
247 let setup = self.storage.setup_oauth(storage, device_name, progress);
248 coven_foundation::progress::forward(watched, setup, on_progress).await
249 }
250 pub async fn setup_cloudkit_storage(
255 &self,
256 container: &str,
257 device_name: &str,
258 on_progress: impl Fn(Progress<StorageSetupStep>),
259 ) -> Result<StorageSetup, StorageSetupError> {
260 let (progress, watched) = tokio::sync::watch::channel(None);
261 let setup = self.storage.setup_cloudkit(container, device_name, progress);
262 coven_foundation::progress::forward(watched, setup, on_progress).await
263 }
264 pub fn cancel_storage_setup(&self) -> crate::storage::SetupCancellation {
271 self.storage.cancel_setup()
272 }
273 #[cfg(test)]
275 pub(crate) fn test_setup_hook(&self, hook: crate::storage::SetupHook) {
276 self.storage.test_setup_hook(hook);
277 }
278 #[cfg(test)]
280 pub(crate) fn test_replace_crash(&self, at: coven_sync::ReplaceStop) {
281 self.storage.test_replace_crash(at);
282 }
283 pub async fn unlock_store_key(&self) -> Result<ConnectedStorage, StoreKeyUnlockError> {
285 self.storage.unlock().await
286 }
287 pub(crate) async fn load_joined(
290 &self,
291 progress: tokio::sync::watch::Sender<Option<Progress<coven_database::LoadStep>>>,
292 ) -> Result<(), SyncError> {
293 self.storage.load_joined(progress).await
294 }
295 pub fn store_key_state(&self) -> Result<StoreKeyState, KeyError> {
297 self.storage.key_state()
298 }
299 pub async fn device_reset(&self) -> CovenResult<Option<DeviceReset>> {
302 self.database.device_reset().await.map_err(CovenError::from)
303 }
304
305 pub fn subscribe_device_reset(&self) -> LiveQuery<Option<DeviceReset>, CovenError> {
307 self.database.subscribe_device_reset()
308 }
309
310 pub async fn pending(&self) -> CovenResult<Vec<PendingRecord>> {
313 self.database.pending().await.map_err(CovenError::from)
314 }
315
316 pub async fn members(&self) -> CovenResult<Vec<MemberInfo>> {
318 self.database.members().await.map_err(CovenError::from)
319 }
320
321 pub fn subscribe_members(&self) -> LiveQuery<Vec<MemberInfo>, CovenError> {
323 self.database.subscribe_members()
324 }
325
326 pub async fn set_member_role(
329 &self,
330 member: &MemberId,
331 role: MemberRole,
332 ) -> Result<Option<OperationId>, SyncError> {
333 let member = *member;
334 self.write(move |write| write.set_member_role(&member, role)).await
335 }
336 pub async fn remove_member(&self, member: &MemberId) -> Result<Option<OperationId>, SyncError> {
338 let member = *member;
339 self.write(move |write| write.remove_member(&member)).await
340 }
341 pub async fn remove_device(
343 &self,
344 device: DeviceId,
345 ) -> Result<(Option<OperationId>, Option<ProviderSignOut>), SyncError> {
346 self.write(move |write| write.remove_device(device)).await
347 }
348 pub async fn confirm_device_cut_off(&self, device: DeviceId) -> Result<(), SyncError> {
361 self.storage.confirm_device_cut_off(device).await
362 }
363 pub async fn holds_store(&self) -> Result<bool, SyncError> {
369 self.storage.holds_store().await
370 }
371 pub async fn operation_outcome(
377 &self,
378 operation: OperationId,
379 ) -> Result<OperationOutcome, SyncError> {
380 let mut listed: LiveQuery<_, SyncError> = self.database.subscribe(move |sql| {
381 Ok(sql
382 .query(
383 "SELECT outcome FROM coven_operations WHERE operation=?1",
384 [operation.0],
385 |row| row.get::<_, Option<OperationOutcome>>(0),
386 )?
387 .pop())
388 });
389 while let Some(listed) = listed.next().await {
390 match listed? {
391 None => return Err(DbError::OperationUnknown(operation).into()),
392 Some(Some(outcome)) => return Ok(outcome),
393 Some(None) => {}
394 }
395 }
396 Err(DbError::StoreClosed.into())
397 }
398 pub async fn guided_access_key(&self) -> Result<GuidedAccessKey, SyncError> {
401 self.storage.guided_access_key().await
402 }
403 pub async fn reload_from_snapshot(&self) -> Result<(), OperationError> {
405 Box::pin(self.storage.reload_from_snapshot()).await
409 }
410 pub async fn reset_store(&self) -> Result<(), SyncError> {
412 self.storage.reset(Audience::Store).await
413 }
414 pub async fn retry_pending_operation(
417 &self,
418 operation: OperationId,
419 ) -> Result<(), OperationError> {
420 self.write(move |write| write.retry_pending_operation(operation)).await
421 }
422 pub async fn create_invite(
424 &self,
425 target: InviteTarget,
426 access: InviteAccess,
427 lifetime: InviteLifetime,
428 inviter: String,
429 ) -> Result<(InviteId, MemberId, OperationId), SyncError> {
430 self.write(move |write| write.create_invite(target, access, lifetime, inviter)).await
431 }
432 #[cfg(any(test, feature = "test-utils"))]
434 pub fn test_invites(&self) -> coven_database::test_utils::Followed<Vec<Invite>, DbError> {
435 Invite::follow(&self.database)
436 }
437 pub async fn approve_join_request(
440 &self,
441 request: &JoinRequest,
442 ) -> Result<OperationId, SyncError> {
443 let request = request.clone();
444 self.write(move |write| write.approve_join_request(&request)).await
445 }
446 pub async fn decline_join_request(&self, request: &JoinRequest) -> Result<(), SyncError> {
449 let request = request.clone();
450 self.write(move |write| write.decline_join_request(&request)).await
451 }
452 pub async fn cancel_invite(&self, invite: &InviteId) -> Result<(), SyncError> {
454 let invite = *invite;
455 self.write(move |write| write.cancel_invite(&invite)).await
456 }
457 pub fn circles(&self) -> Circles<'_> {
459 Circles::new(self)
460 }
461
462 pub fn read<F, R, E>(&self, read: F) -> Read<'_, F>
467 where
468 F: FnOnce(SqlReadContext<'_>) -> Result<R, E> + Send + 'static,
469 R: Send + 'static,
470 E: From<DbError> + task::CarriesBug + Send + 'static,
471 {
472 self.database.read(read)
473 }
474
475 pub fn subscribe<F, R, E>(&self, query: F) -> LiveQuery<R, E>
478 where
479 F: Fn(SqlReadContext<'_>) -> Result<R, E> + Send + Sync + 'static,
480 R: Send + 'static,
481 E: From<DbError> + task::CarriesBug + Send + 'static,
482 {
483 self.database.subscribe(query)
484 }
485
486 pub fn subscribe_reconfigurable<Q, F, R, E>(
489 &self,
490 initial_request: Q,
491 query: F,
492 ) -> ReconfigurableLiveQuery<Q, R, E>
493 where
494 Q: Clone + PartialEq + Send + Sync + 'static,
495 F: Fn(&Q, SqlReadContext<'_>) -> Result<R, E> + Send + Sync + 'static,
496 R: Send + 'static,
497 E: From<DbError> + task::CarriesBug + Send + 'static,
498 {
499 self.database.subscribe_reconfigurable(initial_request, query)
500 }
501
502 pub async fn sqlite_settings(&self) -> Result<SqliteSettingsInEffect, DbError> {
504 self.database.sqlite_settings().await
505 }
506
507 pub async fn checkpoint(&self, mode: CheckpointMode) -> Result<WalCheckpoint, DbError> {
512 self.database.checkpoint(mode).await
513 }
514
515 pub async fn incremental_vacuum(&self, pages: IncrementalVacuum) -> Result<(), DbError> {
518 self.database.incremental_vacuum(pages).await
519 }
520
521 pub async fn release_memory(&self) -> Result<(), DbError> {
528 self.database.release_memory().await
529 }
530
531 pub async fn lost_values(&self) -> CovenResult<Vec<LostValue>> {
533 self.database.lost_values().await.map_err(CovenError::from)
534 }
535
536 pub async fn dismiss_lost_values(&self, values: &[LostValue]) -> CovenResult<()> {
539 let values = values.to_vec();
540 self.write(move |write| Ok(write.dismiss_lost_values(&values)?)).await
541 }
542
543 pub fn subscribe_lost_values(&self) -> LiveQuery<Vec<LostValue>, CovenError> {
545 self.database.subscribe_lost_values()
546 }
547
548 pub async fn file_ref(&self, file: FileId) -> Result<FileRef, DbError> {
553 self.database.file_ref(file).await
554 }
555
556 pub async fn read_file(&self, file: &FileRef) -> Result<Vec<u8>, FileReadError> {
558 let stream = self.open_file_stream(file).await?;
559 stream.read_at(0, stream.plaintext_size()).await
560 }
561
562 pub async fn open_file_stream(&self, file: &FileRef) -> Result<FileStream, FileReadError> {
567 self.files.open_file_stream(file).await
568 }
569
570 pub async fn user_file(&self, file: FileId) -> Result<Option<UserFile>, DbError> {
573 self.database.user_file(file).await
574 }
575
576 pub async fn forget_store_keys(&self) -> Result<(), KeyError> {
579 self.storage.forget_store_keys().await
580 }
581
582 pub fn set_host_secret(&self, name: &str, value: &str) -> Result<(), KeyError> {
589 lock_unpoisoned(&self.custody)
590 .as_ref()
591 .ok_or(KeyError::StoreClosed)?
592 .set_host_secret(name, value)
593 }
594 pub fn host_secret(&self, name: &str) -> Result<Option<String>, KeyError> {
596 lock_unpoisoned(&self.custody).as_ref().ok_or(KeyError::StoreClosed)?.host_secret(name)
597 }
598 pub fn delete_host_secret(&self, name: &str) -> Result<(), KeyError> {
601 lock_unpoisoned(&self.custody)
602 .as_ref()
603 .ok_or(KeyError::StoreClosed)?
604 .delete_host_secret(name)
605 }
606
607 pub async fn close(&self) -> Result<(), SyncError> {
613 self.database.begin_close();
614 let handle = self.clone();
615 let bugs = self.database.bugs().clone();
616 let closing = async move {
617 handle.network.close().await;
619 let storage = handle.storage.close().await;
620 handle.files.close().await;
621 let custody = handle.custody.clone();
622 let bugs = handle.database.bugs().clone();
623 let custody = crate::task::blocking_completing("close custody", bugs, move || {
624 if let Some(custody) = lock_unpoisoned(&custody).take() {
625 custody.close();
626 }
627 Ok::<_, std::convert::Infallible>(())
628 })
629 .await
630 .map(|closed| closed.unwrap_or_else(|never| match never {}));
631 let database = handle.database.close().await;
632 let storage = match storage {
635 Err(SyncError::Database(DbError::StoreClosed)) => Ok(()),
636 result => result,
637 };
638 [storage, custody.map_err(SyncError::from), database.map_err(SyncError::from)]
639 .into_iter()
640 .filter_map(Result::err)
641 .reduce(SyncError::cleanup)
642 .map_or(Ok(()), Err)
643 };
644 crate::task::spawn_completing("close the store", bugs, closing).await?
645 }
646 #[cfg(test)]
648 pub(crate) async fn applied_slot(&self) -> u64 {
649 self.database.applied_slot().await.unwrap()
650 }
651 #[cfg(test)]
653 pub(crate) async fn store_state(&self) -> coven_database::StoreState {
654 self.database.store_state().await.unwrap()
655 }
656 #[cfg(test)]
663 pub(crate) fn test_call_waits(&self) -> impl std::future::Future<Output = ()> {
664 self.storage.call_waits_from_now()
665 }
666 #[cfg(test)]
669 pub(crate) fn test_downloads_waiting(
670 &self,
671 count: usize,
672 ) -> impl std::future::Future<Output = ()> {
673 self.storage.downloads_waiting_from_now(count)
674 }
675 #[cfg(test)]
677 pub(crate) fn test_follow_retry_wait(
678 &self,
679 subject: coven_database::RetrySubject,
680 ) -> coven_database::test_utils::Followed<Option<coven_database::RetryWait>, DbError> {
681 coven_database::test_utils::Followed::new(self.database.subscribe_retry_wait(subject))
682 }
683 #[cfg(test)]
685 pub(crate) fn test_store_state(
686 &self,
687 ) -> coven_database::test_utils::Followed<coven_database::StoreState, DbError> {
688 coven_database::test_utils::Followed::new(self.database.subscribe_store_state())
689 }
690 #[cfg(test)]
692 pub(crate) fn test_pause_asked(&self) -> impl std::future::Future<Output = ()> {
693 self.storage.pause_asked_from_now()
694 }
695 #[cfg(test)]
696 pub(crate) async fn test_parked(&self) {
697 self.network.parked_from_now().await;
698 self.storage.parked().await;
699 }
700 #[cfg(test)]
701 pub(crate) async fn test_connection_retry_wait(&self) -> Option<coven_database::RetryWait> {
702 self.database.retry_wait(coven_database::RetrySubject::Sync).await.unwrap()
703 }
704 #[cfg(test)]
707 pub(crate) async fn test_record_pending(&self, record: coven_sync::PendingRecord) {
708 self.database.record_pending(record).await.unwrap();
709 }
710 #[cfg(test)]
712 pub(crate) async fn test_halt(&self, bug: crate::task::Bug) {
713 self.waits.sync_halted(bug).await.unwrap();
714 }
715
716 #[cfg(test)]
719 pub(crate) async fn test_panic_in_next_write(&self, message: &'static str) {
720 self.database.test_panic_in_next_write(message).await.unwrap();
721 }
722
723 #[cfg(any(test, feature = "test-utils"))]
727 pub async fn test_track_work(&self, counter: crate::WorkCounter) -> Result<(), DbError> {
728 self.database.test_track_work(counter).await
729 }
730
731 #[cfg(any(test, feature = "test-utils"))]
735 pub async fn test_snapshot_section_bytes(
736 &self,
737 ) -> Result<
738 [u64; coven_format::snapshot::SECTIONS],
739 coven_database::SnapshotWriteError<std::convert::Infallible>,
740 > {
741 let id = coven_format::admin::SnapshotId {
742 audience: coven_merge::Audience::Store,
743 device: self.device_id(),
744 number: 1,
745 };
746 self.database.test_snapshot_section_bytes(id).await
747 }
748
749 #[cfg(any(test, feature = "test-utils"))]
751 pub async fn test_queued_writes(
752 &self,
753 ) -> Result<Vec<coven_format::write::WriteRecord>, DbError> {
754 self.database.test_queued_writes().await
755 }
756}
757
758#[cfg(test)]
759impl CovenHandle {
760 pub(crate) async fn test_upload_location(&self, file: FileId) -> coven_database::FileLocation {
763 self.database
764 .upload_queue()
765 .await
766 .unwrap()
767 .uploads
768 .into_iter()
769 .find(|upload| upload.files.contains(&file))
770 .expect("an upload stores the file")
771 .location
772 }
773}
774
775#[cfg(test)]
776#[path = "handle_tests.rs"]
777mod tests;