backpak/ui/
cat.rs

1use std::io;
2use std::io::prelude::*;
3
4use anyhow::{anyhow, Context, Result};
5use clap::Parser;
6use tracing::*;
7
8use crate::backend;
9use crate::blob;
10use crate::hashing::ObjectId;
11use crate::index;
12use crate::pack;
13use crate::snapshot;
14use crate::tree;
15
16/// Print objects (as JSON) to stdout
17#[derive(Debug, Parser)]
18pub struct Args {
19    #[clap(subcommand)]
20    subcommand: Subcommand,
21}
22
23#[derive(Debug, Parser)]
24pub enum Subcommand {
25    /// Print the blob with the given ID
26    ///
27    /// A blob is either a chunk (of a file) or a tree (representing a directory).
28    #[clap(verbatim_doc_comment)]
29    Blob { id: ObjectId },
30
31    /// Print the pack with the given ID
32    ///
33    /// A pack is a compressed collection of blobs,
34    /// with a manifest at the end for reassembling the index (if needed).
35    #[clap(verbatim_doc_comment)]
36    Pack { id: ObjectId },
37
38    /// Print the index with the given ID
39    ///
40    /// An index tells us which packs contain which blobs.
41    /// Each backup stores a new index.
42    /// They can be coalesced with `rebuild-index`
43    #[clap(verbatim_doc_comment)]
44    Index { id: ObjectId },
45
46    /// Print the snapshot with the given ID
47    ///
48    /// A snapshot records the time of the backup,
49    /// the contents of all files and folders at that time,
50    /// and (optionally) an author and tags for later lookup.
51    #[clap(verbatim_doc_comment)]
52    Snapshot { id_prefix: String },
53}
54
55pub fn run(repository: &camino::Utf8Path, args: Args) -> Result<()> {
56    unsafe {
57        crate::prettify::prettify_serialize();
58    }
59
60    let (_cfg, cached_backend) = backend::open(repository, backend::CacheBehavior::Normal)?;
61
62    match args.subcommand {
63        Subcommand::Blob { id } => {
64            let index = index::build_master_index(&cached_backend)?;
65            let blob_map = index::blob_to_pack_map(&index)?;
66            let containing_pack_id = blob_map
67                .get(&id)
68                .ok_or_else(|| anyhow!("Can't find blob {} in the index", id))?;
69            info!("Blob {} found in pack {}", id, containing_pack_id);
70            let index_manifest = index.packs.get(containing_pack_id).unwrap();
71
72            let mut reader = cached_backend.read_pack(containing_pack_id)?;
73
74            let (manifest_entry, blob) = pack::extract_blob(&mut reader, &id, index_manifest)?;
75
76            debug_assert!(manifest_entry.id == id);
77            assert!(!blob.is_empty());
78            match manifest_entry.blob_type {
79                blob::Type::Chunk => io::stdout().write_all(&blob)?,
80                blob::Type::Tree => {
81                    let tree: tree::Tree = ciborium::from_reader(&*blob)
82                        .with_context(|| format!("CBOR decoding of tree {} failed", id))?;
83                    serde_json::to_writer(io::stdout(), &tree)?;
84                }
85            }
86        }
87        Subcommand::Pack { id } => {
88            let manifest = pack::load_manifest(&id, &cached_backend)?;
89            serde_json::to_writer(io::stdout(), &manifest)?;
90        }
91        Subcommand::Index { id } => {
92            let index = index::load(&id, &cached_backend)?;
93            serde_json::to_writer(io::stdout(), &index)?;
94        }
95        Subcommand::Snapshot { id_prefix } => {
96            let chrono_list = snapshot::load_chronologically(&cached_backend)?;
97            let (snapshot, _id) = snapshot::find(&chrono_list, &id_prefix)?;
98            serde_json::to_writer(io::stdout(), &snapshot)?;
99        }
100    }
101    Ok(())
102}