Skip to main content

radicle_cli/terminal/
cob.rs

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