conserve/
show.rs

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