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    pub fn spinner() -> MigrateSpinner {
55        MigrateSpinner::default()
56    }
57}
58
59/// Return a read-only handle for the patches cache.
60pub fn patches<'a, R>(
61    profile: &Profile,
62    repository: &'a R,
63) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreReader>, anyhow::Error>
64where
65    R: ReadRepository + cob::Store<Namespace = NodeId>,
66{
67    profile.patches(repository).map_err(with_hint)
68}
69
70/// Return a read-write handle for the patches cache.
71pub fn patches_mut<'a, R>(
72    profile: &Profile,
73    repository: &'a R,
74) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreWriter>, anyhow::Error>
75where
76    R: ReadRepository + cob::Store<Namespace = NodeId>,
77{
78    profile.patches_mut(repository).map_err(with_hint)
79}
80
81/// Return a read-only handle for the issues cache.
82pub fn issues<'a, R>(
83    profile: &Profile,
84    repository: &'a R,
85) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreReader>, anyhow::Error>
86where
87    R: ReadRepository + cob::Store<Namespace = NodeId>,
88{
89    profile.issues(repository).map_err(with_hint)
90}
91
92/// Return a read-write handle for the issues cache.
93pub fn issues_mut<'a, R>(
94    profile: &Profile,
95    repository: &'a R,
96) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreWriter>, anyhow::Error>
97where
98    R: ReadRepository + cob::Store<Namespace = NodeId>,
99{
100    profile.issues_mut(repository).map_err(with_hint)
101}
102
103/// Adds a hint to the COB out-of-date database error.
104fn with_hint(e: profile::Error) -> anyhow::Error {
105    match e {
106        profile::Error::CobsCache(cob::cache::Error::OutOfDate) => {
107            anyhow::Error::from(terminal::args::Error::WithHint {
108                err: e.into(),
109                hint: MIGRATION_HINT,
110            })
111        }
112        e => anyhow::Error::from(e),
113    }
114}