diff --git a/src/backend/choose.rs b/src/backend/choose.rs index 4fa7a68..4ceced4 100644 --- a/src/backend/choose.rs +++ b/src/backend/choose.rs @@ -1,6 +1,6 @@ use std::fs::File; -use anyhow::Result; +use anyhow::{bail, Result}; use async_trait::async_trait; use super::{FileType, Id, ReadBackend, WriteBackend}; @@ -17,16 +17,13 @@ use ChooseBackend::{Local, Rclone, Rest}; impl ChooseBackend { pub fn from_url(url: &str) -> Result { - if let Some(path) = url.strip_prefix("rclone:") { - return Ok(Rclone(RcloneBackend::new(path)?)); - } - if let Some(path) = url.strip_prefix("rest:") { - return Ok(Rest(RestBackend::new(path))); - } - if let Some(path) = url.strip_prefix("local:") { - return Ok(Local(LocalBackend::new(path))); - } - Ok(Local(LocalBackend::new(url))) + Ok(match url.split_once(':') { + Some(("rclone", path)) => Rclone(RcloneBackend::new(path)?), + Some(("rest", path)) => Rest(RestBackend::new(path)), + Some(("local", path)) => Local(LocalBackend::new(path)), + Some((backend, _)) => bail!("backend {backend} is not supported!"), + None => Local(LocalBackend::new(url)), + }) } } diff --git a/src/blob/tree.rs b/src/blob/tree.rs index 1ba438b..308c3e1 100644 --- a/src/blob/tree.rs +++ b/src/blob/tree.rs @@ -1,6 +1,6 @@ use std::collections::{HashSet, VecDeque}; use std::mem; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::pin::Pin; use anyhow::{anyhow, Result}; @@ -56,6 +56,24 @@ impl Tree { Ok(serde_json::from_slice(&data)?) } + + pub async fn subtree_id(be: &impl IndexedBackend, mut id: Id, path: &Path) -> Result { + for p in path.iter() { + let p = p.to_str().unwrap(); + // TODO: check for root instead + if p == "/" { + continue; + } + let tree = Tree::from_backend(be, id).await?; + let node = tree + .nodes() + .iter() + .find(|node| node.name() == p) + .ok_or_else(|| anyhow!("{} not found", p))?; + id = node.subtree().ok_or_else(|| anyhow!("{} is no dir", p))?; + } + Ok(id) + } } /// NodeStreamer recursively streams all nodes of a given tree including all subtrees in-order diff --git a/src/commands/cat.rs b/src/commands/cat.rs index a151bfd..e9befc4 100644 --- a/src/commands/cat.rs +++ b/src/commands/cat.rs @@ -1,6 +1,6 @@ -use std::path::PathBuf; +use std::path::Path; -use anyhow::{anyhow, Result}; +use anyhow::Result; use clap::{Parser, Subcommand}; use indicatif::ProgressBar; @@ -36,11 +36,9 @@ struct IdOpt { #[derive(Parser)] struct TreeOpts { - /// snapshot id - id: String, - - /// path within snapshot - path: PathBuf, + /// snapshot/path to restore + #[clap(value_name = "SNAPSHOT[:PATH]")] + snap: String, } pub(super) async fn execute(be: &impl DecryptReadBackend, opts: Opts) -> Result<()> { @@ -76,25 +74,10 @@ async fn cat_blob(be: &impl DecryptReadBackend, tpe: BlobType, opt: IdOpt) -> Re } async fn cat_tree(be: &impl DecryptReadBackend, opts: TreeOpts) -> Result<()> { - let snap = SnapshotFile::from_str(be, &opts.id, |_| true, progress_counter()).await?; + let (id, path) = opts.snap.split_once(':').unwrap_or((&opts.snap, "")); + let snap = SnapshotFile::from_str(be, id, |_| true, progress_counter()).await?; let index = IndexBackend::new(be, progress_counter()).await?; - let mut id = snap.tree; - - for p in opts.path.iter() { - let p = p.to_str().unwrap(); - // TODO: check for root instead - if p == "/" { - continue; - } - let tree = Tree::from_backend(&index, id).await?; - let node = tree - .nodes() - .iter() - .find(|node| node.name() == p) - .ok_or_else(|| anyhow!("{} not found", p))?; - id = node.subtree().ok_or_else(|| anyhow!("{} is no dir", p))?; - } - + let id = Tree::subtree_id(&index, snap.tree, Path::new(path)).await?; let data = index.blob_from_backend(&BlobType::Tree, &id).await?; println!("{}", String::from_utf8(data)?); diff --git a/src/commands/diff.rs b/src/commands/diff.rs index 4fd3c18..32dabbe 100644 --- a/src/commands/diff.rs +++ b/src/commands/diff.rs @@ -1,3 +1,5 @@ +use std::path::Path; + use anyhow::Result; use clap::Parser; use futures::StreamExt; @@ -5,30 +7,37 @@ use vlog::*; use super::progress_counter; use crate::backend::DecryptReadBackend; -use crate::blob::{NodeStreamer, NodeType}; +use crate::blob::{NodeStreamer, NodeType, Tree}; use crate::index::IndexBackend; use crate::repo::SnapshotFile; #[derive(Parser)] pub(super) struct Opts { - /// reference snapshot - id1: String, + /// reference snapshot/path + #[clap(value_name = "SNAPSHOT1[:PATH1]")] + snap1: String, - /// new snapshot - id2: String, + /// new snapshot/path [default for PATH2: PATH1] + #[clap(value_name = "SNAPSHOT2[:PATH2]")] + snap2: String, } pub(super) async fn execute(be: &(impl DecryptReadBackend + Unpin), opts: Opts) -> Result<()> { + let (id1, path1) = opts.snap1.split_once(':').unwrap_or((&opts.snap1, "")); + let (id2, path2) = opts.snap2.split_once(':').unwrap_or((&opts.snap2, path1)); + v1!("getting snapshots..."); - let snaps = SnapshotFile::from_ids(be, &[opts.id1, opts.id2]).await?; + let snaps = SnapshotFile::from_ids(be, &[id1.to_string(), id2.to_string()]).await?; let snap1 = &snaps[0]; let snap2 = &snaps[1]; let index = IndexBackend::new(be, progress_counter()).await?; + let id1 = Tree::subtree_id(&index, snap1.tree, Path::new(path1)).await?; + let id2 = Tree::subtree_id(&index, snap2.tree, Path::new(path2)).await?; - let mut tree_streamer1 = NodeStreamer::new(index.clone(), snap1.tree).await?; - let mut tree_streamer2 = NodeStreamer::new(index, snap2.tree).await?; + let mut tree_streamer1 = NodeStreamer::new(index.clone(), id1).await?; + let mut tree_streamer2 = NodeStreamer::new(index, id2).await?; let mut item1 = tree_streamer1.next().await.transpose()?; let mut item2 = tree_streamer2.next().await.transpose()?; diff --git a/src/commands/ls.rs b/src/commands/ls.rs index 8f666b9..e1223d8 100644 --- a/src/commands/ls.rs +++ b/src/commands/ls.rs @@ -1,24 +1,28 @@ use anyhow::Result; use clap::Parser; use futures::StreamExt; +use std::path::Path; use super::progress_counter; use crate::backend::DecryptReadBackend; -use crate::blob::NodeStreamer; +use crate::blob::{NodeStreamer, Tree}; use crate::index::IndexBackend; use crate::repo::SnapshotFile; #[derive(Parser)] pub(super) struct Opts { - /// snapshot to ls - id: String, + /// snapshot/path to ls + #[clap(value_name = "SNAPSHOT[:PATH]")] + snap: String, } pub(super) async fn execute(be: &(impl DecryptReadBackend + Unpin), opts: Opts) -> Result<()> { - let snap = SnapshotFile::from_str(be, &opts.id, |_| true, progress_counter()).await?; + let (id, path) = opts.snap.split_once(':').unwrap_or((&opts.snap, "")); + let snap = SnapshotFile::from_str(be, id, |_| true, progress_counter()).await?; let index = IndexBackend::new(be, progress_counter()).await?; + let tree = Tree::subtree_id(&index, snap.tree, Path::new(path)).await?; - let mut tree_streamer = NodeStreamer::new(index, snap.tree).await?; + let mut tree_streamer = NodeStreamer::new(index, tree).await?; while let Some(item) = tree_streamer.next().await { let (path, _) = item?; println!("{:?} ", path); diff --git a/src/commands/restore.rs b/src/commands/restore.rs index 366eef2..f61ff54 100644 --- a/src/commands/restore.rs +++ b/src/commands/restore.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::io::Read; use std::num::NonZeroU32; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use anyhow::{anyhow, Result}; use clap::Parser; @@ -12,7 +12,7 @@ use vlog::*; use super::{bytes, progress_bytes, progress_counter}; use crate::backend::{DecryptReadBackend, FileType, LocalBackend}; -use crate::blob::{Node, NodeStreamer, NodeType}; +use crate::blob::{Node, NodeStreamer, NodeType, Tree}; use crate::crypto::hash; use crate::id::Id; use crate::index::{IndexBackend, IndexedBackend}; @@ -32,21 +32,25 @@ pub(super) struct Opts { #[clap(long)] numeric_id: bool, - /// snapshot to restore - id: String, + /// snapshot/path to restore + #[clap(value_name = "SNAPSHOT[:PATH]")] + snap: String, /// restore destination dest: String, } pub(super) async fn execute(be: &(impl DecryptReadBackend + Unpin), opts: Opts) -> Result<()> { - let snap = SnapshotFile::from_str(be, &opts.id, |_| true, progress_counter()).await?; + let (id, path) = opts.snap.split_once(':').unwrap_or((&opts.snap, "")); + let snap = SnapshotFile::from_str(be, id, |_| true, progress_counter()).await?; + + let index = IndexBackend::new(be, progress_counter()).await?; + let tree = Tree::subtree_id(&index, snap.tree, Path::new(path)).await?; let dest = LocalBackend::new(&opts.dest); - let index = IndexBackend::new(be, progress_counter()).await?; v1!("collecting restore information and allocating non-existing files..."); - let file_infos = allocate_and_collect(&dest, index.clone(), snap.tree, &opts).await?; + let file_infos = allocate_and_collect(&dest, index.clone(), tree, &opts).await?; v1!("total restore size: {}", bytes(file_infos.total_size)); if file_infos.matched_size > 0 { v1!( @@ -63,7 +67,7 @@ pub(super) async fn execute(be: &(impl DecryptReadBackend + Unpin), opts: Opts) } v1!("setting metadata..."); - restore_metadata(&dest, index, snap.tree, &opts).await?; + restore_metadata(&dest, index, tree, &opts).await?; v1!("done."); Ok(())