catalog_shell: impl find for catalog shell.
Implements the find command which allows to find and select files for subsequent restore. Files selected for restore are now stored in a Vec instead of a HashSet. This is needed, since instead of the full paths for each file, selected files are now identified by a list of match pattern, where ordering matters. Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
This commit is contained in:
parent
90dfd0a71d
commit
25cdd0e0a1
@ -1,5 +1,5 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashMap;
|
||||||
use std::ffi::{CString, OsStr};
|
use std::ffi::{CString, OsStr};
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::os::unix::ffi::OsStrExt;
|
use std::os::unix::ffi::OsStrExt;
|
||||||
@ -75,6 +75,12 @@ pub fn catalog_shell_cli() -> CommandLineInterface {
|
|||||||
.arg_param(&["target"])
|
.arg_param(&["target"])
|
||||||
.completion_cb("target", tools::complete_file_name)
|
.completion_cb("target", tools::complete_file_name)
|
||||||
)
|
)
|
||||||
|
.insert(
|
||||||
|
"find",
|
||||||
|
CliCommand::new(&API_METHOD_FIND_COMMAND)
|
||||||
|
.arg_param(&["path", "pattern"])
|
||||||
|
.completion_cb("path", Shell::complete_path)
|
||||||
|
)
|
||||||
.insert_help();
|
.insert_help();
|
||||||
|
|
||||||
CommandLineInterface::Nested(map)
|
CommandLineInterface::Nested(map)
|
||||||
@ -96,7 +102,7 @@ impl Shell {
|
|||||||
let mut ctx = handle.borrow_mut();
|
let mut ctx = handle.borrow_mut();
|
||||||
*ctx = Some(Context {
|
*ctx = Some(Context {
|
||||||
catalog,
|
catalog,
|
||||||
selected: HashSet::new(),
|
selected: Vec::new(),
|
||||||
decoder,
|
decoder,
|
||||||
root: root.clone(),
|
root: root.clone(),
|
||||||
current: root,
|
current: root,
|
||||||
@ -344,14 +350,12 @@ fn select_command(path: String) -> Result<(), Error> {
|
|||||||
// Calling canonical_path() makes sure the provided path is valid and
|
// Calling canonical_path() makes sure the provided path is valid and
|
||||||
// actually contained within the catalog and therefore also the archive.
|
// actually contained within the catalog and therefore also the archive.
|
||||||
let path = ctx.canonical_path(&path)?;
|
let path = ctx.canonical_path(&path)?;
|
||||||
if ctx
|
let pattern = MatchPattern::from_line(Context::generate_cstring(&path)?.as_bytes())?
|
||||||
.selected
|
.ok_or_else(|| format_err!("encountered invalid match pattern"))?;
|
||||||
.insert(Context::generate_cstring(&path)?.into_bytes())
|
if ctx.selected.iter().find(|p| **p == pattern).is_none() {
|
||||||
{
|
ctx.selected.push(pattern);
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
bail!("entry already selected for restore")
|
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,11 +376,17 @@ fn select_command(path: String) -> Result<(), Error> {
|
|||||||
fn deselect_command(path: String) -> Result<(), Error> {
|
fn deselect_command(path: String) -> Result<(), Error> {
|
||||||
Context::with(|ctx| {
|
Context::with(|ctx| {
|
||||||
let path = ctx.canonical_path(&path)?;
|
let path = ctx.canonical_path(&path)?;
|
||||||
if ctx.selected.remove(&Context::generate_cstring(&path)?.into_bytes()) {
|
let mut pattern = MatchPattern::from_line(Context::generate_cstring(&path)?.as_bytes())?
|
||||||
Ok(())
|
.ok_or_else(|| format_err!("encountered invalid match pattern"))?;
|
||||||
} else {
|
if let Some(last) = ctx.selected.last() {
|
||||||
bail!("entry not selected for restore")
|
if last == &pattern {
|
||||||
|
ctx.selected.pop();
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
pattern.invert();
|
||||||
|
ctx.selected.push(pattern);
|
||||||
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -395,13 +405,7 @@ fn deselect_command(path: String) -> Result<(), Error> {
|
|||||||
/// Target must not exist on the clients filesystem.
|
/// Target must not exist on the clients filesystem.
|
||||||
fn restore_selected_command(target: String) -> Result<(), Error> {
|
fn restore_selected_command(target: String) -> Result<(), Error> {
|
||||||
Context::with(|ctx| {
|
Context::with(|ctx| {
|
||||||
let mut list = Vec::new();
|
if ctx.selected.is_empty() {
|
||||||
for path in &ctx.selected {
|
|
||||||
let pattern = MatchPattern::from_line(path)?
|
|
||||||
.ok_or_else(|| format_err!("encountered invalid match pattern"))?;
|
|
||||||
list.push(pattern);
|
|
||||||
}
|
|
||||||
if list.is_empty() {
|
|
||||||
bail!("no entries selected for restore");
|
bail!("no entries selected for restore");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,7 +413,7 @@ fn restore_selected_command(target: String) -> Result<(), Error> {
|
|||||||
// patterns are relative to root as well.
|
// patterns are relative to root as well.
|
||||||
let start_dir = ctx.decoder.root()?;
|
let start_dir = ctx.decoder.root()?;
|
||||||
ctx.decoder
|
ctx.decoder
|
||||||
.restore(&start_dir, &Path::new(&target), &list)?;
|
.restore(&start_dir, &Path::new(&target), &ctx.selected)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -418,14 +422,8 @@ fn restore_selected_command(target: String) -> Result<(), Error> {
|
|||||||
/// List entries currently selected for restore.
|
/// List entries currently selected for restore.
|
||||||
fn list_selected_command() -> Result<(), Error> {
|
fn list_selected_command() -> Result<(), Error> {
|
||||||
Context::with(|ctx| {
|
Context::with(|ctx| {
|
||||||
let mut list = ctx.selected.iter().collect::<Vec<&Vec<u8>>>();
|
|
||||||
list.sort();
|
|
||||||
|
|
||||||
let mut out = std::io::stdout();
|
let mut out = std::io::stdout();
|
||||||
for entry in list {
|
out.write_all(&MatchPattern::to_bytes(ctx.selected.as_slice()))?;
|
||||||
out.write_all(entry)?;
|
|
||||||
out.write_all(&[b'\n'])?;
|
|
||||||
}
|
|
||||||
out.flush()?;
|
out.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
@ -475,6 +473,66 @@ fn restore_command(target: String, pattern: Option<String>) -> Result<(), Error>
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[api(
|
||||||
|
input: {
|
||||||
|
properties: {
|
||||||
|
path: {
|
||||||
|
type: String,
|
||||||
|
description: "Path to node from where to start the search."
|
||||||
|
},
|
||||||
|
pattern: {
|
||||||
|
type: String,
|
||||||
|
description: "Match pattern for matching files in the catalog."
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
type: bool,
|
||||||
|
optional: true,
|
||||||
|
description: "Add matching filenames to list for restore."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)]
|
||||||
|
/// Find entries in the catalog matching the given match pattern.
|
||||||
|
fn find_command(path: String, pattern: String, select: Option<bool>) -> Result<(), Error> {
|
||||||
|
Context::with(|ctx| {
|
||||||
|
let mut path = ctx.canonical_path(&path)?;
|
||||||
|
if !path.last().unwrap().is_directory() {
|
||||||
|
bail!("path should be a directory, not a file!");
|
||||||
|
}
|
||||||
|
let select = select.unwrap_or(false);
|
||||||
|
|
||||||
|
let cpath = Context::generate_cstring(&path).unwrap();
|
||||||
|
let pattern = if pattern.starts_with("!") {
|
||||||
|
let mut buffer = vec![b'!'];
|
||||||
|
buffer.extend_from_slice(cpath.as_bytes());
|
||||||
|
buffer.extend_from_slice(pattern[1..pattern.len()].as_bytes());
|
||||||
|
buffer
|
||||||
|
} else {
|
||||||
|
let mut buffer = cpath.as_bytes().to_vec();
|
||||||
|
buffer.extend_from_slice(pattern.as_bytes());
|
||||||
|
buffer
|
||||||
|
};
|
||||||
|
|
||||||
|
let pattern = MatchPattern::from_line(&pattern)?
|
||||||
|
.ok_or_else(|| format_err!("invalid match pattern"))?;
|
||||||
|
let slice = vec![pattern.as_slice()];
|
||||||
|
|
||||||
|
ctx.catalog.find(
|
||||||
|
&mut path,
|
||||||
|
&slice,
|
||||||
|
&Box::new(|path: &[DirEntry]| println!("{:?}", Context::generate_cstring(path).unwrap()))
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Insert if matches should be selected.
|
||||||
|
// Avoid duplicate entries of the same match pattern.
|
||||||
|
if select && ctx.selected.iter().find(|p| **p == pattern).is_none() {
|
||||||
|
ctx.selected.push(pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
std::thread_local! {
|
std::thread_local! {
|
||||||
static CONTEXT: RefCell<Option<Context>> = RefCell::new(None);
|
static CONTEXT: RefCell<Option<Context>> = RefCell::new(None);
|
||||||
}
|
}
|
||||||
@ -484,7 +542,7 @@ struct Context {
|
|||||||
/// Calalog reader instance to navigate
|
/// Calalog reader instance to navigate
|
||||||
catalog: CatalogReader<std::fs::File>,
|
catalog: CatalogReader<std::fs::File>,
|
||||||
/// List of selected paths for restore
|
/// List of selected paths for restore
|
||||||
selected: HashSet<Vec<u8>>,
|
selected: Vec<MatchPattern>,
|
||||||
/// Decoder instance for the current pxar archive
|
/// Decoder instance for the current pxar archive
|
||||||
decoder: Decoder,
|
decoder: Decoder,
|
||||||
/// Root directory for the give archive as stored in the catalog
|
/// Root directory for the give archive as stored in the catalog
|
||||||
|
Loading…
Reference in New Issue
Block a user