1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
// Conserve backup system.
// Copyright 2018-2023 Martin Pool.
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//! Text output formats for structured data.
//!
//! These are objects that accept iterators of different types of content, and write it to a
//! file (typically stdout).
use std::borrow::Cow;
use std::io::{BufWriter, Write};
use std::sync::Arc;
use time::format_description::well_known::Rfc3339;
use time::UtcOffset;
use tracing::error;
use crate::misc::duration_to_hms;
use crate::termui::TermUiMonitor;
use crate::*;
/// Options controlling the behavior of `show_versions`.
#[derive(Default, Clone, Eq, PartialEq)]
pub struct ShowVersionsOptions {
/// Show versions in LIFO order by band_id.
pub newest_first: bool,
/// Show the total size of files in the tree. This is
/// slower because it requires walking the whole index.
pub tree_size: bool,
/// Show the date and time that each backup started.
pub start_time: bool,
/// Show how much time the backup took, or "incomplete" if it never finished.
pub backup_duration: bool,
/// Show times in this zone.
pub timezone: Option<UtcOffset>,
}
/// Print a list of versions, one per line, on stdout.
pub fn show_versions(
archive: &Archive,
options: &ShowVersionsOptions,
monitor: Arc<TermUiMonitor>,
) -> Result<()> {
let mut band_ids = archive.list_band_ids()?;
if options.newest_first {
band_ids.reverse();
}
for band_id in band_ids {
if !(options.tree_size || options.start_time || options.backup_duration) {
println!("{}", band_id);
continue;
}
let mut l: Vec<String> = Vec::new();
l.push(format!("{band_id:<20}"));
let band = match Band::open(archive, band_id) {
Ok(band) => band,
Err(err) => {
error!("Failed to open band {band_id:?}: {err}");
continue;
}
};
let info = match band.get_info() {
Ok(info) => info,
Err(err) => {
error!("Failed to read band tail {band_id:?}: {err}");
continue;
}
};
if options.start_time {
let mut start_time = info.start_time;
if let Some(timezone) = options.timezone {
start_time = start_time.to_offset(timezone);
}
l.push(format!(
"{date:<25}", // "yyyy-mm-ddThh:mm:ss+oooo" => 25
date = start_time.format(&Rfc3339).unwrap(),
));
}
if options.backup_duration {
let duration_str: Cow<str> = if info.is_closed {
if let Some(end_time) = info.end_time {
let duration = end_time - info.start_time;
if let Ok(duration) = duration.try_into() {
duration_to_hms(duration).into()
} else {
Cow::Borrowed("negative")
}
} else {
Cow::Borrowed("unknown")
}
} else {
Cow::Borrowed("incomplete")
};
l.push(format!("{duration_str:>10}"));
}
if options.tree_size {
let tree_mb_str = crate::misc::bytes_to_human_mb(
archive
.open_stored_tree(BandSelectionPolicy::Specified(band_id))?
.size(Exclude::nothing(), monitor.clone())?
.file_bytes,
);
l.push(format!("{tree_mb_str:>14}",));
}
monitor.clear_progress_bars(); // to avoid fighting with stdout
println!("{}", l.join(" "));
}
Ok(())
}
pub fn show_index_json(band: &Band, w: &mut dyn Write) -> Result<()> {
// TODO: Maybe use https://docs.serde.rs/serde/ser/trait.Serializer.html#method.collect_seq.
let bw = BufWriter::new(w);
let index_entries: Vec<IndexEntry> = band.index().iter_entries().collect();
serde_json::ser::to_writer_pretty(bw, &index_entries)
.map_err(|source| Error::SerializeJson { source })
}
pub fn show_entry_names<E: EntryTrait, I: Iterator<Item = E>>(
it: I,
w: &mut dyn Write,
long_listing: bool,
) -> Result<()> {
let mut bw = BufWriter::new(w);
for entry in it {
if long_listing {
writeln!(
bw,
"{} {} {}",
entry.unix_mode(),
entry.owner(),
entry.apath()
)?;
} else {
writeln!(bw, "{}", entry.apath())?;
}
}
Ok(())
}