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