feat(recover): recover a selection of folders relative to each marked folder

A marked folder a/b/Projeto is recreated as Projeto/... with everything
inside it; single marked files go to the destination root and unmarked
files inside a marked folder are left out. recover_start accepts
{selection: {folders, ids, exclude}}.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas RayanandClaude Opus 5.5 committed 2026-11-03 13:31:45 -03:00
1 parent b274f0b7ef
commit ca677a8bc1
5 files changed
+410 -11

No files matched your search

+27 -9
View File
@@ -470,6 +470,7 @@ impl Engine {
scan_id: &str,
ids: Option<Vec<u64>>,
filter: Option<Query>,
selection: Option<crate::explorer::Selection>,
dest: &str,
layout: recover::RecoverLayout,
) -> Result<String> {
@@ -477,15 +478,25 @@ impl Engine {
let src = sj.source().ok_or_else(no_source)?;
let files: Vec<Found> = {
let s = sj.st.lock().unwrap();
match (&ids, &filter) {
(Some(ids), _) => ids
.iter()
.filter_map(|i| s.index.get(i).map(|&k| s.found[k].clone()))
.collect(),
(None, Some(q)) => q.select(&s.found).into_iter().cloned().collect(),
(None, None) => s.found.clone(),
if let Some(sel) = &selection {
crate::explorer::resolve_selection(&s.found, sel)
.into_iter()
.cloned()
.collect()
} else {
match (&ids, &filter) {
(Some(ids), _) => ids
.iter()
.filter_map(|i| s.index.get(i).map(|&k| s.found[k].clone()))
.collect(),
(None, Some(q)) => q.select(&s.found).into_iter().cloned().collect(),
(None, None) => s.found.clone(),
}
}
};
let roots = selection
.as_ref()
.map(|sel| recover::SelectionRoots::new(&sel.folders));
if files.is_empty() {
return Err(Error::msg("nenhum arquivo selecionado"));
}
@@ -533,11 +544,12 @@ impl Engine {
};
};
let refs: Vec<&Found> = files.iter().collect();
let res = recover::recover_resumable(
let res = recover::recover_resumable_with(
src.as_ref(),
&refs,
&dest_path,
layout,
roots.as_ref(),
&source_key,
true,
&cancelled,
@@ -1283,7 +1295,13 @@ impl Engine {
.and_then(|v| v.as_str())
.and_then(recover::RecoverLayout::parse)
.unwrap_or_default();
json!({ "job": self.start_recover(&s("job")?, ids, filter, &s("dest")?, layout)? })
let selection: Option<crate::explorer::Selection> = p
.get("selection")
.cloned()
.filter(|v| !v.is_null())
.map(serde_json::from_value)
.transpose()?;
json!({ "job": self.start_recover(&s("job")?, ids, filter, selection, &s("dest")?, layout)? })
}
"image_start" => {
let format: image::ImageFormat = p