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:
1 parent
b274f0b7ef
commit
ca677a8bc1
5 files changed
+410
-11
No files matched your search
@@ -381,6 +381,7 @@ fn sessao_da_v0_1_0_abre_e_funciona() {
|
||||
&job,
|
||||
None,
|
||||
Some(service::Query::default()),
|
||||
None,
|
||||
out.path().to_str().unwrap(),
|
||||
recover::RecoverLayout::Folders,
|
||||
)
|
||||
@@ -437,6 +438,7 @@ fn sessao_abre_sem_a_origem_e_pode_ser_reconectada() {
|
||||
&job,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
out.path().to_str().unwrap(),
|
||||
recover::RecoverLayout::Folders,
|
||||
)
|
||||
@@ -452,6 +454,7 @@ fn sessao_abre_sem_a_origem_e_pode_ser_reconectada() {
|
||||
&job,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
out.path().to_str().unwrap(),
|
||||
recover::RecoverLayout::Folders,
|
||||
)
|
||||
@@ -712,6 +715,7 @@ fn espaco_insuficiente_no_destino_e_avisado_antes_de_copiar() {
|
||||
&job,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
out.path().to_str().unwrap(),
|
||||
recover::RecoverLayout::Folders,
|
||||
)
|
||||
@@ -719,3 +723,120 @@ fn espaco_insuficiente_no_destino_e_avisado_antes_de_copiar() {
|
||||
assert!(e.to_string().contains("Espaço insuficiente"), "{e}");
|
||||
assert!(recover::free_space(out.path()).unwrap_or(0) > 0);
|
||||
}
|
||||
|
||||
/// Folder browsing on FAT32 (deleted short names lose their first character,
|
||||
/// hence `_raia.jpg`): one level at a time, and recovering a marked folder
|
||||
/// recreates only that folder (plus single marked files at the root, minus the
|
||||
/// unmarked ones).
|
||||
#[test]
|
||||
fn navegacao_por_pastas_e_recuperacao_da_selecao() {
|
||||
init();
|
||||
let Some(dir) = fixtures() else { return };
|
||||
let img = dir.join("fat32.img");
|
||||
let eng = service::Engine::new();
|
||||
let job = eng
|
||||
.start_scan(img.to_str().unwrap(), ScanOptions::default())
|
||||
.unwrap();
|
||||
wait_done(&eng, &job);
|
||||
|
||||
let root = eng.browse(&job, "", &service::Query::default()).unwrap();
|
||||
let folders: Vec<(String, u64)> = root["folders"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter(|f| f["path"] != "@carved")
|
||||
.map(|f| {
|
||||
(
|
||||
f["path"].as_str().unwrap().to_string(),
|
||||
f["files"].as_u64().unwrap(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
assert!(folders.contains(&("Documentos".into(), 5)), "{folders:?}");
|
||||
assert!(folders.contains(&("Fotos".into(), 2)), "{folders:?}");
|
||||
let docs = eng
|
||||
.browse(&job, "Documentos", &service::Query::default())
|
||||
.unwrap();
|
||||
assert_eq!(docs["folders"][0]["path"], "Documentos/Trabalho");
|
||||
assert_eq!(docs["folders"][0]["files"], 3);
|
||||
|
||||
let all = eng
|
||||
.results(
|
||||
&job,
|
||||
&service::Query {
|
||||
limit: 5000,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
let id_of = |name: &str| {
|
||||
all["items"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|i| i["name"] == name && i["origin"] == "fat")
|
||||
.unwrap_or_else(|| panic!("{name} not found"))["id"]
|
||||
.as_u64()
|
||||
.unwrap()
|
||||
};
|
||||
let selection = explorer::Selection {
|
||||
folders: vec!["Documentos/Trabalho".into()],
|
||||
ids: vec![id_of("_raia.jpg")],
|
||||
exclude: vec![id_of("tese.docx")],
|
||||
};
|
||||
let out = tempfile::tempdir().unwrap();
|
||||
let rj = eng
|
||||
.start_recover(
|
||||
&job,
|
||||
None,
|
||||
None,
|
||||
Some(selection),
|
||||
out.path().to_str().unwrap(),
|
||||
recover::RecoverLayout::Folders,
|
||||
)
|
||||
.unwrap();
|
||||
wait_done(&eng, &rj);
|
||||
let got = hashes_in(out.path());
|
||||
let mut paths: Vec<String> = got
|
||||
.values()
|
||||
.map(|p| {
|
||||
p.strip_prefix(out.path())
|
||||
.unwrap()
|
||||
.to_string_lossy()
|
||||
.replace('\\', "/")
|
||||
})
|
||||
.collect();
|
||||
paths.sort();
|
||||
assert_eq!(
|
||||
paths,
|
||||
vec!["Trabalho/_otas.txt", "Trabalho/relatorio.pdf", "_raia.jpg"]
|
||||
);
|
||||
let expected = expected(&dir, "fat32");
|
||||
for (hash, original) in &expected {
|
||||
let wanted = [
|
||||
"Documentos/Trabalho/notas.txt",
|
||||
"Documentos/Trabalho/relatorio.pdf",
|
||||
"Fotos/praia.jpg",
|
||||
];
|
||||
if wanted.contains(&original.as_str()) {
|
||||
assert!(got.contains_key(hash), "content of {original} differs");
|
||||
}
|
||||
}
|
||||
assert!(out.path().join("guara-manifesto.json").is_file());
|
||||
|
||||
let empty = explorer::Selection {
|
||||
folders: vec!["Nada/Aqui".into()],
|
||||
..Default::default()
|
||||
};
|
||||
let e = eng
|
||||
.start_recover(
|
||||
&job,
|
||||
None,
|
||||
None,
|
||||
Some(empty),
|
||||
out.path().to_str().unwrap(),
|
||||
recover::RecoverLayout::Folders,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(e.to_string().contains("nenhum arquivo selecionado"), "{e}");
|
||||
}
|
||||
Reference in new issue
Block a user