1use crate::*;
4use coven_crypto::custody::{
5 Custody, CustodyChoice, InMemoryCustody, KeyMaterial, KeySession, Keychain, KeyringCustody,
6 PassphraseCustody, PersonEntries, StoreCustody, StoreKeychain,
7};
8use coven_database::{DatabaseBuilder, OpenStep};
9use coven_foundation::files::StoreDirAccess;
10use coven_foundation::files::StoreFile;
11use coven_foundation::progress::Progress;
12#[cfg(test)]
13use coven_foundation::progress::RecordingClock;
14use coven_storage::{
15 providers::{OAuthExchange, OAuthFlow, StorageConnector},
16 Storage,
17};
18use std::sync::Arc;
19
20pub struct CovenBuilder {
22 tables: Option<Vec<SyncedTable>>,
23 namespaces: Vec<coven_database::Namespace>,
24 migrations: Option<Vec<Migration>>,
25 directories: coven_foundation::files::StoreDirectories,
26 keychain: Arc<Keychain>,
27 ids: IdSourceRef,
28 clock: ClockRef,
29 connector: Option<Arc<dyn StorageConnector>>,
30 transfers: Arc<coven_storage::TransferBudget>,
31 network: coven_foundation::network_watch::NetworkSource,
33 oauth: Option<OAuthClients>,
34 presenter: Option<Arc<dyn OAuthPresenter>>,
35 authentication: Option<Arc<coven_sync::SessionSignIn>>,
38 keys: SessionSource<StoreKeyring>,
39 identity: SessionSource<MemberIdentity>,
40 sqlite: SqliteSettings,
41 sql_functions: coven_database::SqlFunctions,
42 #[cfg(test)]
44 interrupt_after_swap: bool,
45 #[cfg(test)]
48 fail_device_reset_notice: bool,
49 #[cfg(test)]
51 refuse_database_close: bool,
52 #[cfg(test)]
55 interrupt_after_registering: bool,
56 #[cfg(test)]
59 interrupt_commit: bool,
60 #[cfg(test)]
63 loading_recorder: Option<Arc<RecordingClock<coven_database::LoadStep>>>,
64}
65
66enum Continuing {
68 Fresh,
71 Loaded {
75 state: Arc<coven_sync::SequenceState>,
76 storage: Arc<coven_storage::StorageConnection>,
77 credentials: Option<Arc<coven_sync::DeviceCredentials>>,
78 },
79}
80
81enum SessionSource<T: KeyMaterial> {
84 Configured(CustodyChoice<T>),
85 Session(Arc<KeySession<T>>),
86}
87
88impl CovenBuilder {
89 pub(crate) fn new(
90 directories: coven_foundation::files::StoreDirectories,
91 keychain: Arc<Keychain>,
92 network: coven_foundation::network_watch::NetworkSource,
93 ) -> Self {
94 let ids: IdSourceRef = Arc::new(UuidIds);
95 let clock: ClockRef = Arc::new(SystemClock);
96 Self {
97 tables: None,
98 namespaces: Vec::new(),
99 migrations: None,
100 directories,
101 keychain,
102 ids,
103 clock,
104 connector: None,
105 oauth: None,
106 #[cfg(not(any(target_os = "ios", target_os = "android")))]
107 presenter: Some(Arc::new(DesktopOAuthPresenter)),
108 #[cfg(any(target_os = "ios", target_os = "android"))]
109 presenter: None,
110 authentication: None,
111 transfers: Arc::new(coven_storage::TransferBudget::new(TransferLimits::default())),
112 network,
113 keys: SessionSource::Configured(CustodyChoice::Keyring),
114 identity: SessionSource::Configured(CustodyChoice::Keyring),
115 sqlite: SqliteSettings::default(),
116 sql_functions: coven_database::SqlFunctions::default(),
117 #[cfg(test)]
118 interrupt_after_swap: false,
119 #[cfg(test)]
120 fail_device_reset_notice: false,
121 #[cfg(test)]
122 refuse_database_close: false,
123 #[cfg(test)]
124 interrupt_after_registering: false,
125 #[cfg(test)]
126 interrupt_commit: false,
127 #[cfg(test)]
128 loading_recorder: None,
129 }
130 }
131
132 pub fn synced_tables(mut self, tables: Vec<SyncedTable>) -> Self {
134 self.tables = Some(tables);
135 self
136 }
137 pub fn namespaces(mut self, namespaces: Vec<coven_database::Namespace>) -> Self {
140 self.namespaces = namespaces;
141 self
142 }
143 pub fn migrations(mut self, migrations: Vec<Migration>) -> Self {
146 self.migrations = Some(migrations);
147 self
148 }
149 pub fn clock(mut self, clock: ClockRef) -> Self {
151 self.clock = clock;
152 self
153 }
154 pub fn id_source(mut self, ids: IdSourceRef) -> Self {
157 self.ids = ids;
158 self
159 }
160 #[cfg(any(test, feature = "test-utils"))]
164 pub fn storage_connector(mut self, connector: Arc<dyn StorageConnector>) -> Self {
165 self.connector = Some(connector);
166 self
167 }
168 fn connector(&mut self) -> Arc<dyn StorageConnector> {
169 self.connector
170 .get_or_insert_with(|| {
171 Arc::new(coven_storage::providers::ProviderConnector::new(
172 self.clock.clone(),
173 self.ids.clone(),
174 ))
175 })
176 .clone()
177 }
178
179 pub fn oauth_clients(mut self, clients: OAuthClients) -> Self {
181 self.oauth = Some(clients);
182 self
183 }
184 pub fn oauth_presenter(mut self, presenter: Arc<dyn OAuthPresenter>) -> Self {
187 self.presenter = Some(presenter);
188 self
189 }
190
191 pub async fn authenticate(&mut self, access: OAuthAccess) -> Result<(), OAuthError> {
194 let exchange = self.oauth_exchange();
195 let flow = Self::oauth_flow(exchange.clone(), self.presenter.clone())
196 .ok_or(OAuthError::Unavailable(access.provider()))?;
197 let tokens = flow.authenticate(access).await?;
198 self.authentication = Some(coven_sync::SessionSignIn::new(tokens, exchange));
199 Ok(())
200 }
201
202 fn oauth_exchange(&self) -> Option<OAuthExchange> {
205 Some(OAuthExchange::new(self.oauth.clone()?, self.clock.clone()))
206 }
207
208 fn signs_in(&self) -> bool {
210 self.oauth.is_some() && self.presenter.is_some()
211 }
212
213 fn oauth_flow(
214 exchange: Option<OAuthExchange>,
215 presenter: Option<Arc<dyn OAuthPresenter>>,
216 ) -> Option<OAuthFlow> {
217 Some(OAuthFlow::new(exchange?, presenter?))
218 }
219
220 pub fn max_concurrent_uploads(self, n: std::num::NonZeroUsize) -> Self {
222 let mut limits = self.transfers.limits();
223 limits.uploads = n;
224 self.transfers.set_limits(limits);
225 self
226 }
227 pub fn max_concurrent_downloads(self, n: std::num::NonZeroUsize) -> Self {
230 let mut limits = self.transfers.limits();
231 limits.downloads = n;
232 self.transfers.set_limits(limits);
233 self
234 }
235
236 pub fn key_custody(mut self, custody: CustodyChoice<StoreKeyring>) -> Self {
240 self.keys = SessionSource::Configured(custody);
241 self
242 }
243 pub fn identity_custody(mut self, custody: CustodyChoice<MemberIdentity>) -> Self {
246 self.identity = SessionSource::Configured(custody);
247 self
248 }
249
250 pub fn sqlite_settings(mut self, settings: SqliteSettings) -> Self {
255 self.sqlite = settings;
256 self
257 }
258
259 #[cfg(target_vendor = "apple")]
269 pub fn file_protection(mut self, protection: FileProtection) -> Self {
270 self.directories = self.directories.file_protection(protection);
271 self
272 }
273
274 pub fn collation<F>(mut self, name: impl Into<String>, version: u32, compare: F) -> Self
284 where
285 F: Fn(&str, &str) -> std::cmp::Ordering + Send + Sync + 'static,
286 {
287 self.sql_functions =
288 std::mem::take(&mut self.sql_functions).collation(name, version, compare);
289 self
290 }
291
292 pub fn function<F, T>(
305 mut self,
306 name: impl Into<String>,
307 arguments: u8,
308 determinism: Determinism,
309 compute: F,
310 ) -> Self
311 where
312 F: Fn(&FunctionArguments<'_>) -> SqliteResult<T> + Send + Sync + 'static,
313 T: Into<types::Value>,
314 {
315 self.sql_functions =
316 std::mem::take(&mut self.sql_functions).scalar(name, arguments, determinism, compute);
317 self
318 }
319
320 pub async fn create_store(self, name: &str) -> Result<CovenHandle, StoreCreationError> {
324 let name = name.to_owned();
325 let bugs = self.directories.bugs().clone();
326 crate::task::spawn_completing(
327 "create the store",
328 bugs,
329 creation::create(self, name, coven_foundation::files::BootstrapStore::publish),
330 )
331 .await?
332 }
333
334 pub async fn open(
342 self,
343 store: StoreId,
344 on_progress: impl Fn(Progress<OpenStep>),
345 ) -> CovenResult<CovenHandle> {
346 Box::pin(async move {
349 let opening = self.open_graph(store, Opening::ForUse).await?;
350 let progress = opening.database.subscribe_progress();
351 progress.forward(opening.open(), on_progress).await
352 })
353 .await
354 }
355
356 pub async fn resume_deletion(self, store: StoreId) -> Result<(), StoreDeletionError> {
366 Box::pin(async move {
367 let directory = self.directories.store_dir(&store);
368 let bugs = directory.bugs().clone();
369 let unsent =
370 crate::task::blocking_completing("read the deletion record", bugs, move || {
371 directory.unfinished_deletion()
372 })
373 .await??
374 .ok_or(StoreDeletionError::NoUnfinishedDeletion(store))?;
375 let opening = self
376 .open_graph(store, Opening::ToDelete)
377 .await
378 .map_err(StoreDeletionError::Open)?;
379 let handle = opening.open().await.map_err(StoreDeletionError::Open)?;
380 handle.leave(unsent).await
381 })
382 .await
383 }
384
385 pub async fn reset_device(
402 self,
403 store: StoreId,
404 device_name: &str,
405 on_status: impl Fn(BootstrapStatus),
406 ) -> Result<CovenHandle, BootstrapError> {
407 Box::pin(bootstrap::reset_device(self, store, device_name, on_status)).await
408 }
409
410 pub async fn restore_device(
432 self,
433 store: StoreId,
434 code: &str,
435 s3_credentials: Option<S3Credentials>,
436 device_name: &str,
437 prepare: impl FnOnce(BulkSqlContext<'_>) -> CovenResult<()> + Send + 'static,
438 on_status: impl Fn(BootstrapStatus),
439 cancel: &tokio::sync::watch::Receiver<bool>,
440 ) -> Result<CovenHandle, BootstrapError> {
441 Box::pin(bootstrap::restore_device(
442 self,
443 store,
444 code,
445 s3_credentials,
446 device_name,
447 Box::new(prepare),
448 on_status,
449 cancel,
450 ))
451 .await
452 }
453
454 pub async fn resume_replacement(
462 self,
463 store: StoreId,
464 on_status: impl Fn(BootstrapStatus),
465 cancel: &tokio::sync::watch::Receiver<bool>,
466 ) -> Result<CovenHandle, BootstrapError> {
467 Box::pin(bootstrap::resume_replacement(self, store, on_status, cancel)).await
468 }
469
470 pub async fn unfinished_joins(&self) -> Result<Vec<InviteInfo>, BootstrapError> {
476 bootstrap::unfinished_joins(self).await
477 }
478
479 pub async fn resume_join(
490 self,
491 store: StoreId,
492 on_status: impl Fn(BootstrapStatus),
493 control: &JoinControl,
494 ) -> Result<Option<CovenHandle>, BootstrapError> {
495 Box::pin(bootstrap::resume_join(self, store, on_status, control)).await
496 }
497
498 pub async fn unfinished_restores(&self) -> Result<Vec<CodeMetadata>, BootstrapError> {
505 bootstrap::unfinished_restores(self).await
506 }
507
508 pub async fn resume_restore(
518 self,
519 store: StoreId,
520 on_status: impl Fn(BootstrapStatus),
521 cancel: &tokio::sync::watch::Receiver<bool>,
522 ) -> Result<CovenHandle, BootstrapError> {
523 Box::pin(bootstrap::resume_restore(self, store, on_status, cancel)).await
524 }
525
526 fn database(&self, directory: StoreDir) -> CovenResult<DatabaseBuilder> {
527 let tables = self
528 .tables
529 .clone()
530 .ok_or(CovenError::MissingConfiguration { field: "synced_tables" })?;
531 let migrations = self
532 .migrations
533 .clone()
534 .ok_or(CovenError::MissingConfiguration { field: "migrations" })?;
535 let database = DatabaseBuilder::new(directory)
536 .migration_operation(coven_sync::SequenceSync::migration_operation)
537 .synced_tables(tables)
538 .namespaces(self.namespaces.clone())
539 .migrations(migrations)
540 .clock(self.clock.clone())
541 .id_source(self.ids.clone())
542 .sqlite_settings(self.sqlite.clone())
543 .sql_functions(self.sql_functions.clone());
544 #[cfg(test)]
545 let database = if self.refuse_database_close { database.refuse_close() } else { database };
546 Ok(database)
547 }
548
549 fn keychain(&self) -> Result<Arc<Keychain>, KeyError> {
550 Ok(self.keychain.clone())
551 }
552
553 async fn open_graph(self, store: StoreId, opening: Opening) -> CovenResult<OpeningStore> {
554 let directory = self.directories.store_dir(&store);
555 let database = self.database(directory.clone())?;
556 let lock = directory.lock_for_open().await?;
557 let closing = lock.closing_guard();
558 let bugs = directory.bugs().clone();
559 let result =
560 crate::task::blocking_completing("open the store's custody", bugs, move || {
561 let mut builder = self;
562 let settings = lock.settings()?;
563 let keychain = Arc::new(StoreKeychain::new(builder.keychain()?, settings.id));
564 refuse_unfinished_replacement(&directory, settings.id)?;
567 if opening == Opening::ForUse {
569 refuse_unfinished_deletion(&directory, settings.id)?;
570 }
571 if keychain.device_id()? != Some(settings.device_id) {
574 let identity = Self::make_session(
575 std::mem::replace(
576 &mut builder.identity,
577 SessionSource::Configured(CustodyChoice::InMemory),
578 ),
579 &directory,
580 settings.id,
581 keychain,
582 );
583 let store = settings.id;
584 let connected = coven_storage::recorded_config(&directory)
587 .map_err(|error| SettingsError::Corrupt(Box::new(error)))?
588 .is_some();
589 return Err(match identity?.read()? {
590 None if connected => CovenError::MemberKeysMissing { store },
591 _ => CovenError::DeviceKeyMissing { store },
592 });
593 }
594 coven_foundation::files::settle_files(&directory, &lock).map_err(|error| {
597 match error {
598 coven_foundation::files::FileError::DeviceLocked { path } => {
599 CovenError::DeviceLocked { path }
600 }
601 error => DbError::from(error).into(),
602 }
603 })?;
604 if !directory.is_unpublished().map_err(DbError::from)? {
606 keychain.delete_invite_code()?;
607 }
608 Ok(OpeningStore { database, lock, builder, directory, keychain })
609 })
610 .await
611 .unwrap_or_else(|shutdown| Err(shutdown.into()));
612 closing.disarm();
613 result
614 }
615
616 fn owners(
617 mut self,
618 directory: StoreDir,
619 keychain: Arc<StoreKeychain>,
620 continuing: Continuing,
621 ) -> CovenResult<OpeningOwners> {
622 let clients = self.oauth_exchange();
623 let oauth = Self::oauth_flow(clients.clone(), self.presenter.clone());
624 let settings = directory.settings()?;
625 let person = Arc::new(PersonEntries::new(self.keychain()?, settings.id));
626 let (state, storage, credentials) = match continuing {
627 Continuing::Fresh => (Arc::new(coven_sync::SequenceState::new()), None, None),
628 Continuing::Loaded { state, storage, credentials } => {
629 (state, Some(storage), credentials)
630 }
631 };
632 let credentials = credentials.unwrap_or_else(|| {
635 Arc::new(coven_sync::DeviceCredentials::new(
636 keychain.clone(),
637 person.clone(),
638 directory.clone(),
639 clients.clone(),
640 ))
641 });
642 let connector = self.connector();
643 let keys = Self::make_session(self.keys, &directory, settings.id, keychain.clone())?;
644 let identity =
645 Self::make_session(self.identity, &directory, settings.id, keychain.clone())?;
646 let network = coven_foundation::network_watch::NetworkWatch::start(
647 &self.network,
648 self.directories.bugs(),
649 )
650 .map_err(CovenError::NetworkWatch)?;
651 Ok(OpeningOwners {
652 directory,
653 custody: StoreCustody::new(keys.clone(), identity.clone(), keychain.clone()),
654 keychain,
655 person,
656 credentials,
657 connector,
658 oauth,
659 clients,
660 authentication: self.authentication,
661 transfers: self.transfers,
662 network,
663 device: settings.device_id,
664 keys,
665 identity,
666 state,
667 clock: self.clock,
668 ids: self.ids,
669 storage,
670 #[cfg(test)]
671 interrupt_commit: self.interrupt_commit,
672 })
673 }
674
675 fn make_session<T: KeyMaterial>(
676 chosen: SessionSource<T>,
677 directory: &StoreDir,
678 id: StoreId,
679 keychain: Arc<StoreKeychain>,
680 ) -> Result<Arc<KeySession<T>>, KeyError> {
681 let custody = match chosen {
682 SessionSource::Session(session) => return Ok(session),
683 SessionSource::Configured(custody) => custody,
684 };
685 let custody: Arc<dyn Custody<T>> = match custody {
686 CustodyChoice::Keyring => Arc::new(KeyringCustody::new(keychain)),
687 CustodyChoice::Passphrase(secret) => {
688 Arc::new(PassphraseCustody::new(secret, directory.owned_file(T::FILE), id))
689 }
690 CustodyChoice::InMemory => Arc::new(InMemoryCustody::empty()),
691 CustodyChoice::Custom(custody) => custody,
692 };
693 Ok(Arc::new(KeySession::open(custody)?))
694 }
695
696 #[cfg(test)]
698 pub(crate) fn interrupt_after_swap(mut self) -> Self {
699 self.interrupt_after_swap = true;
700 self
701 }
702
703 #[cfg(test)]
705 pub(crate) fn refuse_database_close(mut self) -> Self {
706 self.refuse_database_close = true;
707 self
708 }
709
710 #[cfg(test)]
712 pub(crate) fn refuse_network_watch(mut self, error: NetworkWatchError) -> Self {
713 self.network = coven_foundation::network_watch::NetworkSource::Refused(error);
714 self
715 }
716
717 #[cfg(test)]
719 pub(crate) fn fail_device_reset_notice(mut self) -> Self {
720 self.fail_device_reset_notice = true;
721 self
722 }
723
724 #[cfg(test)]
727 pub(crate) fn interrupt_after_registering(mut self) -> Self {
728 self.interrupt_after_registering = true;
729 self
730 }
731
732 #[cfg(test)]
735 pub(crate) fn interrupt_commit(mut self) -> Self {
736 self.interrupt_commit = true;
737 self
738 }
739
740 #[cfg(test)]
744 pub(crate) fn record_loading(
745 mut self,
746 recorder: Arc<RecordingClock<coven_database::LoadStep>>,
747 ) -> Self {
748 self.clock = recorder.clone();
749 self.loading_recorder = Some(recorder);
750 self
751 }
752}
753
754struct OpeningStore {
756 database: DatabaseBuilder,
757 lock: coven_foundation::files::StoreLock,
758 builder: CovenBuilder,
759 directory: StoreDir,
760 keychain: Arc<StoreKeychain>,
761}
762
763struct OpeningOwners {
764 transfers: Arc<coven_storage::TransferBudget>,
765 network: coven_foundation::network_watch::NetworkWatch,
767 keychain: Arc<StoreKeychain>,
768 person: Arc<PersonEntries>,
770 credentials: Arc<coven_sync::DeviceCredentials>,
771 connector: Arc<dyn StorageConnector>,
772 oauth: Option<OAuthFlow>,
773 clients: Option<OAuthExchange>,
774 authentication: Option<Arc<coven_sync::SessionSignIn>>,
775 device: DeviceId,
776 directory: StoreDir,
777 custody: StoreCustody,
778 keys: Arc<KeySession<StoreKeyring>>,
779 identity: Arc<KeySession<MemberIdentity>>,
780 state: Arc<coven_sync::SequenceState>,
782 clock: ClockRef,
783 ids: IdSourceRef,
784 storage: Option<Arc<coven_storage::StorageConnection>>,
786 #[cfg(test)]
788 interrupt_commit: bool,
789}
790
791impl OpeningStore {
792 async fn open(self) -> CovenResult<CovenHandle> {
793 let database = self.database.open_locked(self.lock).await?;
794 let state = database.store_state().await?;
795 if let Some(identity) = &state.store {
796 check_store_settings(identity, &self.directory)?;
797 }
798 let waits = Arc::new(
799 coven_sync::RetryWaits::open(database.clone(), self.builder.clock.clone()).await?,
800 );
801 let bugs = database.bugs().clone();
802 let owners = crate::task::blocking_completing("open the store's owners", bugs, move || {
803 let owners = self.builder.owners(self.directory, self.keychain, Continuing::Fresh)?;
804 if let Some(identity) = &state.store {
809 coven_sync::follow_name(
810 &owners.person,
811 &coven_sync::SavedName::of(identity),
812 || {
813 let location = coven_storage::recorded_config(&owners.directory)
814 .map_err(|error| CovenError::from(SyncError::from(error)))?;
815 coven_sync::replacement_code(
816 &state,
817 owners.identity.read()?.as_ref(),
818 location.as_ref(),
819 )
820 .map_err(CovenError::from)
821 },
822 )?;
823 owners.person.store_again()?;
827 }
828 Ok::<_, CovenError>(owners)
829 })
830 .await??;
831 let location = owners.credentials.location().map_err(CovenError::from)?;
832 let configured = location.is_connected();
833 let finishing =
836 Box::pin(crate::storage::settle_opened(&database, &owners.keychain, &owners.keys))
837 .await
838 .map_err(CovenError::from)?;
839 Box::pin(crate::storage::record_opened(&database, &owners.clock, location, None))
842 .await
843 .map_err(CovenError::from)?;
844 Ok(owners.into_handle(database, waits, configured, finishing))
845 }
846}
847
848#[derive(Clone, Copy, PartialEq, Eq)]
850enum Opening {
851 ForUse,
853 ToDelete,
855}
856
857pub(crate) fn refuse_unfinished_deletion(
860 directory: &impl StoreDirAccess,
861 store: StoreId,
862) -> CovenResult<()> {
863 match directory.unfinished_deletion().map_err(DbError::from)? {
864 Some(_) => Err(StoreLockError::DeletionUnfinished(store).into()),
865 None => Ok(()),
866 }
867}
868
869fn refuse_unfinished_replacement(
872 directory: &impl StoreDirAccess,
873 store: StoreId,
874) -> CovenResult<()> {
875 match directory.unfinished_replacement()? {
876 Some(record) => Err(StoreLockError::ReplacementUnfinished { store, by: record.by }.into()),
877 None => Ok(()),
878 }
879}
880
881fn check_store_settings(
884 identity: &coven_database::StoreIdentity,
885 directory: &impl StoreDirAccess,
886) -> CovenResult<()> {
887 Ok(identity.check_settings(&directory.settings()?)?)
888}
889
890pub(crate) fn device_credentials(
892 keychain: Arc<Keychain>,
893 store: StoreId,
894 directory: StoreDir,
895 oauth: Option<OAuthExchange>,
896) -> Arc<coven_sync::DeviceCredentials> {
897 Arc::new(coven_sync::DeviceCredentials::new(
898 Arc::new(StoreKeychain::new(keychain.clone(), store)),
899 Arc::new(PersonEntries::new(keychain, store)),
900 directory,
901 oauth,
902 ))
903}
904
905impl OpeningOwners {
906 fn into_handle(
908 self,
909 database: coven_database::Database,
910 waits: Arc<coven_sync::RetryWaits>,
911 configured: bool,
912 setup_finishing: bool,
913 ) -> CovenHandle {
914 let files = coven_sync::Files::new(
915 coven_database::FileDatabase::new(database.clone()),
916 self.directory.clone(),
917 self.storage.clone().map(|storage| storage as Arc<dyn coven_storage::Storage>),
918 self.state.clone(),
919 waits.clone(),
920 self.ids.clone(),
921 self.transfers.clone(),
922 self.clock.clone(),
923 self.network.view(),
924 );
925 let network =
926 coven_sync::NetworkFollow::start(self.network, waits.clone(), database.clone());
927 let steps = Arc::new(coven_sync::OperationSteps::new(
928 self.device,
929 self.identity.clone(),
930 self.ids.clone(),
931 self.clock.clone(),
932 self.directory.clone(),
933 self.credentials.clone(),
934 waits.clone(),
935 self.state.clone(),
936 ));
937 let codes = coven_sync::RestoreCodes::new(
938 self.directory.clone(),
939 database.clone(),
940 self.identity.clone(),
941 self.credentials.clone(),
942 );
943 let storage = crate::storage::StorageConnections::new(
944 database.clone(),
945 files.clone(),
946 self.identity,
947 self.person,
948 self.credentials,
949 codes.clone(),
950 self.state.clone(),
951 self.directory.clone(),
952 waits.clone(),
953 self.transfers,
954 self.keys,
955 self.connector,
956 self.oauth,
957 self.clients,
958 self.authentication,
959 self.storage,
960 configured,
961 self.keychain.clone(),
962 setup_finishing,
963 self.device,
964 self.ids,
965 self.clock,
966 );
967 CovenHandle::new(
968 database,
969 self.custody,
970 files,
971 steps,
972 waits,
973 self.state,
974 codes,
975 Arc::new(network),
976 Arc::new(storage),
977 self.directory,
978 self.keychain,
979 self.device,
980 )
981 }
982}
983
984#[cfg(test)]
985#[path = "builder_tests.rs"]
986mod tests;
987
988#[path = "builder_creation.rs"]
989mod creation;
990
991#[path = "builder_read_only.rs"]
992mod read_only;
993
994#[path = "bootstrap.rs"]
995mod bootstrap;
996pub use bootstrap::{
997 join_with_invite, restore_from_code, restore_from_saved_code, BootstrapError, BootstrapStatus,
998 JoinControl,
999};