Skip to main content

radicle_cli/terminal/
cob.rs

1use radicle::{
2    Profile,
3    cob::{
4        self,
5        cache::{MigrateCallback, MigrateProgress},
6        store::access::{ReadOnly, WriteAs},
7    },
8    crypto,
9    prelude::NodeId,
10    profile,
11    storage::ReadRepository,
12};
13use radicle_term as term;
14
15use crate::terminal;
16
17/// Hint to migrate COB database.
18pub const MIGRATION_HINT: &str = "run `rad cob migrate` to update your database";
19
20/// COB migration progress spinner.
21pub struct MigrateSpinner {
22    spinner: Option<term::Spinner>,
23}
24
25impl Default for MigrateSpinner {
26    /// Create a new [`MigrateSpinner`].
27    fn default() -> Self {
28        Self { spinner: None }
29    }
30}
31
32impl MigrateCallback for MigrateSpinner {
33    fn progress(&mut self, progress: MigrateProgress) {
34        self.spinner
35            .get_or_insert_with(|| term::spinner("Migration in progress.."))
36            .message(format!(
37                "Migration {}/{} in progress.. ({}%)",
38                progress.migration.current(),
39                progress.migration.total(),
40                progress.rows.percentage()
41            ));
42
43        if progress.is_done()
44            && let Some(spinner) = self.spinner.take()
45        {
46            spinner.finish()
47        }
48    }
49}
50
51/// Migrate functions.
52pub mod migrate {
53    use super::MigrateSpinner;
54
55    /// Display migration progress via a spinner.
56    #[must_use]
57    pub fn spinner() -> MigrateSpinner {
58        MigrateSpinner::default()
59    }
60}
61
62/// Return a read-only handle for the patches cache.
63pub fn patches<'a, Repo>(
64    profile: &Profile,
65    repository: &'a Repo,
66) -> Result<cob::patch::Cache<'a, Repo, ReadOnly, cob::cache::StoreReader>, anyhow::Error>
67where
68    Repo: ReadRepository + cob::Store<Namespace = NodeId>,
69{
70    profile.patches(repository).map_err(with_hint)
71}
72
73/// Return a read-write handle for the patches cache.
74/// Prefer this over [`radicle::profile::Home::patches_mut`],
75/// to obtain an error hint in case migrations must be run.
76pub fn patches_mut<'a, 'b, Repo, Signer: crypto::Signer>(
77    profile: &Profile,
78    repository: &'a Repo,
79    signer: &'b Signer,
80) -> Result<cob::patch::Cache<'a, Repo, WriteAs<'b, Signer>, cob::cache::StoreWriter>, anyhow::Error>
81where
82    Repo: ReadRepository + cob::Store<Namespace = NodeId>,
83{
84    profile.patches_mut(repository, signer).map_err(with_hint)
85}
86
87/// Return a read-only handle for the issues cache.
88pub fn issues<'a, Repo>(
89    profile: &Profile,
90    repository: &'a Repo,
91) -> Result<cob::issue::Cache<'a, Repo, ReadOnly, cob::cache::StoreReader>, anyhow::Error>
92where
93    Repo: ReadRepository + cob::Store<Namespace = NodeId>,
94{
95    profile.issues(repository).map_err(with_hint)
96}
97
98/// Return a read-write handle for the issues cache.
99/// Prefer this over [`radicle::profile::Home::issues_mut`],
100/// to obtain an error hint in case migrations must be run.
101pub fn issues_mut<'a, 'b, Repo, Signer: crypto::Signer>(
102    profile: &Profile,
103    repository: &'a Repo,
104    signer: &'b Signer,
105) -> Result<cob::issue::Cache<'a, Repo, WriteAs<'b, Signer>, cob::cache::StoreWriter>, anyhow::Error>
106where
107    Repo: ReadRepository + cob::Store<Namespace = NodeId>,
108{
109    profile.issues_mut(repository, signer).map_err(with_hint)
110}
111
112/// Adds a hint to the COB out-of-date database error.
113fn with_hint(e: profile::Error) -> anyhow::Error {
114    // There are many types that aren't `profile::Error::CobsCache`; specifying them all in an
115    // error path seems overly verbose with little value.
116    #[allow(clippy::wildcard_enum_match_arm)]
117    match e {
118        profile::Error::CobsCache(cob::cache::Error::OutOfDate) => {
119            terminal::args::Error::with_hint(e, MIGRATION_HINT).into()
120        }
121        e => anyhow::Error::from(e),
122    }
123}