radicle: Implement migration callback mechanism

Add the ability to have native migrations and progress callback
functions in migration code for the COB cache.
This commit is contained in:
cloudhead 2024-11-14 17:21:33 +01:00
parent 088339850b
commit 6c8ee4330e
No known key found for this signature in database
31 changed files with 183 additions and 83 deletions

View File

@ -9,6 +9,7 @@ use radicle::issue::cache::Issues as _;
use radicle::patch::cache::Patches as _;
use thiserror::Error;
use radicle::cob::migrate;
use radicle::git::raw;
use radicle::identity::doc;
use radicle::identity::doc::{DocError, RepoId};
@ -182,8 +183,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
info.push([term::format::bold(proj.name()).into()]);
info.push([term::format::italic(proj.description()).into()]);
let issues = profile.issues(&repo)?.counts()?;
let patches = profile.patches(&repo)?.counts()?;
let issues = profile.issues(&repo, migrate::ignore)?.counts()?;
let patches = profile.patches(&repo, migrate::ignore)?.counts()?;
info.push([term::Line::spaced([
term::format::tertiary(issues.open).into(),

View File

@ -5,6 +5,7 @@ use anyhow::anyhow;
use chrono::prelude::*;
use nonempty::NonEmpty;
use radicle::cob;
use radicle::cob::migrate;
use radicle::cob::Op;
use radicle::identity::Identity;
use radicle::issue::cache::Issues;
@ -180,13 +181,13 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let oid = &oid.resolve(&repo.backend)?;
if options.type_name == cob::patch::TYPENAME.clone() {
let patches = profile.patches(&repo)?;
let patches = profile.patches(&repo, migrate::ignore)?;
let Some(patch) = patches.get(oid)? else {
anyhow::bail!(cob::store::Error::NotFound(options.type_name, *oid))
};
serde_json::to_writer_pretty(std::io::stdout(), &patch)?
} else if options.type_name == cob::issue::TYPENAME.clone() {
let issues = profile.issues(&repo)?;
let issues = profile.issues(&repo, migrate::ignore)?;
let Some(issue) = issues.get(oid)? else {
anyhow::bail!(cob::store::Error::NotFound(options.type_name, *oid))
};

View File

@ -6,6 +6,7 @@ use anyhow::anyhow;
use git_ref_format::Qualified;
use localtime::LocalTime;
use radicle::cob::migrate;
use radicle::cob::TypedId;
use radicle::identity::Identity;
use radicle::issue::cache::Issues as _;
@ -271,8 +272,8 @@ where
let (_, head) = repo.head()?;
let doc = repo.identity_doc()?;
let proj = doc.project()?;
let issues = profile.issues(&repo)?;
let patches = profile.patches(&repo)?;
let issues = profile.issues(&repo, migrate::ignore)?;
let patches = profile.patches(&repo, migrate::ignore)?;
let mut notifs = notifs.by_repo(&rid, sort_by.field)?.collect::<Vec<_>>();
if !sort_by.reverse {
@ -535,7 +536,7 @@ fn show(
match n.kind {
NotificationKind::Cob { typed_id } if typed_id.is_issue() => {
let issues = profile.issues(&repo)?;
let issues = profile.issues(&repo, migrate::ignore)?;
let issue = issues.get(&typed_id.id)?.unwrap();
term::issue::show(
@ -546,7 +547,7 @@ fn show(
)?;
}
NotificationKind::Cob { typed_id } if typed_id.is_patch() => {
let patches = profile.patches(&repo)?;
let patches = profile.patches(&repo, migrate::ignore)?;
let patch = patches.get(&typed_id.id)?.unwrap();
term::patch::show(&patch, &typed_id.id, false, &repo, None, profile)?;

View File

@ -8,9 +8,8 @@ use std::str::FromStr;
use anyhow::{anyhow, Context as _};
use radicle::cob::common::{Label, Reaction};
use radicle::cob::issue;
use radicle::cob::issue::{CloseReason, State};
use radicle::cob::thread;
use radicle::cob::{issue, migrate, thread};
use radicle::crypto::Signer;
use radicle::issue::cache::Issues as _;
use radicle::prelude::{Did, RepoId};
@ -461,7 +460,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
| Operation::Comment { .. }
);
let mut issues = profile.issues_mut(&repo)?;
let mut issues = profile.issues_mut(&repo, migrate::ignore)?;
match options.op {
Operation::Edit {

View File

@ -1,5 +1,6 @@
use std::ops::ControlFlow;
use radicle::cob::migrate;
use radicle::issue::IssueId;
use radicle::storage::git::Repository;
use radicle::storage::ReadStorage as _;
@ -37,8 +38,9 @@ pub fn run(mode: CacheMode, profile: &Profile) -> anyhow::Result<()> {
}
fn cache(id: Option<IssueId>, repository: &Repository, profile: &Profile) -> anyhow::Result<()> {
let mut issues = profile.issues_mut(repository)?;
let mut issues = profile.issues_mut(repository, migrate::ignore)?;
// FIXME: Should this change?
match id {
Some(id) => {
issues.write(&id)?;
@ -48,7 +50,7 @@ fn cache(id: Option<IssueId>, repository: &Repository, profile: &Profile) -> any
match result {
Ok((id, _)) => term::success!(
"Successfully cached issue {id} ({}/{})",
progress.seen(),
progress.current(),
progress.total()
),
Err(e) => term::warning(format!("Failed to retrieve issue: {e}")),

View File

@ -37,7 +37,7 @@ use std::ffi::OsString;
use anyhow::anyhow;
use radicle::cob::patch::PatchId;
use radicle::cob::{patch, Label};
use radicle::cob::{migrate, patch, Label};
use radicle::git::RefString;
use radicle::patch::cache::Patches as _;
use radicle::storage::git::transport;
@ -928,7 +928,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
label::run(&patch_id, add, delete, &profile, &repository)?;
}
Operation::Set { patch_id, remote } => {
let patches = profile.patches(&repository)?;
let patches = profile.patches(&repository, migrate::ignore)?;
let patch_id = patch_id.resolve(&repository.backend)?;
let patch = patches
.get(&patch_id)?

View File

@ -1,5 +1,6 @@
use super::*;
use radicle::cob::migrate;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@ -10,7 +11,7 @@ pub fn run(
repository: &Repository,
) -> anyhow::Result<()> {
let signer = term::signer(profile)?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
let Ok(mut patch) = patches.get_mut(patch_id) else {
anyhow::bail!("Patch `{patch_id}` not found");
};

View File

@ -2,6 +2,7 @@ use std::collections::BTreeSet;
use super::*;
use radicle::cob::migrate;
use radicle::prelude::Did;
use radicle::storage::git::Repository;
@ -15,7 +16,7 @@ pub fn run(
repository: &Repository,
) -> anyhow::Result<()> {
let signer = term::signer(profile)?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
let Ok(mut patch) = patches.get_mut(patch_id) else {
anyhow::bail!("Patch `{patch_id}` not found");
};

View File

@ -1,5 +1,6 @@
use std::ops::ControlFlow;
use radicle::cob::migrate;
use radicle::patch::PatchId;
use radicle::storage::git::Repository;
use radicle::storage::ReadStorage as _;
@ -37,7 +38,7 @@ pub fn run(mode: CacheMode, profile: &Profile) -> anyhow::Result<()> {
}
fn cache(id: Option<PatchId>, repository: &Repository, profile: &Profile) -> anyhow::Result<()> {
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
match id {
Some(id) => {
@ -48,7 +49,7 @@ fn cache(id: Option<PatchId>, repository: &Repository, profile: &Profile) -> any
match result {
Ok((id, _)) => term::success!(
"Successfully cached patch {id} ({}/{})",
progress.seen(),
progress.current(),
progress.total()
),
Err(e) => term::warning(format!("Failed to retrieve patch: {e}")),

View File

@ -1,6 +1,7 @@
use anyhow::anyhow;
use git_ref_format::Qualified;
use radicle::cob::migrate;
use radicle::cob::patch;
use radicle::cob::patch::RevisionId;
use radicle::git::RefString;
@ -38,7 +39,7 @@ pub fn run(
profile: &Profile,
opts: Options,
) -> anyhow::Result<()> {
let patches = profile.patches(stored)?;
let patches = profile.patches(stored, migrate::ignore)?;
let patch = patches
.get(patch_id)?
.ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?;

View File

@ -1,8 +1,8 @@
use super::*;
use radicle::cob;
use radicle::cob::patch;
use radicle::cob::thread::CommentId;
use radicle::cob::{migrate, patch};
use radicle::patch::ByRevision;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@ -20,7 +20,7 @@ pub fn run(
profile: &Profile,
) -> anyhow::Result<()> {
let signer = term::signer(profile)?;
let mut patches = profile.patches_mut(repo)?;
let mut patches = profile.patches_mut(repo, migrate::ignore)?;
let revision_id = revision_id.resolve::<cob::EntryId>(&repo.backend)?;
let ByRevision {

View File

@ -1,3 +1,4 @@
use radicle::cob::migrate;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@ -5,7 +6,7 @@ use super::*;
pub fn run(patch_id: &PatchId, profile: &Profile, repository: &Repository) -> anyhow::Result<()> {
let signer = &term::signer(profile)?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
patches.remove(patch_id, signer)?;
Ok(())

View File

@ -1,6 +1,6 @@
use std::process;
use radicle::cob::patch;
use radicle::cob::{migrate, patch};
use radicle::storage::git::Repository;
use super::*;
@ -11,7 +11,7 @@ pub fn run(
stored: &Repository,
profile: &Profile,
) -> anyhow::Result<()> {
let patches = profile.patches(stored)?;
let patches = profile.patches(stored, migrate::ignore)?;
let Some(patch) = patches.get(patch_id)? else {
anyhow::bail!("Patch `{patch_id}` not found");
};

View File

@ -1,6 +1,7 @@
use super::*;
use radicle::cob::{self, patch};
use radicle::cob;
use radicle::cob::{migrate, patch};
use radicle::crypto;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@ -15,7 +16,7 @@ pub fn run(
repository: &Repository,
) -> anyhow::Result<()> {
let signer = term::signer(profile)?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
let Ok(patch) = patches.get_mut(patch_id) else {
anyhow::bail!("Patch `{patch_id}` not found");
};

View File

@ -1,5 +1,6 @@
use super::*;
use radicle::cob::migrate;
use radicle::storage::git::Repository;
use crate::terminal as term;
@ -12,7 +13,7 @@ pub fn run(
repository: &Repository,
) -> anyhow::Result<()> {
let signer = term::signer(profile)?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
let Ok(mut patch) = patches.get_mut(patch_id) else {
anyhow::bail!("Patch `{patch_id}` not found");
};

View File

@ -1,7 +1,7 @@
use std::collections::{BTreeMap, BTreeSet};
use radicle::cob::patch;
use radicle::cob::patch::{Patch, PatchId};
use radicle::cob::{migrate, patch};
use radicle::patch::cache::Patches as _;
use radicle::prelude::*;
use radicle::profile::Profile;
@ -21,7 +21,7 @@ pub fn run(
repository: &Repository,
profile: &Profile,
) -> anyhow::Result<()> {
let patches = profile.patches(repository)?;
let patches = profile.patches(repository, migrate::ignore)?;
let mut all = Vec::new();
let iter = match filter {

View File

@ -1,5 +1,6 @@
use super::*;
use radicle::cob::migrate;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@ -10,7 +11,7 @@ pub fn run(
repository: &Repository,
) -> anyhow::Result<bool> {
let signer = term::signer(profile)?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
let Ok(mut patch) = patches.get_mut(patch_id) else {
anyhow::bail!("Patch `{patch_id}` not found");
};

View File

@ -1,4 +1,4 @@
use radicle::cob::patch;
use radicle::cob::{migrate, patch};
use radicle::git::Oid;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@ -13,7 +13,7 @@ pub fn run(
repository: &Repository,
) -> anyhow::Result<()> {
let signer = &term::signer(profile)?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
let revision_id = revision_id.resolve::<Oid>(&repository.backend)?;
let patch::ByRevision {

View File

@ -1,4 +1,5 @@
use anyhow::anyhow;
use radicle::cob::migrate;
use radicle::cob::thread::CommentId;
use radicle::patch::{self, PatchId};
use radicle::patch::{cache::Patches as _, ReviewId};
@ -15,7 +16,7 @@ pub fn resolve(
profile: &Profile,
) -> anyhow::Result<()> {
let signer = term::signer(profile)?;
let mut patches = profile.patches_mut(repo)?;
let mut patches = profile.patches_mut(repo, migrate::ignore)?;
let patch = patches
.get(&patch_id)?
.ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?;
@ -32,7 +33,7 @@ pub fn unresolve(
profile: &Profile,
) -> anyhow::Result<()> {
let signer = term::signer(profile)?;
let mut patches = profile.patches_mut(repo)?;
let mut patches = profile.patches_mut(repo, migrate::ignore)?;
let patch = patches
.get(&patch_id)?
.ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?;

View File

@ -3,6 +3,7 @@ mod builder;
use anyhow::{anyhow, Context};
use radicle::cob::migrate;
use radicle::cob::patch::{PatchId, RevisionId, Verdict};
use radicle::git;
use radicle::prelude::*;
@ -61,7 +62,7 @@ pub fn run(
"couldn't load repository {} from local state",
repository.id
))?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
let mut patch = patches
.get_mut(&patch_id)
.context(format!("couldn't find patch {patch_id} locally"))?;

View File

@ -1,6 +1,6 @@
use std::process;
use radicle::cob::patch;
use radicle::cob::{migrate, patch};
use radicle::git;
use radicle::storage::git::Repository;
@ -32,7 +32,7 @@ pub fn run(
stored: &Repository,
workdir: Option<&git::raw::Repository>,
) -> anyhow::Result<()> {
let patches = profile.patches(stored)?;
let patches = profile.patches(stored, migrate::ignore)?;
let Some(patch) = patches.get(patch_id).map_err(|e| Error::WithHint {
err: e.into(),
hint: "reset the cache with `rad patch cache` and try again",

View File

@ -1,4 +1,4 @@
use radicle::cob::patch;
use radicle::cob::{migrate, patch};
use radicle::git;
use radicle::prelude::*;
use radicle::storage::git::Repository;
@ -19,7 +19,7 @@ pub fn run(
let head_branch = try_branch(workdir.head()?)?;
let (_, target_oid) = get_merge_target(repository, &head_branch)?;
let mut patches = profile.patches_mut(repository)?;
let mut patches = profile.patches_mut(repository, migrate::ignore)?;
let Ok(mut patch) = patches.get_mut(&patch_id) else {
anyhow::bail!("Patch `{patch_id}` not found");
};

View File

@ -3,6 +3,7 @@ use std::path::Path;
use localtime::LocalDuration;
use localtime::LocalTime;
use radicle::cob::migrate;
use radicle::git;
use radicle::issue::cache::Issues as _;
use radicle::node::address;
@ -95,8 +96,8 @@ pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
for repo in storage.repositories()? {
let repo = storage.repository(repo.rid)?;
let issues = profile.issues(&repo)?.counts()?;
let patches = profile.patches(&repo)?.counts()?;
let issues = profile.issues(&repo, migrate::ignore)?.counts()?;
let patches = profile.patches(&repo, migrate::ignore)?.counts()?;
stats.local.issues += issues.total();
stats.local.patches += patches.total();

View File

@ -3,6 +3,7 @@ use radicle::profile;
use thiserror::Error;
use radicle::cob;
use radicle::cob::migrate;
use radicle::git;
use radicle::storage::git::transport::local::Url;
use radicle::storage::ReadRepository;
@ -87,7 +88,7 @@ fn patch_refs<R: ReadRepository + cob::Store + 'static>(
profile: &Profile,
stored: &R,
) -> Result<(), Error> {
let patches = profile.patches(stored)?;
let patches = profile.patches(stored, migrate::ignore)?;
for patch in patches.list()? {
let Ok((id, patch)) = patch else {
// Ignore patches that fail to decode.

View File

@ -9,8 +9,8 @@ use thiserror::Error;
use radicle::cob;
use radicle::cob::object::ParseObjectId;
use radicle::cob::patch;
use radicle::cob::patch::cache::Patches as _;
use radicle::cob::{migrate, patch};
use radicle::crypto::Signer;
use radicle::explorer::ExplorerResource;
use radicle::git::canonical;
@ -227,7 +227,7 @@ pub fn run(
&nid,
&working,
stored,
profile.patches_mut(stored)?,
profile.patches_mut(stored, migrate::ignore)?,
&signer,
profile,
opts.clone(),
@ -247,7 +247,7 @@ pub fn run(
&nid,
&working,
stored,
profile.patches_mut(stored)?,
profile.patches_mut(stored, migrate::ignore)?,
&signer,
opts.clone(),
)
@ -320,7 +320,7 @@ pub fn run(
&nid,
&working,
stored,
profile.patches_mut(stored)?,
profile.patches_mut(stored, migrate::ignore)?,
&signer,
)
}

View File

@ -2,6 +2,7 @@ use std::collections::HashSet;
use std::env;
use anyhow::{anyhow, bail};
use radicle::cob::migrate;
use radicle::cob::patch::{PatchId, RevisionId};
use radicle::git::Oid;
use radicle::storage::ReadStorage;
@ -26,7 +27,7 @@ fn main() -> anyhow::Result<()> {
let profile = radicle::Profile::load()?;
let (working, rid) = radicle::rad::cwd()?;
let stored = profile.storage.repository(rid)?;
let mut patches = profile.patches_mut(&stored)?;
let mut patches = profile.patches_mut(&stored, migrate::ignore)?;
let mut patch = patches.get_mut(&pid)?;
if patch.is_merged() {

View File

@ -12,6 +12,7 @@ pub mod thread;
#[cfg(test)]
pub mod test;
pub use cache::{migrate, MigrateCallback};
pub use common::*;
pub use op::{ActorId, Op};
pub use radicle_cob::{

View File

@ -23,7 +23,58 @@ const DB_WRITE_TIMEOUT: time::Duration = time::Duration::from_secs(6);
/// Database migrations.
/// The first migration is the creation of the initial tables.
const MIGRATIONS: &[&str] = &[include_str!("cache/migrations/1.sql")];
const MIGRATIONS: &[Migration] = &[Migration::Sql(include_str!("cache/migrations/1.sql"))];
/// Function signature for native migrations.
type MigrateFn = fn(&sql::Connection, Progress, &dyn MigrateCallback) -> Result<usize, Error>;
/// A database migration.
enum Migration {
/// Migration written in SQL.
Sql(&'static str),
/// Migration function written in Rust.
#[allow(dead_code)]
Native(MigrateFn),
}
/// Something that can process migration progress.
pub trait MigrateCallback {
/// A migration has progressed.
/// The first [`Progress`] parameter refers to the progress within the list of migrations.
/// The second [`Progress`] parameter refers to the progress within the current migration.
fn progress(&mut self, migration: Progress, item: Progress) -> Result<bool, Error>;
}
impl<F> MigrateCallback for F
where
F: Fn(Progress, Progress) -> Result<bool, Error>,
{
fn progress(&mut self, migration: Progress, item: Progress) -> Result<bool, Error> {
(self)(migration, item)
}
}
/// Migration functions that implement [`MigrateCallback`].
pub mod migrate {
use super::*;
/// Log progress via installed logger at "info" level.
pub fn log(migration: Progress, item: Progress) -> Result<bool, Error> {
log::info!(
target: "db",
"Migration {}/{} in progress.. ({}%)",
migration.current() + 1,
migration.total(),
item.percentage()
);
Ok(true)
}
/// Ignore progress, just migrate.
pub fn ignore(_migration: Progress, _item: Progress) -> Result<bool, Error> {
Ok(true)
}
}
#[derive(Error, Debug)]
pub enum Error {
@ -97,7 +148,7 @@ impl Store<Write> {
pub fn open<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
let mut db = sql::Connection::open_thread_safe(path)?;
db.set_busy_timeout(DB_WRITE_TIMEOUT.as_millis() as usize)?;
migrate(&db)?;
migrate(&db, migrate::ignore)?;
Ok(Self {
db: Arc::new(db),
@ -108,7 +159,7 @@ impl Store<Write> {
/// Create a new in-memory database.
pub fn memory() -> Result<Self, Error> {
let db = Arc::new(sql::Connection::open_thread_safe(":memory:")?);
migrate(&db)?;
migrate(&db, migrate::ignore)?;
Ok(Self {
db,
@ -164,17 +215,34 @@ fn bump(db: &sql::Connection) -> Result<usize, Error> {
}
/// Migrate the database to the latest schema.
fn migrate(db: &sql::Connection) -> Result<usize, Error> {
fn migrate<M>(db: &sql::Connection, mut callback: M) -> Result<usize, Error>
where
M: MigrateCallback,
{
let mut version = version(db)?;
for (i, migration) in MIGRATIONS.iter().enumerate() {
if i >= version {
transaction(db, |db| {
db.execute(migration)?;
version = bump(db)?;
let total = MIGRATIONS.len();
Ok::<_, Error>(())
})?;
for (i, migration) in MIGRATIONS.iter().enumerate() {
if i < version {
continue;
}
transaction(db, |db| {
match migration {
Migration::Sql(query) => {
db.execute(query)?;
callback.progress(
Progress { total, current: i },
Progress::done(db.change_count()),
)?;
}
Migration::Native(migrate) => {
migrate(db, Progress { total, current: i }, &callback)?;
}
}
version = bump(db)?;
Ok::<_, Error>(())
})?;
}
Ok(version)
}
@ -283,20 +351,28 @@ impl<T> Remove<T> for NoCache {
///
/// See [`crate::cob::issue::Cache::write_all`] and
/// [`crate::cob::patch::Cache::write_all`].
pub struct WriteAllProgress {
pub struct Progress {
current: usize,
total: usize,
seen: usize,
}
impl WriteAllProgress {
impl Progress {
/// Create a new progress tracker with the given `total` amount.
pub fn new(total: usize) -> Self {
Self { total, seen: 0 }
Self { current: 0, total }
}
/// Increment the [`WriteAllProgress::seen`] progress.
/// Create a new progress tracker that is "done".
pub fn done(total: usize) -> Self {
Self {
current: total,
total,
}
}
/// Increment the [`Progress::current`] progress.
pub fn inc(&mut self) {
self.seen += 1;
self.current += 1;
}
/// Return the `total` amount.
@ -304,9 +380,9 @@ impl WriteAllProgress {
self.total
}
/// Return the `seen` amount.
pub fn seen(&self) -> usize {
self.seen
/// Return the `current` amount.
pub fn current(&self) -> usize {
self.current
}
/// Return the percentage of the progress made.
@ -315,6 +391,6 @@ impl WriteAllProgress {
///
/// If the `total` provided is `0`.
pub fn percentage(&self) -> f32 {
(self.seen as f32 / self.total as f32) * 100.0
(self.current as f32 / self.total as f32) * 100.0
}
}

View File

@ -143,10 +143,7 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
/// or break from the process.
pub fn write_all(
&mut self,
on_issue: impl Fn(
&Result<(IssueId, Issue), store::Error>,
&cache::WriteAllProgress,
) -> ControlFlow<()>,
on_issue: impl Fn(&Result<(IssueId, Issue), store::Error>, &cache::Progress) -> ControlFlow<()>,
) -> Result<(), super::Error>
where
R: ReadRepository + cob::Store,
@ -158,7 +155,7 @@ impl<'a, R, C> Cache<super::Issues<'a, R>, C> {
.map_err(|e| super::Error::CacheRemoveAll { err: e.into() })?;
let issues = self.store.all()?;
let mut progress = cache::WriteAllProgress::new(issues.len());
let mut progress = cache::Progress::new(issues.len());
for issue in self.store.all()? {
progress.inc();
match on_issue(&issue, &progress) {

View File

@ -210,10 +210,7 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
/// or break from the process.
pub fn write_all(
&mut self,
callback: impl Fn(
&Result<(PatchId, Patch), store::Error>,
&cache::WriteAllProgress,
) -> ControlFlow<()>,
callback: impl Fn(&Result<(PatchId, Patch), store::Error>, &cache::Progress) -> ControlFlow<()>,
) -> Result<(), super::Error>
where
R: ReadRepository + cob::Store,
@ -225,7 +222,7 @@ impl<'a, R, C> Cache<super::Patches<'a, R>, C> {
.map_err(|e| super::Error::CacheRemoveAll { err: e.into() })?;
let patches = self.store.all()?;
let mut progress = cache::WriteAllProgress::new(patches.len());
let mut progress = cache::Progress::new(patches.len());
for patch in self.store.all()? {
progress.inc();
match callback(&patch, &progress) {

View File

@ -562,58 +562,70 @@ impl Home {
}
/// Return a read-only handle for the issues cache.
pub fn issues<'a, R>(
pub fn issues<'a, R, M>(
&self,
repository: &'a R,
_migrate: M,
) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreReader>, Error>
where
M: cob::MigrateCallback,
R: ReadRepository + cob::Store,
{
let path = self.cobs().join(cob::cache::COBS_DB_FILE);
let db = cob::cache::Store::reader(path)?;
let store = cob::issue::Issues::open(repository)?;
Ok(cob::issue::Cache::reader(store, db))
}
/// Return a read-write handle for the issues cache.
pub fn issues_mut<'a, R>(
pub fn issues_mut<'a, R, M>(
&self,
repository: &'a R,
_migrate: M,
) -> Result<cob::issue::Cache<cob::issue::Issues<'a, R>, cob::cache::StoreWriter>, Error>
where
M: cob::MigrateCallback,
R: ReadRepository + cob::Store,
{
let path = self.cobs().join(cob::cache::COBS_DB_FILE);
let db = cob::cache::Store::open(path)?;
let store = cob::issue::Issues::open(repository)?;
Ok(cob::issue::Cache::open(store, db))
}
/// Return a read-only handle for the patches cache.
pub fn patches<'a, R>(
pub fn patches<'a, R, M>(
&self,
repository: &'a R,
_migrate: M,
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreReader>, Error>
where
M: cob::MigrateCallback,
R: ReadRepository + cob::Store,
{
let path = self.cobs().join(cob::cache::COBS_DB_FILE);
let db = cob::cache::Store::reader(path)?;
let store = cob::patch::Patches::open(repository)?;
Ok(cob::patch::Cache::reader(store, db))
}
/// Return a read-write handle for the patches cache.
pub fn patches_mut<'a, R>(
pub fn patches_mut<'a, R, M>(
&self,
repository: &'a R,
_migrate: M,
) -> Result<cob::patch::Cache<cob::patch::Patches<'a, R>, cob::cache::StoreWriter>, Error>
where
M: cob::MigrateCallback,
R: ReadRepository + cob::Store,
{
let path = self.cobs().join(cob::cache::COBS_DB_FILE);
let db = cob::cache::Store::open(path)?;
let store = cob::patch::Patches::open(repository)?;
Ok(cob::patch::Cache::open(store, db))
}
}