From 8795c4475775516e9bce51c4a4c7f12ea74ddd51 Mon Sep 17 00:00:00 2001 From: Alexander Weiss Date: Fri, 23 Jun 2023 12:19:22 +0200 Subject: [PATCH] repoinfo: Add options --json, --only-files, --only-index --- changelog/new.txt | 1 + crates/rustic_core/src/backend.rs | 8 +- crates/rustic_core/src/commands/repoinfo.rs | 23 ++-- src/commands/repoinfo.rs | 125 +++++++------------- 4 files changed, 66 insertions(+), 91 deletions(-) diff --git a/changelog/new.txt b/changelog/new.txt index 30ea692c1..a209b2e87 100644 --- a/changelog/new.txt +++ b/changelog/new.txt @@ -11,3 +11,4 @@ New features: - restore: Files are now allocated just before being first processed. This allows easier resumed restores. - New option: `no-require-git` for backup - if enabled, a git repository is not required to apply `git-ignore` rule. - fix: wait for password-command to successfully exit, allowing to input something into the command, and read password from stdout. +- repoinfo: Added new options --json, --only-files, --only-index diff --git a/crates/rustic_core/src/backend.rs b/crates/rustic_core/src/backend.rs index 03a674ab8..3a3a5d9da 100644 --- a/crates/rustic_core/src/backend.rs +++ b/crates/rustic_core/src/backend.rs @@ -15,6 +15,7 @@ use std::{io::Read, path::PathBuf}; use bytes::Bytes; use displaydoc::Display; use log::trace; +use serde::{Deserialize, Serialize}; use crate::{backend::node::Node, error::BackendErrorKind, id::Id, RusticResult}; @@ -27,17 +28,22 @@ pub const ALL_FILE_TYPES: [FileType; 4] = [ ]; /// Type for describing the kind of a file that can occur. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Display)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Display, Serialize, Deserialize)] pub enum FileType { /// config + #[serde(rename = "config")] Config, /// index + #[serde(rename = "index")] Index, /// keys + #[serde(rename = "key")] Key, /// snapshots + #[serde(rename = "snapshot")] Snapshot, /// data + #[serde(rename = "pack")] Pack, } diff --git a/crates/rustic_core/src/commands/repoinfo.rs b/crates/rustic_core/src/commands/repoinfo.rs index eda3093fc..3d4197e02 100644 --- a/crates/rustic_core/src/commands/repoinfo.rs +++ b/crates/rustic_core/src/commands/repoinfo.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + use crate::{ index::IndexEntry, repofile::indexfile::{IndexFile, IndexPack}, @@ -5,7 +7,7 @@ use crate::{ ReadBackend, Repository, RusticResult, ALL_FILE_TYPES, }; -#[derive(Default, Clone, Debug)] +#[derive(Default, Clone, Debug, Serialize, Deserialize)] pub struct IndexInfos { pub blobs: Vec, pub blobs_delete: Vec, @@ -13,7 +15,7 @@ pub struct IndexInfos { pub packs_delete: Vec, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct BlobInfo { pub blob_type: BlobType, pub count: u64, @@ -29,7 +31,8 @@ impl BlobInfo { } } -#[derive(Clone, Copy, Debug)] +#[serde_with::apply(Option => #[serde(default, skip_serializing_if = "Option::is_none")])] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct PackInfo { pub blob_type: BlobType, pub count: u64, @@ -102,13 +105,14 @@ pub(crate) fn collect_index_infos( Ok(info) } -#[derive(Default, Clone, Debug)] +#[serde_with::apply(Option => #[serde(default, skip_serializing_if = "Option::is_none")])] +#[derive(Default, Clone, Debug, Serialize, Deserialize)] pub struct RepoFileInfos { - pub files: Vec, - pub files_hot: Option>, + pub repo: Vec, + pub repo_hot: Option>, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct RepoFileInfo { pub tpe: FileType, pub count: u64, @@ -132,5 +136,8 @@ pub fn collect_file_infos(repo: &Repository

) -> RusticResult let files_hot = repo.be_hot.as_ref().map(collect_file_info).transpose()?; p.finish(); - Ok(RepoFileInfos { files, files_hot }) + Ok(RepoFileInfos { + repo: files, + repo_hot: files_hot, + }) } diff --git a/src/commands/repoinfo.rs b/src/commands/repoinfo.rs index 62fcfa44f..18d715ec5 100644 --- a/src/commands/repoinfo.rs +++ b/src/commands/repoinfo.rs @@ -7,14 +7,27 @@ use crate::{ }; use abscissa_core::{Command, Runnable, Shutdown}; +use serde::Serialize; use crate::helpers::table_right_from; use anyhow::Result; -use rustic_core::RepoFileInfo; +use rustic_core::{IndexInfos, RepoFileInfo, RepoFileInfos}; /// `repoinfo` subcommand #[derive(clap::Parser, Command, Debug)] -pub(crate) struct RepoInfoCmd; +pub(crate) struct RepoInfoCmd { + /// Only scan repository files (doesn't need repository password) + #[clap(long)] + only_files: bool, + + /// Only scan index + #[clap(long)] + only_index: bool, + + /// Show infos in json format + #[clap(long)] + json: bool, +} impl Runnable for RepoInfoCmd { fn run(&self) { @@ -25,97 +38,45 @@ impl Runnable for RepoInfoCmd { } } +#[serde_with::apply(Option => #[serde(default, skip_serializing_if = "Option::is_none")])] +#[derive(Serialize)] +struct Infos { + files: Option, + index: Option, +} + impl RepoInfoCmd { fn inner_run(&self) -> Result<()> { let config = RUSTIC_APP.config(); let repo = get_repository(&config); - let file_info = repo.infos_files()?; - print_file_info("repository files", file_info.files); - if let Some(info) = file_info.files_hot { - print_file_info("hot repository files", info); - } - let repo = repo.open()?; - let index_info = repo.infos_index()?; - - let mut table = table_right_from( - 1, - ["Blob type", "Count", "Total Size", "Total Size in Packs"], - ); - - let mut total_count = 0; - let mut total_data_size = 0; - let mut total_size = 0; + let infos = Infos { + files: (!self.only_index).then(|| repo.infos_files()).transpose()?, + index: (!self.only_files) + .then(|| -> Result<_> { + let repo = repo.open()?; + let info_index = repo.infos_index()?; + Ok(info_index) + }) + .transpose()?, + }; - for blobs in &index_info.blobs { - _ = table.add_row([ - format!("{:?}", blobs.blob_type), - blobs.count.to_string(), - bytes_size_to_string(blobs.data_size), - bytes_size_to_string(blobs.size), - ]); - total_count += blobs.count; - total_data_size += blobs.data_size; - total_size += blobs.size; - } - for blobs in &index_info.blobs_delete { - if blobs.count > 0 { - _ = table.add_row([ - format!("{:?} to delete", blobs.blob_type), - blobs.count.to_string(), - bytes_size_to_string(blobs.data_size), - bytes_size_to_string(blobs.size), - ]); - total_count += blobs.count; - total_data_size += blobs.data_size; - total_size += blobs.size; - } + if self.json { + let mut stdout = std::io::stdout(); + serde_json::to_writer_pretty(&mut stdout, &infos)?; + return Ok(()); } - _ = table.add_row([ - "Total".to_string(), - total_count.to_string(), - bytes_size_to_string(total_data_size), - bytes_size_to_string(total_size), - ]); - - println!(); - println!("{table}"); - - let mut table = table_right_from( - 1, - ["Blob type", "Pack Count", "Minimum Size", "Maximum Size"], - ); - - for packs in index_info.packs { - _ = table.add_row([ - format!("{:?} packs", packs.blob_type), - packs.count.to_string(), - packs - .min_size - .map_or("-".to_string(), |s| bytes_size_to_string(s)), - packs - .max_size - .map_or("-".to_string(), |s| bytes_size_to_string(s)), - ]); - } - for packs in index_info.packs_delete { - if packs.count > 0 { - _ = table.add_row([ - format!("{:?} packs to delete", packs.blob_type), - packs.count.to_string(), - packs - .min_size - .map_or("-".to_string(), |s| bytes_size_to_string(s)), - packs - .max_size - .map_or("-".to_string(), |s| bytes_size_to_string(s)), - ]); + if let Some(file_info) = infos.files { + print_file_info("repository files", file_info.repo); + if let Some(info) = file_info.repo_hot { + print_file_info("hot repository files", info); } } - println!(); - println!("{table}"); + if let Some(index_info) = infos.index { + print_index_info(index_info); + } Ok(()) } }