use std::collections::HashSet;
use std::path::PathBuf;
use clap;
use output::Output;
use rand;
use rand::Rng;
use rustc_serialize::hex::ToHex;
use ::convert::utils::*;
use ::misc::*;
use ::zbackup::data::*;
use ::zbackup::disk_format::*;
use ::zbackup::repository::*;
pub fn gc_indexes (
output: & Output,
arguments: & GcIndexesArguments,
) -> Result <bool, String> {
let repository =
string_result_with_prefix (
|| format! (
"Error opening repository {}: ",
arguments.repository_path.to_string_lossy ()),
Repository::open (
& output,
Repository::default_config (),
& arguments.repository_path,
arguments.password_file_path.clone ()),
) ?;
let atomic_file_writer =
AtomicFileWriter::new (
output,
& arguments.repository_path,
None,
) ?;
repository.load_indexes (
output,
) ?;
let output_job =
output_job_start! (
output,
"Scanning indexes");
let old_index_ids_and_sizes =
scan_index_files_with_sizes (
& arguments.repository_path,
) ?;
let total_index_size: u64 =
old_index_ids_and_sizes.iter ().map (
|& (_, old_index_size)|
old_index_size
).sum ();
output_job_replace! (
output_job,
"Found {} index files",
old_index_ids_and_sizes.len ());
let output_job =
output_job_start! (
output,
"Scanning backups");
let backup_files =
scan_backup_files (
& arguments.repository_path,
) ?;
output_job_replace! (
output_job,
"Found {} backup files",
backup_files.len ());
let output_job =
output_job_start! (
output,
"Reading backups");
let mut backup_chunk_ids: HashSet <ChunkId> =
HashSet::new ();
let mut backup_count: u64 = 0;
let backup_total = backup_files.len () as u64;
for backup_file in backup_files {
output_job.progress (
backup_count,
backup_total);
collect_chunks_from_backup (
& repository,
& mut backup_chunk_ids,
& backup_file,
) ?;
backup_count += 1;
}
output_job_replace! (
output_job,
"Found {} chunks referenced by backups",
backup_chunk_ids.len ());
let output_job =
output_job_start! (
output,
"Garbage collecting indexes");
let mut old_index_progress: u64 = 0;
let mut chunks_removed: u64 = 0;
let mut indexes_modified: u64 = 0;
let mut indexes_removed: u64 = 0;
for (
old_index_id,
old_index_size,
) in old_index_ids_and_sizes {
output_job.progress (
old_index_progress,
total_index_size);
let old_index_path =
repository.index_path (
old_index_id);
let old_index_entries =
index_read_path (
& old_index_path,
repository.encryption_key (),
) ?;
if ! old_index_entries.iter ().any (
|& RawIndexEntry {
index_bundle_header: ref _old_index_bundle_header,
bundle_info: ref old_index_bundle_info,
}|
old_index_bundle_info.chunks ().any (
|ref old_chunk_record| {
! backup_chunk_ids.contains (
& old_chunk_record.chunk_id (),
)
})
) {
old_index_progress += 1;
continue;
}
let mut new_index_entries: Vec <RawIndexEntry> =
Vec::new ();
for RawIndexEntry {
index_bundle_header: old_index_bundle_header,
bundle_info: old_index_bundle_info,
} in old_index_entries.into_iter () {
if ! old_index_bundle_info.chunks ().any (
|ref old_chunk_record| {
backup_chunk_ids.contains (
& old_chunk_record.chunk_id ())
}) {
chunks_removed +=
old_index_bundle_info.num_chunks ();
continue;
}
let mut new_bundle_chunks =
Vec::new ();
for old_bundle_chunk
in old_index_bundle_info.chunks () {
if backup_chunk_ids.contains (
& old_bundle_chunk.chunk_id ()) {
new_bundle_chunks.push (
old_bundle_chunk.clone ());
} else {
chunks_removed += 1;
}
}
new_index_entries.push (
RawIndexEntry {
index_bundle_header:
old_index_bundle_header,
bundle_info:
DiskBundleInfo::new (
new_bundle_chunks),
}
);
}
atomic_file_writer.delete (
old_index_path);
if ! new_index_entries.is_empty () {
let new_index_bytes: Vec <u8> =
rand::thread_rng ()
.gen_iter::<u8> ()
.take (24)
.collect ();
let new_index_name: String =
new_index_bytes.to_hex ();
let new_index_path =
repository.path ()
.join ("index")
.join (new_index_name);
let mut new_index_file =
atomic_file_writer.create (
new_index_path,
) ?;
index_write_direct (
& mut new_index_file,
repository.encryption_key (),
& new_index_entries,
) ?;
indexes_modified += 1;
} else {
indexes_removed += 1;
}
old_index_progress +=
old_index_size;
}
output_job_replace! (
output_job,
"Removed {} chunks from {} modified and {} deleted indexes",
chunks_removed,
indexes_modified,
indexes_removed);
let output_job =
output_job_start! (
output,
"Committing changes");
atomic_file_writer.commit () ?;
output_job.remove ();
repository.close (
output);
Ok (true)
}
command! (
name = gc_indexes,
export = gc_indexes_command,
arguments = GcIndexesArguments {
repository_path: PathBuf,
password_file_path: Option <PathBuf>,
},
clap_subcommand = {
clap::SubCommand::with_name ("gc-indexes")
.about ("Removes index entries which are not referenced by any \
backup")
.arg (
clap::Arg::with_name ("repository")
.long ("repository")
.value_name ("REPOSITORY")
.required (true)
.help ("Path to the repository")
)
.arg (
clap::Arg::with_name ("password-file")
.long ("password-file")
.value_name ("PASSWORD-FILE")
.required (false)
.help ("Path to the password file")
)
},
clap_arguments_parse = |clap_matches| {
GcIndexesArguments {
repository_path:
args::path_required (
& clap_matches,
"repository"),
password_file_path:
args::path_optional (
& clap_matches,
"password-file"),
}
},
action = |output, arguments| {
gc_indexes (output, arguments)
},
);