Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add methods for repairing hot/cold repositories #255

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions crates/core/src/commands/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,32 @@ pub(crate) fn save_config<P, S>(
let dbe = DecryptBackend::new(repo.be.clone(), key);
// for hot/cold backend, this only saves the config to the cold repo.
_ = dbe.save_file_uncompressed(&new_config)?;
save_config_hot(repo, new_config, key)
}

/// Save a [`ConfigFile`] only to the hot part of a repository
///
/// # Type Parameters
///
/// * `P` - The progress bar type.
/// * `S` - The state the repository is in.
///
/// # Arguments
///
/// * `repo` - The repository to save the config to
/// * `new_config` - The config to save
/// * `key` - The key to encrypt the config with
///
/// # Errors
///
/// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json.
///
/// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed
pub(crate) fn save_config_hot<P, S>(
repo: &Repository<P, S>,
mut new_config: ConfigFile,
key: impl CryptoKey,
) -> RusticResult<()> {
if let Some(hot_be) = repo.be_hot.clone() {
// save config to hot repo
let dbe = DecryptBackend::new(hot_be.clone(), key);
Expand Down
15 changes: 8 additions & 7 deletions crates/core/src/commands/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub(crate) fn init<P, S>(
Ok((key, config))
}

/// Initialize a new repository with a given config.
/// Save a [`ConfigFile`] only to the hot part of a repository
///
/// # Type Parameters
///
Expand All @@ -69,14 +69,15 @@ pub(crate) fn init<P, S>(
///
/// # Arguments
///
/// * `repo` - The repository to initialize.
/// * `pass` - The password to encrypt the key with.
/// * `key_opts` - The options to create the key with.
/// * `config` - The config to use.
/// * `repo` - The repository to save the config to
/// * `new_config` - The config to save
/// * `key` - The key to encrypt the config with
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// * `key` - The key to encrypt the config with
/// * `key_opts` - The options to create the key with.

///
/// # Returns
/// # Errors
///
/// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// * [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`] - If the file could not be serialized to json.
/// * If the file could not be serialized to json.

///
/// The key used to encrypt the config.
/// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// [`CryptBackendErrorKind::SerializingToJsonByteVectorFailed`]: crate::error::CryptBackendErrorKind::SerializingToJsonByteVectorFailed

pub(crate) fn init_with_config<P, S>(
repo: &Repository<P, S>,
pass: &str,
Expand Down
1 change: 1 addition & 0 deletions crates/core/src/commands/repair.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod hotcold;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub mod hotcold;
pub mod hot_cold;

I think two separate words value an underscore.

pub mod index;
pub mod snapshots;
180 changes: 180 additions & 0 deletions crates/core/src/commands/repair/hotcold.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use std::collections::{BTreeMap, BTreeSet};

use log::{debug, info, warn};

use crate::{
backend::decrypt::DecryptReadBackend,
repofile::{BlobType, IndexFile, PackId},
repository::Open,
ErrorKind, FileType, Id, Progress, ProgressBars, ReadBackend, Repository, RusticError,
RusticResult, WriteBackend, ALL_FILE_TYPES,
};

pub(crate) fn repair_hotcold<P: ProgressBars, S>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub(crate) fn repair_hotcold<P: ProgressBars, S>(
pub(crate) fn repair_hot_cold<P: ProgressBars, S>(

same

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of documentation for this new function would be lovely as well 🚀

repo: &Repository<P, S>,
dry_run: bool,
) -> RusticResult<()> {
for file_type in ALL_FILE_TYPES {
if file_type != FileType::Pack {
correct_missing_files(repo, file_type, |_| true, dry_run)?;

Check warning on line 19 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L17-L19

Added lines #L17 - L19 were not covered by tests
}
}
Ok(())

Check warning on line 22 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L22

Added line #L22 was not covered by tests
}

pub(crate) fn repair_hotcold_packs<P: ProgressBars, S: Open>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
pub(crate) fn repair_hotcold_packs<P: ProgressBars, S: Open>(
pub(crate) fn repair_hot_cold_packs<P: ProgressBars, S: Open>(

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of documentation for this new function would be lovely as well 🚀

repo: &Repository<P, S>,
dry_run: bool,
) -> RusticResult<()> {
let tree_packs = get_tree_packs(repo)?;

Check warning on line 29 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L29

Added line #L29 was not covered by tests
correct_missing_files(
repo,
FileType::Pack,
|id| tree_packs.contains(&PackId::from(*id)),
dry_run,

Check warning on line 34 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L31-L34

Added lines #L31 - L34 were not covered by tests
)
}

pub(crate) fn correct_missing_files<P: ProgressBars, S>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of documentation for this new function would be lovely as well 🚀

repo: &Repository<P, S>,
file_type: FileType,
is_relevant: impl Fn(&Id) -> bool,
dry_run: bool,
) -> RusticResult<()> {
let Some(repo_hot) = &repo.be_hot else {
return Err(RusticError::new(
ErrorKind::Repository,
"Repository is no hot/cold repository.",

Check warning on line 47 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L44-L47

Added lines #L44 - L47 were not covered by tests
));
};

let (missing_hot, missing_hot_size, missing_cold, missing_cold_size) =
get_missing_files(repo, file_type, is_relevant)?;

Check warning on line 52 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L51-L52

Added lines #L51 - L52 were not covered by tests

// copy missing files from hot to cold repo
if !missing_cold.is_empty() {
if dry_run {
info!(
"would have copied {} hot {file_type:?} files to cold",
missing_cold.len()

Check warning on line 59 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L55-L59

Added lines #L55 - L59 were not covered by tests
);
debug!("files: {missing_cold:?}");

Check warning on line 61 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L61

Added line #L61 was not covered by tests
} else {
let p = repo
.pb
.progress_bytes(format!("copying missing cold {file_type:?} files..."));
p.set_length(missing_cold_size);
copy(missing_cold, file_type, repo_hot, &repo.be_cold)?;
p.finish();

Check warning on line 68 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L63-L68

Added lines #L63 - L68 were not covered by tests
}
}

if !missing_hot.is_empty() {
if dry_run {
info!(
"would have copied {} cold {file_type:?} files to hot",
missing_hot.len()

Check warning on line 76 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L72-L76

Added lines #L72 - L76 were not covered by tests
);
debug!("files: {missing_hot:?}");

Check warning on line 78 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L78

Added line #L78 was not covered by tests
} else {
// TODO: warm-up
// copy missing files from cold to hot repo
let p = repo
.pb
.progress_bytes(format!("copying missing hot {file_type:?} files..."));
p.set_length(missing_hot_size);
copy(missing_hot, file_type, &repo.be_cold, repo_hot)?;
p.finish();

Check warning on line 87 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L82-L87

Added lines #L82 - L87 were not covered by tests
}
}

Ok(())

Check warning on line 91 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L91

Added line #L91 was not covered by tests
}

fn copy(
files: Vec<Id>,
file_type: FileType,
from: &impl ReadBackend,
to: &impl WriteBackend,
) -> RusticResult<()> {
for id in files {
let file = from.read_full(file_type, &id)?;
to.write_bytes(file_type, &id, false, file)?;
}
Ok(())
}

pub(crate) fn get_tree_packs<P: ProgressBars, S: Open>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of documentation for this new function would be lovely as well 🚀

repo: &Repository<P, S>,
) -> RusticResult<BTreeSet<PackId>> {
let p = repo.pb.progress_counter("reading index...");
let mut tree_packs = BTreeSet::new();
for index in repo.dbe().stream_all::<IndexFile>(&p)? {
let index = index?.1;
for (p, _) in index.all_packs() {
let blob_type = p.blob_type();
if blob_type == BlobType::Tree {
_ = tree_packs.insert(p.id);

Check warning on line 117 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L110-L117

Added lines #L110 - L117 were not covered by tests
}
}
}
Ok(tree_packs)

Check warning on line 121 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L121

Added line #L121 was not covered by tests
}

pub(crate) fn get_missing_files<P: ProgressBars, S>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit of documentation for this new function would be lovely as well 🚀

repo: &Repository<P, S>,
file_type: FileType,
is_relevant: impl Fn(&Id) -> bool,
) -> RusticResult<(Vec<Id>, u64, Vec<Id>, u64)> {
let Some(repo_hot) = &repo.be_hot else {
return Err(RusticError::new(
ErrorKind::Repository,
"Repository is no hot/cold repository.",

Check warning on line 132 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L129-L132

Added lines #L129 - L132 were not covered by tests
));
};

let p = repo
.pb
.progress_spinner(format!("listing hot {file_type:?} files..."));
let hot_files: BTreeMap<_, _> = repo_hot.list_with_size(file_type)?.into_iter().collect();
p.finish();

Check warning on line 140 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L136-L140

Added lines #L136 - L140 were not covered by tests

let p = repo
.pb
.progress_spinner(format!("listing cold {file_type:?} files..."));
let cold_files: BTreeMap<_, _> = repo
.be_cold
.list_with_size(file_type)?

Check warning on line 147 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L142-L147

Added lines #L142 - L147 were not covered by tests
.into_iter()
.collect();
p.finish();

Check warning on line 150 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L150

Added line #L150 was not covered by tests

let common: BTreeSet<_> = hot_files

Check warning on line 152 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L152

Added line #L152 was not covered by tests
.iter()
.filter_map(|(id, size_hot)| match cold_files.get(id) {
Some(size_cold) if size_cold == size_hot => Some(*id),
Some(size_cold) => {
warn!("sizes mismatch: type {file_type:?}, id: {id}, size hot: {size_hot}, size cold: {size_cold}. Ignoring...");
None

Check warning on line 158 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L154-L158

Added lines #L154 - L158 were not covered by tests
}
None => None,

Check warning on line 160 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L160

Added line #L160 was not covered by tests
})
.collect();

let retain = |files: BTreeMap<_, _>| {
let mut retain_size: u64 = 0;
let only: Vec<_> = files

Check warning on line 166 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L164-L166

Added lines #L164 - L166 were not covered by tests
.into_iter()
.filter(|(id, _)| !common.contains(id) && is_relevant(id))
.map(|(id, size)| {
retain_size += u64::from(size);
id

Check warning on line 171 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L168-L171

Added lines #L168 - L171 were not covered by tests
})
.collect();
(only, retain_size)

Check warning on line 174 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L174

Added line #L174 was not covered by tests
};

let (cold_only, cold_only_size) = retain(cold_files);
let (hot_only, hot_only_size) = retain(hot_files);
Ok((cold_only, cold_only_size, hot_only, hot_only_size))

Check warning on line 179 in crates/core/src/commands/repair/hotcold.rs

View check run for this annotation

Codecov / codecov/patch

crates/core/src/commands/repair/hotcold.rs#L177-L179

Added lines #L177 - L179 were not covered by tests
}
Loading
Loading