Skip to main content

ouisync/repository/
mod.rs

1pub(crate) mod monitor;
2
3mod credentials;
4mod metadata;
5mod params;
6mod vault;
7mod worker;
8
9#[cfg(test)]
10mod tests;
11
12pub use self::{credentials::Credentials, metadata::Metadata, params::RepositoryParams};
13
14pub(crate) use self::{
15    metadata::{data_version, quota},
16    vault::Vault,
17};
18
19use self::monitor::RepositoryMonitor;
20use crate::{
21    access_control::{Access, AccessChange, AccessKeys, AccessMode, AccessSecrets, LocalSecret},
22    block_tracker::BlockRequestMode,
23    branch::{Branch, BranchShared},
24    crypto::{PasswordSalt, sign::PublicKey},
25    db::{self, DatabaseId},
26    debug::DebugPrinter,
27    directory::{Directory, DirectoryFallback, DirectoryLocking, EntryRef, EntryType},
28    error::{Error, Result},
29    event::{Event, EventSender},
30    file::File,
31    joint_directory::{JointDirectory, JointEntryRef, MissingVersionStrategy},
32    path,
33    progress::Progress,
34    protocol::{BLOCK_SIZE, RootNodeFilter, StorageSize},
35    store,
36    sync::stream::Throttle,
37    version_vector::VersionVector,
38};
39use camino::Utf8Path;
40use deadlock::{BlockingMutex, BlockingRwLock};
41use futures_util::{StreamExt, stream};
42use futures_util::{TryStreamExt, future};
43use metrics::{NoopRecorder, Recorder};
44use scoped_task::ScopedJoinHandle;
45use state_monitor::StateMonitor;
46use std::{
47    borrow::Cow,
48    path::{Path, PathBuf},
49    pin::pin,
50    sync::Arc,
51    time::SystemTime,
52};
53use tokio::{
54    sync::broadcast::{self, error::RecvError},
55    time::Duration,
56};
57use tracing::instrument::Instrument;
58
59const EVENT_CHANNEL_CAPACITY: usize = 10000;
60
61pub struct Repository {
62    shared: Arc<Shared>,
63    worker_handle: BlockingMutex<Option<ScopedJoinHandle<()>>>,
64    progress_reporter_handle: BlockingMutex<Option<ScopedJoinHandle<()>>>,
65}
66
67/// List of repository database files. Includes the main db file and any auxiliary files.
68///
69/// The aux files don't always exists but when they do and one wants to rename, move or delete the
70/// repository, they should rename/move/delete these files as well.
71///
72/// Note ideally the aux files should be deleted automatically when the repo has been closed. But
73/// because of a [bug][1] in sqlx, they are sometimes not and need to be handled
74/// (move,deleted) manually. When the bug is fixed, this function can be removed.
75///
76/// [1]: https://github.com/launchbadge/sqlx/issues/3217
77pub fn database_files(store_path: impl AsRef<Path>) -> Vec<PathBuf> {
78    // Sqlite database consists of up to three files: main db (always present), WAL and WAL-index.
79    ["", "-wal", "-shm"]
80        .into_iter()
81        .map(|suffix| {
82            let mut path = store_path.as_ref().as_os_str().to_owned();
83            path.push(suffix);
84            path.into()
85        })
86        .collect()
87}
88
89impl Repository {
90    /// Creates a new repository.
91    pub async fn create(params: &RepositoryParams<impl Recorder>, access: Access) -> Result<Self> {
92        let pool = params.create().await?;
93        let device_id = params.device_id();
94        let monitor = params.monitor();
95
96        let mut tx = pool.begin_write().await?;
97
98        let local_keys = metadata::initialize_access_secrets(&mut tx, &access).await?;
99        let writer_id =
100            metadata::get_or_generate_writer_id(&mut tx, local_keys.write.as_deref()).await?;
101        metadata::set_device_id(&mut tx, &device_id).await?;
102
103        tx.commit().await?;
104
105        let credentials = Credentials {
106            secrets: access.secrets(),
107            writer_id,
108        };
109
110        Self::new(pool, credentials, monitor).init().await
111    }
112
113    /// Opens an existing repository.
114    pub async fn open(
115        params: &RepositoryParams<impl Recorder>,
116        local_secret: Option<LocalSecret>,
117        access_mode: AccessMode,
118    ) -> Result<Self> {
119        let pool = params.open().await?;
120        let monitor = params.monitor();
121        let device_id = params.device_id();
122
123        let mut tx = pool.begin_write().await?;
124
125        let (secrets, local_key) =
126            metadata::get_access_secrets(&mut tx, local_secret.as_ref()).await?;
127
128        let secrets = secrets.with_mode(access_mode);
129
130        let writer_id = if metadata::check_device_id(&mut tx, &device_id).await? {
131            if secrets.can_write() {
132                metadata::get_or_generate_writer_id(&mut tx, local_key.as_deref()).await?
133            } else {
134                metadata::generate_writer_id()
135            }
136        } else {
137            // Device id changed, likely because the repo database has been transferred to a
138            // different device. We need to generate a new writer id.
139            //
140            // Note we need to do this even when not currently opening the repo in write mode. This
141            // is so that when the access mode is subsequently switched to write
142            // (with [set_access_mode]) we don't end up using the wrong writer_id.
143            let writer_id = metadata::generate_writer_id();
144
145            metadata::set_device_id(&mut tx, &device_id).await?;
146            metadata::set_writer_id(&mut tx, &writer_id, local_key.as_deref()).await?;
147
148            writer_id
149        };
150
151        tx.commit().await?;
152
153        let credentials = Credentials { secrets, writer_id };
154
155        Self::new(pool, credentials, monitor).init().await
156    }
157
158    fn new(pool: db::Pool, credentials: Credentials, monitor: RepositoryMonitor) -> Self {
159        Self {
160            shared: Arc::new(Shared::new(pool, credentials, monitor)),
161            worker_handle: BlockingMutex::new(None),
162            progress_reporter_handle: BlockingMutex::new(None),
163        }
164    }
165
166    async fn init(self) -> Result<Self> {
167        let credentials = self.credentials();
168
169        if let Some(keys) = credentials
170            .secrets
171            .write_secrets()
172            .map(|secrets| &secrets.write_keys)
173        {
174            self.shared
175                .vault
176                .store()
177                .migrate_data(credentials.writer_id, keys)
178                .await?;
179        }
180
181        {
182            let mut conn = self.shared.vault.store().db().acquire().await?;
183            if let Some(block_expiration) = metadata::block_expiration::get(&mut conn).await? {
184                self.shared
185                    .vault
186                    .set_block_expiration(Some(block_expiration))?;
187            }
188        }
189
190        tracing::debug!(
191            parent: self.shared.vault.monitor.span(),
192            access = ?credentials.secrets.access_mode(),
193            writer_id = ?credentials.writer_id,
194            "Repository opened"
195        );
196
197        *self.worker_handle.lock().unwrap() = Some(spawn_worker(self.shared.clone()));
198
199        *self.progress_reporter_handle.lock().unwrap() = Some(scoped_task::spawn(
200            report_sync_progress(self.shared.vault.clone())
201                .instrument(self.shared.vault.monitor.span().clone()),
202        ));
203
204        Ok(self)
205    }
206
207    pub async fn database_id(&self) -> Result<DatabaseId> {
208        Ok(metadata::get_or_generate_database_id(self.db()).await?)
209    }
210
211    pub async fn requires_local_secret_for_reading(&self) -> Result<bool> {
212        let mut conn = self.db().acquire().await?;
213        Ok(metadata::requires_local_secret_for_reading(&mut conn).await?)
214    }
215
216    pub async fn requires_local_secret_for_writing(&self) -> Result<bool> {
217        let mut conn = self.db().acquire().await?;
218        Ok(metadata::requires_local_secret_for_writing(&mut conn).await?)
219    }
220
221    pub fn store_path(&self) -> &Path {
222        self.shared.vault.store().path()
223    }
224
225    /// Sets, unsets or changes local secrets for accessing the repository or disables the given
226    /// access mode.
227    ///
228    /// In order to enable or change a given access mode the repository must currently be in at
229    /// least that access mode. Disabling an access mode doesn't have this restriction.
230    ///
231    /// Disabling a given access mode makes the repo impossible to be opened in that mode anymore.
232    /// However, when the repo is currently in that mode it still remains in it until the repo is
233    /// closed.
234    ///
235    /// To restore a disabled mode the repo must first be put into that mode using
236    /// [Self::set_credentials()] where the `Credentials` must be obtained from `AccessSecrets` with
237    /// at least the mode one wants to restore.
238    ///
239    /// If `read` or `write` is `None` then no change is made to that mode. If both are `None` then
240    /// this function is a no-op.
241    ///
242    /// Disabling the read mode while keeping write mode enabled is allowed but not very useful as
243    /// write mode also grants read access.
244    pub async fn set_access(
245        &self,
246        read_change: Option<AccessChange>,
247        write_change: Option<AccessChange>,
248    ) -> Result<()> {
249        let mut tx = self.db().begin_write().await?;
250
251        if let Some(change) = read_change {
252            self.set_read_access(&mut tx, change).await?;
253        }
254
255        if let Some(change) = write_change {
256            self.set_write_access(&mut tx, change).await?;
257        }
258
259        tx.commit().await?;
260
261        Ok(())
262    }
263
264    async fn set_read_access(
265        &self,
266        tx: &mut db::WriteTransaction,
267        change: AccessChange,
268    ) -> Result<()> {
269        let local = match &change {
270            AccessChange::Enable(Some(local_secret)) => {
271                Some(metadata::secret_to_key_and_salt(local_secret))
272            }
273            AccessChange::Enable(None) => None,
274            AccessChange::Disable => {
275                metadata::remove_read_key(tx).await?;
276                return Ok(());
277            }
278        };
279
280        let (id, read_key) = {
281            let cred = self.shared.credentials.read().unwrap();
282            (
283                *cred.secrets.id(),
284                cred.secrets
285                    .read_key()
286                    .ok_or(Error::PermissionDenied)?
287                    .clone(),
288            )
289        };
290
291        metadata::set_read_key(
292            tx,
293            &id,
294            &read_key,
295            local.as_ref().map(|(k, s)| (k.as_ref(), s.as_ref())),
296        )
297        .await?;
298
299        Ok(())
300    }
301
302    async fn set_write_access(
303        &self,
304        tx: &mut db::WriteTransaction,
305        change: AccessChange,
306    ) -> Result<()> {
307        let local = match &change {
308            AccessChange::Enable(Some(local_secret)) => {
309                Some(metadata::secret_to_key_and_salt(local_secret))
310            }
311            AccessChange::Enable(None) => None,
312            AccessChange::Disable => {
313                metadata::remove_write_key(tx).await?;
314                return Ok(());
315            }
316        };
317
318        let (write_secrets, writer_id) = {
319            let cred = self.shared.credentials.read().unwrap();
320            (
321                cred.secrets
322                    .write_secrets()
323                    .ok_or(Error::PermissionDenied)?
324                    .clone(),
325                cred.writer_id,
326            )
327        };
328
329        metadata::set_write_key(
330            tx,
331            &write_secrets,
332            local.as_ref().map(|(k, s)| (k.as_ref(), s.as_ref())),
333        )
334        .await?;
335        metadata::set_writer_id(tx, &writer_id, local.as_ref().map(|(k, _)| k.as_ref())).await?;
336
337        Ok(())
338    }
339
340    /// Gets the current credentials of this repository.
341    ///
342    /// See also [Self::set_credentials()].
343    pub fn credentials(&self) -> Credentials {
344        self.shared.credentials.read().unwrap().clone()
345    }
346
347    pub fn secrets(&self) -> AccessSecrets {
348        self.shared.credentials.read().unwrap().secrets.clone()
349    }
350
351    /// Gets the current access mode of this repository.
352    pub fn access_mode(&self) -> AccessMode {
353        self.shared
354            .credentials
355            .read()
356            .unwrap()
357            .secrets
358            .access_mode()
359    }
360
361    /// Switches the repository to the given mode.
362    ///
363    /// The actual mode the repository gets switched to is the higher of the current access mode
364    /// and the mode provided by `local_key` but at most the mode specified in `access_mode`
365    /// ("higher" means according to `AccessMode`'s `Ord` impl, that is: Write > Read > Blind).
366    pub async fn set_access_mode(
367        &self,
368        access_mode: AccessMode,
369        local_secret: Option<LocalSecret>,
370    ) -> Result<()> {
371        // Try to use the current secrets but fall back to the secrets stored in the metadata if the
372        // current ones are insufficient and the stored ones have higher access mode.
373        let old_secrets = {
374            let creds = self.shared.credentials.read().unwrap();
375
376            if creds.secrets.access_mode() == access_mode {
377                return Ok(());
378            }
379
380            creds.secrets.clone()
381        };
382
383        let mut tx = self.db().begin_write().await?;
384
385        let (secrets, local_key) = if old_secrets.access_mode() >= access_mode {
386            (old_secrets, None)
387        } else {
388            let (new_secrets, local_key) =
389                metadata::get_access_secrets(&mut tx, local_secret.as_ref()).await?;
390
391            if new_secrets.access_mode() > old_secrets.access_mode() {
392                (new_secrets, local_key)
393            } else {
394                (old_secrets, None)
395            }
396        };
397
398        let secrets = secrets.with_mode(access_mode);
399
400        let writer_id = if secrets.can_write() {
401            metadata::get_or_generate_writer_id(&mut tx, local_key.as_deref()).await?
402        } else {
403            metadata::generate_writer_id()
404        };
405
406        tx.commit().await?;
407
408        // Data migration can only be applied in write mode so apply any pending ones if we just
409        // switched to write mode.
410        if let Some(write_keys) = secrets.write_secrets().map(|secrets| &secrets.write_keys) {
411            self.shared
412                .vault
413                .store()
414                .migrate_data(writer_id, write_keys)
415                .await?;
416        }
417
418        self.update_credentials(Credentials { secrets, writer_id });
419
420        Ok(())
421    }
422
423    /// Overrides the current credentials of this repository.
424    ///
425    /// This is useful for moving/renaming the repo database or to restore access which has been
426    /// either disabled or it's local secret lost.
427    ///
428    /// # Move/rename the repo db
429    ///
430    /// 1. Obtain the current credentials with [Self::credentials()] and keep them locally.
431    /// 2. Close the repo.
432    /// 3. Rename the repo database files(s).
433    /// 4. Open the repo from its new location in blind mode.
434    /// 5. Restore the credentials from step 1 with [Self::set_credentials()].
435    ///
436    /// # Restore access
437    ///
438    /// 1. Get the `AccessSecrets` the repository was originally created from (e.g., by extracting
439    ///    them from the original `ShareToken`).
440    /// 2. Construct `Credentials` using this access secrets and a random writer id.
441    /// 3. Restore the credentials with [Self::set_credentials()].
442    /// 4. Enable/change the access with [Self::set_access()].
443    pub async fn set_credentials(&self, credentials: Credentials) -> Result<()> {
444        // Check the credentials are actually for this repository
445        let expected_id = {
446            let mut conn = self.db().acquire().await?;
447            metadata::get_repository_id(&mut conn).await?
448        };
449
450        if credentials.secrets.id() != &expected_id {
451            return Err(Error::PermissionDenied);
452        }
453
454        if let Some(write_secrets) = credentials.secrets.write_secrets() {
455            self.shared
456                .vault
457                .store()
458                .migrate_data(credentials.writer_id, &write_secrets.write_keys)
459                .await?;
460        }
461
462        self.update_credentials(credentials);
463
464        Ok(())
465    }
466
467    pub async fn unlock_secrets(&self, local_secret: LocalSecret) -> Result<AccessSecrets> {
468        let mut tx = self.db().begin_write().await?;
469        Ok(metadata::get_access_secrets(&mut tx, Some(&local_secret))
470            .await?
471            .0)
472    }
473
474    /// Get accessor for repository metadata. The metadata are arbitrary key-value entries that are
475    /// stored inside the repository but not synced to other replicas.
476    pub fn metadata(&self) -> Metadata {
477        self.shared.vault.metadata()
478    }
479
480    /// Set the storage quota in bytes. Use `None` to disable quota. Default is `None`.
481    pub async fn set_quota(&self, quota: Option<StorageSize>) -> Result<()> {
482        self.shared.vault.set_quota(quota).await
483    }
484
485    /// Get the storage quota in bytes or `None` if no quota is set.
486    pub async fn quota(&self) -> Result<Option<StorageSize>> {
487        self.shared.vault.quota().await
488    }
489
490    /// Set the duration after which blocks start to expire (are deleted) when not used. Use `None`
491    /// to disable expiration. Default is `None`.
492    pub async fn set_block_expiration(&self, block_expiration: Option<Duration>) -> Result<()> {
493        let mut tx = self.db().begin_write().await?;
494        metadata::block_expiration::set(&mut tx, block_expiration).await?;
495        tx.commit().await?;
496
497        self.shared.vault.set_block_expiration(block_expiration)
498    }
499
500    /// Get the block expiration duration. `None` means block expiration is not set.
501    pub fn block_expiration(&self) -> Option<Duration> {
502        self.shared.vault.block_expiration()
503    }
504
505    /// Get the time when the last block expired or `None` if there are still some unexpired blocks.
506    /// If block expiration is not enabled, always return `None`.
507    pub fn last_block_expiration_time(&self) -> Option<SystemTime> {
508        self.shared.vault.last_block_expiration_time()
509    }
510
511    /// Get the total size of the data stored in this repository.
512    pub async fn size(&self) -> Result<StorageSize> {
513        self.shared.vault.size().await
514    }
515
516    pub fn handle(&self) -> RepositoryHandle {
517        RepositoryHandle {
518            vault: self.shared.vault.clone(),
519        }
520    }
521
522    pub async fn get_read_password_salt(&self) -> Result<PasswordSalt> {
523        let mut tx = self.db().begin_write().await?;
524        Ok(metadata::get_password_salt(&mut tx, metadata::KeyType::Read).await?)
525    }
526
527    pub async fn get_write_password_salt(&self) -> Result<PasswordSalt> {
528        let mut tx = self.db().begin_write().await?;
529        Ok(metadata::get_password_salt(&mut tx, metadata::KeyType::Write).await?)
530    }
531
532    /// Get the state monitor node of this repository.
533    pub fn monitor(&self) -> &StateMonitor {
534        self.shared.vault.monitor.node()
535    }
536
537    /// Export the repository to the given file.
538    ///
539    /// The repository is currently exported as read-only with no password. In the future other
540    /// modes might be added.
541    pub async fn export(&self, dst: &Path) -> Result<()> {
542        /// RAII to delete the exported repo in case the process fails or is interupted to avoid
543        /// exporting the repo with a wrong access mode.
544        struct Cleanup<'a> {
545            path: &'a Path,
546            armed: bool,
547        }
548
549        impl Drop for Cleanup<'_> {
550            fn drop(&mut self) {
551                if !self.armed {
552                    return;
553                }
554
555                if let Err(error) = std::fs::remove_file(self.path) {
556                    tracing::error!(
557                        path = ?self.path,
558                        ?error,
559                        "failed to delete partially exported repository",
560                    );
561                }
562            }
563        }
564
565        let mut cleanup = Cleanup {
566            path: dst,
567            armed: true,
568        };
569
570        // Export the repo to `dst`
571        self.shared.vault.store().export(dst).await?;
572
573        // Open it and strip write access and read password (if any).
574        let pool = db::open(dst).await?;
575        let credentials = self.credentials().with_mode(AccessMode::Read);
576        let access_mode = credentials.secrets.access_mode();
577        let monitor = RepositoryMonitor::new(StateMonitor::make_root(), &NoopRecorder);
578        let repo = Self::new(pool, credentials, monitor);
579
580        match access_mode {
581            AccessMode::Blind => {
582                repo.set_access(Some(AccessChange::Disable), Some(AccessChange::Disable))
583                    .await?
584            }
585            AccessMode::Read => {
586                repo.set_access(
587                    Some(AccessChange::Enable(None)),
588                    Some(AccessChange::Disable),
589                )
590                .await?
591            }
592            AccessMode::Write => unreachable!(),
593        }
594
595        repo.close().await?;
596
597        cleanup.armed = false;
598
599        Ok(())
600    }
601
602    /// Looks up an entry by its path. The path must be relative to the repository root.
603    /// If the entry exists, returns its `EntryType`, otherwise returns `EntryNotFound`.
604    pub async fn lookup_type<P: AsRef<Utf8Path>>(&self, path: P) -> Result<EntryType> {
605        match path::decompose(path.as_ref()) {
606            Some((parent, name)) => {
607                let parent = self.open_directory(parent).await?;
608                Ok(parent.lookup_unique(name)?.entry_type())
609            }
610            None => Ok(EntryType::Directory),
611        }
612    }
613
614    /// Opens a file at the given path (relative to the repository root)
615    pub async fn open_file<P: AsRef<Utf8Path>>(&self, path: P) -> Result<File> {
616        let (parent, name) = path::decompose(path.as_ref()).ok_or(Error::EntryIsDirectory)?;
617
618        self.cd(parent)
619            .await?
620            .lookup_unique(name)?
621            .file()?
622            .open()
623            .await
624    }
625
626    /// Open a specific version of the file at the given path.
627    pub async fn open_file_version<P: AsRef<Utf8Path>>(
628        &self,
629        path: P,
630        branch_id: &PublicKey,
631    ) -> Result<File> {
632        let (parent, name) = path::decompose(path.as_ref()).ok_or(Error::EntryIsDirectory)?;
633
634        self.cd(parent)
635            .await?
636            .lookup_version(name, branch_id)?
637            .open()
638            .await
639    }
640
641    /// Opens a directory at the given path (relative to the repository root)
642    pub async fn open_directory<P: AsRef<Utf8Path>>(&self, path: P) -> Result<JointDirectory> {
643        self.cd(path).await
644    }
645
646    /// Creates a new file at the given path.
647    pub async fn create_file<P: AsRef<Utf8Path>>(&self, path: P) -> Result<File> {
648        let file = self
649            .local_branch()?
650            .ensure_file_exists(path.as_ref())
651            .await?;
652
653        Ok(file)
654    }
655
656    /// Creates a new directory at the given path.
657    pub async fn create_directory<P: AsRef<Utf8Path>>(&self, path: P) -> Result<Directory> {
658        let dir = self
659            .local_branch()?
660            .ensure_directory_exists(path.as_ref())
661            .await?;
662
663        Ok(dir)
664    }
665
666    /// Removes the file or directory (must be empty) and flushes its parent directory.
667    pub async fn remove_entry<P: AsRef<Utf8Path>>(&self, path: P) -> Result<()> {
668        let (parent, name) = path::decompose(path.as_ref()).ok_or(Error::OperationNotSupported)?;
669        let mut parent = self.cd(parent).await?;
670        parent.remove_entry(name).await?;
671
672        Ok(())
673    }
674
675    /// Removes the file or directory (including its content) and flushes its parent directory.
676    pub async fn remove_entry_recursively<P: AsRef<Utf8Path>>(&self, path: P) -> Result<()> {
677        let (parent, name) = path::decompose(path.as_ref()).ok_or(Error::OperationNotSupported)?;
678        let mut parent = self.cd(parent).await?;
679        parent.remove_entry_recursively(name).await?;
680
681        Ok(())
682    }
683
684    /// Moves (renames) an entry from the source path to the destination path.
685    /// If both source and destination refer to the same entry, this is a no-op.
686    pub async fn move_entry<S: AsRef<Utf8Path>, D: AsRef<Utf8Path>>(
687        &self,
688        src: S,
689        dst: D,
690    ) -> Result<()> {
691        let (src_dir_path, src_name) = path::decompose(src.as_ref()).ok_or(Error::EntryNotFound)?;
692        let (dst_dir_path, dst_name) = path::decompose(dst.as_ref()).ok_or(Error::EntryNotFound)?;
693
694        let local_branch = self.local_branch()?;
695        let src_joint_dir = self.cd(src_dir_path).await?;
696
697        // If the src is in a remote branch, need to merge it into the local one first:
698        let (mut src_dir, src_name, src_type) = match src_joint_dir.lookup_unique(src_name)? {
699            JointEntryRef::File(entry) => {
700                let src_name = entry.name().to_string();
701
702                let mut file = entry.open().await?;
703                file.fork(local_branch.clone()).await?;
704
705                (file.parent().await?, Cow::Owned(src_name), EntryType::File)
706            }
707            JointEntryRef::Directory(entry) => {
708                let mut dir_to_move = entry
709                    .open_with(MissingVersionStrategy::Skip, DirectoryFallback::Disabled)
710                    .await?;
711
712                let src_dir = dir_to_move
713                    .merge()
714                    .await?
715                    .1
716                    .parent()
717                    .await?
718                    .ok_or(Error::OperationNotSupported /* can't move root */)?;
719
720                (src_dir, Cow::Borrowed(src_name), EntryType::Directory)
721            }
722        };
723
724        let src_entry = src_dir.lookup(&src_name)?.clone_data();
725
726        let mut dst_joint_dir = self.cd(&dst_dir_path).await?;
727        let dst_dir = dst_joint_dir
728            .local_version_mut()
729            .ok_or(Error::PermissionDenied)?;
730
731        let dst_old_entry = dst_dir.lookup(dst_name);
732
733        // Emulating the behaviour of the libc's `rename` function
734        // (https://www.man7.org/linux/man-pages/man2/rename.2.html)
735        let dst_old_vv = match (src_type, dst_old_entry) {
736            (EntryType::File | EntryType::Directory, Ok(EntryRef::Tombstone(old_entry))) => {
737                old_entry.version_vector().clone()
738            }
739            (EntryType::File | EntryType::Directory, Err(Error::EntryNotFound)) => {
740                VersionVector::new()
741            }
742            (EntryType::File | EntryType::Directory, Err(error)) => return Err(error),
743            (EntryType::File, Ok(EntryRef::File(old_entry))) => old_entry.version_vector().clone(),
744            (EntryType::Directory, Ok(EntryRef::Directory(old_entry))) => {
745                if old_entry
746                    .open(DirectoryFallback::Disabled)
747                    .await?
748                    .entries()
749                    .all(|entry| entry.is_tombstone())
750                {
751                    old_entry.version_vector().clone()
752                } else {
753                    return Err(Error::DirectoryNotEmpty);
754                }
755            }
756            (EntryType::File, Ok(EntryRef::Directory(_))) => return Err(Error::EntryIsDirectory),
757            (EntryType::Directory, Ok(EntryRef::File(_))) => return Err(Error::EntryIsFile),
758        };
759
760        let dst_vv = dst_old_vv
761            .merged(src_entry.version_vector())
762            .incremented(*local_branch.id());
763
764        src_dir
765            .move_entry(&src_name, src_entry, dst_dir, dst_name, dst_vv)
766            .await?;
767
768        Ok(())
769    }
770
771    /// Returns the local branch or `Error::PermissionDenied` if this repo doesn't have at least
772    /// read access.
773    pub fn local_branch(&self) -> Result<Branch> {
774        self.shared.local_branch()
775    }
776
777    /// Returns the branch corresponding to the given id or `Error::PermissionDenied. if this repo
778    /// doesn't have at least read access.
779    #[cfg(test)]
780    pub fn get_branch(&self, id: PublicKey) -> Result<Branch> {
781        self.shared.get_branch(id)
782    }
783
784    /// Returns version vector of the given branch. Works in all access moded.
785    pub async fn get_branch_version_vector(&self, writer_id: &PublicKey) -> Result<VersionVector> {
786        Ok(self
787            .shared
788            .vault
789            .store()
790            .acquire_read()
791            .await?
792            .load_latest_approved_root_node(writer_id, RootNodeFilter::Any)
793            .await?
794            .proof
795            .into_version_vector())
796    }
797
798    /// Returns the version vector calculated by merging the version vectors of all branches.
799    pub async fn get_merged_version_vector(&self) -> Result<VersionVector> {
800        Ok(self
801            .shared
802            .vault
803            .store()
804            .acquire_read()
805            .await?
806            .load_latest_approved_root_nodes()
807            .try_fold(VersionVector::default(), |mut merged, node| {
808                merged.merge(&node.proof.version_vector);
809                future::ready(Ok(merged))
810            })
811            .await?)
812    }
813
814    /// Subscribe to event notifications.
815    pub fn subscribe(&self) -> broadcast::Receiver<Event> {
816        self.shared.vault.event_tx.subscribe()
817    }
818
819    /// Gets the syncing progress of this repository (number of downloaded blocks / number of
820    /// all blocks)
821    pub async fn sync_progress(&self) -> Result<Progress> {
822        Ok(self.shared.vault.store().sync_progress().await?)
823    }
824
825    /// Check integrity of the stored data.
826    // TODO: Return more detailed info about any integrity violation.
827    pub async fn check_integrity(&self) -> Result<bool> {
828        Ok(self.shared.vault.store().check_integrity().await?)
829    }
830
831    // Opens the root directory across all branches as JointDirectory.
832    async fn root(&self) -> Result<JointDirectory> {
833        let local_branch = self.local_branch()?;
834        let branches = self.shared.load_branches().await?;
835
836        // If we are writer and the local branch doesn't exist yet in the db we include it anyway.
837        // This fixes a race condition when the local branch doesn't exist yet at the moment we
838        // load the branches but is subsequently created by merging a remote branch and the remote
839        // branch is then pruned.
840        let branches = if local_branch.keys().write().is_some()
841            && branches
842                .iter()
843                .all(|branch| branch.id() != local_branch.id())
844        {
845            let mut branches = branches;
846            branches.push(local_branch.clone());
847            branches
848        } else {
849            branches
850        };
851
852        let mut dirs = Vec::new();
853
854        for branch in branches {
855            let dir = match branch
856                .open_root(DirectoryLocking::Enabled, DirectoryFallback::Enabled)
857                .await
858            {
859                Ok(dir) => dir,
860                Err(error @ Error::Store(store::Error::BranchNotFound)) => {
861                    tracing::trace!(
862                        branch_id = ?branch.id(),
863                        ?error,
864                        "Failed to open root directory"
865                    );
866                    // Either this is the local branch which doesn't exist yet in the store or a
867                    // remote branch which has been pruned in the meantime. This is safe to ignore.
868                    continue;
869                }
870                Err(error @ Error::Store(store::Error::BlockNotFound)) => {
871                    tracing::trace!(
872                        branch_id = ?branch.id(),
873                        ?error,
874                        "Failed to open root directory"
875                    );
876                    // Some branch root blocks may not have been loaded across the network yet.
877                    // This is safe to ignore.
878                    continue;
879                }
880                Err(error) => {
881                    tracing::error!(
882                        branch_id = ?branch.id(),
883                        ?error,
884                        "Failed to open root directory"
885                    );
886                    return Err(error);
887                }
888            };
889
890            dirs.push(dir);
891        }
892
893        Ok(JointDirectory::new(Some(local_branch), dirs))
894    }
895
896    pub async fn cd<P: AsRef<Utf8Path>>(&self, path: P) -> Result<JointDirectory> {
897        self.root().await?.cd(path).await
898    }
899
900    /// Close all db connections held by this repository. After this function returns, any
901    /// subsequent operation on this repository that requires to access the db returns an error.
902    pub async fn close(&self) -> Result<()> {
903        // Abort and *await* the tasks to make sure that the state they are holding is definitely
904        // dropped before we return from this function.
905        for task in [&self.worker_handle, &self.progress_reporter_handle] {
906            let task = task.lock().unwrap().take();
907            if let Some(task) = task {
908                task.abort();
909                task.await.ok();
910            }
911        }
912
913        self.shared.vault.store().close().await?;
914
915        Ok(())
916    }
917
918    pub async fn debug_print_root(&self) {
919        self.debug_print(DebugPrinter::new()).await
920    }
921
922    pub async fn debug_print(&self, print: DebugPrinter) {
923        print.display(&"Repository");
924
925        let branches = match self.shared.load_branches().await {
926            Ok(branches) => branches,
927            Err(error) => {
928                print.display(&format_args!("failed to load branches: {error:?}"));
929                return;
930            }
931        };
932
933        let writer_id = self.shared.credentials.read().unwrap().writer_id;
934
935        for branch in branches {
936            let print = print.indent();
937            let local = if branch.id() == &writer_id {
938                " (local)"
939            } else {
940                ""
941            };
942            print.display(&format_args!(
943                "Branch ID: {:?}{}, root block ID:{:?}",
944                branch.id(),
945                local,
946                branch.root_block_id().await
947            ));
948            let print = print.indent();
949            print.display(&format_args!(
950                "/, vv: {:?}",
951                branch.version_vector().await.unwrap_or_default()
952            ));
953            branch.debug_print(print.indent()).await;
954        }
955
956        print.display(&"Index");
957        let print = print.indent();
958        self.shared.vault.debug_print(print).await;
959    }
960
961    /// Returns the total number of blocks in this repository. This is useful for diagnostics and
962    /// tests.
963    pub async fn count_blocks(&self) -> Result<u64> {
964        Ok(self.shared.vault.store().count_blocks().await?)
965    }
966
967    fn db(&self) -> &db::Pool {
968        self.shared.vault.store().db()
969    }
970
971    fn update_credentials(&self, credentials: Credentials) {
972        tracing::debug!(
973            parent: self.shared.vault.monitor.span(),
974            access = ?credentials.secrets.access_mode(),
975            writer_id = ?credentials.writer_id,
976            "Repository access mode changed"
977        );
978
979        self.shared
980            .vault
981            .block_tracker
982            .set_request_mode(block_request_mode(&credentials.secrets));
983
984        *self.shared.credentials.write().unwrap() = credentials;
985        *self.worker_handle.lock().unwrap() = Some(spawn_worker(self.shared.clone()));
986    }
987}
988
989pub struct RepositoryHandle {
990    pub(crate) vault: Vault,
991}
992
993struct Shared {
994    vault: Vault,
995    credentials: BlockingRwLock<Credentials>,
996    branch_shared: BranchShared,
997}
998
999impl Shared {
1000    fn new(pool: db::Pool, credentials: Credentials, monitor: RepositoryMonitor) -> Self {
1001        let event_tx = EventSender::new(EVENT_CHANNEL_CAPACITY);
1002        let vault = Vault::new(*credentials.secrets.id(), event_tx, pool, monitor);
1003
1004        vault
1005            .block_tracker
1006            .set_request_mode(block_request_mode(&credentials.secrets));
1007
1008        Self {
1009            vault,
1010            credentials: BlockingRwLock::new(credentials),
1011            branch_shared: BranchShared::new(),
1012        }
1013    }
1014
1015    pub fn local_branch(&self) -> Result<Branch> {
1016        let credentials = self.credentials.read().unwrap();
1017
1018        Ok(self.make_branch(
1019            credentials.writer_id,
1020            credentials.secrets.keys().ok_or(Error::PermissionDenied)?,
1021        ))
1022    }
1023
1024    pub fn get_branch(&self, id: PublicKey) -> Result<Branch> {
1025        let credentials = self.credentials.read().unwrap();
1026        let keys = credentials.secrets.keys().ok_or(Error::PermissionDenied)?;
1027
1028        // Only the local branch is writable.
1029        let keys = if id == credentials.writer_id {
1030            keys
1031        } else {
1032            keys.read_only()
1033        };
1034
1035        Ok(self.make_branch(id, keys))
1036    }
1037
1038    fn make_branch(&self, id: PublicKey, keys: AccessKeys) -> Branch {
1039        Branch::new(
1040            id,
1041            self.vault.store().clone(),
1042            keys,
1043            self.branch_shared.clone(),
1044            self.vault.event_tx.clone(),
1045        )
1046    }
1047
1048    pub async fn load_branches(&self) -> Result<Vec<Branch>> {
1049        self.vault
1050            .store()
1051            .acquire_read()
1052            .await?
1053            .load_latest_approved_root_nodes()
1054            .err_into()
1055            .and_then(|root_node| future::ready(self.get_branch(root_node.proof.writer_id)))
1056            .try_collect()
1057            .await
1058    }
1059}
1060
1061fn spawn_worker(shared: Arc<Shared>) -> ScopedJoinHandle<()> {
1062    let span = shared.vault.monitor.span().clone();
1063    scoped_task::spawn(worker::run(shared).instrument(span))
1064}
1065
1066async fn report_sync_progress(vault: Vault) {
1067    let mut prev_progress = Progress { value: 0, total: 0 };
1068
1069    let events = stream::unfold(vault.event_tx.subscribe(), |mut rx| async move {
1070        match rx.recv().await {
1071            Ok(_) | Err(RecvError::Lagged(_)) => Some(((), rx)),
1072            Err(RecvError::Closed) => None,
1073        }
1074    });
1075    let events = Throttle::new(events, Duration::from_secs(1));
1076    let mut events = pin!(events);
1077
1078    while events.next().await.is_some() {
1079        let next_progress = match vault.store().sync_progress().await {
1080            Ok(progress) => progress,
1081            Err(error) => {
1082                tracing::error!("Failed to retrieve sync progress: {:?}", error);
1083                continue;
1084            }
1085        };
1086
1087        if next_progress != prev_progress {
1088            prev_progress = next_progress;
1089            tracing::debug!(
1090                "Sync progress: {} bytes ({:.1})",
1091                prev_progress * BLOCK_SIZE as u64,
1092                prev_progress.percent()
1093            );
1094        }
1095    }
1096}
1097
1098fn block_request_mode(secrets: &AccessSecrets) -> BlockRequestMode {
1099    if secrets.can_read() {
1100        BlockRequestMode::Lazy
1101    } else {
1102        BlockRequestMode::Greedy
1103    }
1104}