Fix extracting password protected archives

Closes: #1157

The fix splits the "canceled" and "failed" states for OperationError. It
also preserves that state because some functions overwrote the state by
rewrapping the error.
This commit is contained in:
Josh Megnauth 2025-09-01 01:28:26 -04:00 committed by Jeremy Soller
parent cf2e2faf3c
commit d8acbd2ce0
7 changed files with 341 additions and 189 deletions

View file

@ -7,6 +7,8 @@ use std::time::Instant;
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
use walkdir::WalkDir;
use crate::operation::OperationError;
use super::{copy_unique_path, Controller, OperationSelection, ReplaceResult};
pub enum Method {
@ -52,11 +54,14 @@ impl Context {
&mut self,
from_to_pairs: Vec<(PathBuf, PathBuf)>,
method: Method,
) -> Result<bool, String> {
) -> Result<bool, OperationError> {
let mut ops = Vec::new();
let mut cleanup_ops = Vec::new();
for (from_parent, to_parent) in from_to_pairs {
self.controller.check().await?;
self.controller
.check()
.await
.map_err(|s| OperationError::from_state(s, &self.controller))?;
if from_parent == to_parent {
// Skip matching source and destination
@ -64,10 +69,16 @@ impl Context {
}
for entry in WalkDir::new(&from_parent).into_iter() {
self.controller.check().await?;
self.controller
.check()
.await
.map_err(|s| OperationError::from_state(s, &self.controller))?;
let entry = entry.map_err(|err| {
format!("failed to walk directory {:?}: {}", from_parent, err)
OperationError::from_err(
format!("failed to walk directory {:?}: {}", from_parent, err),
&self.controller,
)
})?;
let file_type = entry.file_type();
let from = entry.into_path();
@ -79,21 +90,31 @@ impl Context {
Method::Move { cross_device_copy } => OpKind::Move { cross_device_copy },
}
} else if file_type.is_symlink() {
let target = fs::read_link(&from)
.map_err(|err| format!("failed to read link {:?}: {}", from, err))?;
let target = fs::read_link(&from).map_err(|err| {
OperationError::from_err(
format!("failed to read link {:?}: {}", from, err),
&self.controller,
)
})?;
OpKind::Symlink { target }
} else {
//TODO: present dialog and allow continue
return Err(format!("{} is not a known file type", from.display()));
return Err(OperationError::from_err(
format!("{} is not a known file type", from.display()),
&self.controller,
));
};
let to = if from == from_parent {
// When copying a file, from matches from_parent, and to_parent must be used
to_parent.clone()
} else {
let relative = from.strip_prefix(&from_parent).map_err(|err| {
format!(
"failed to remove prefix {:?} from {:?}: {}",
from_parent, from, err
OperationError::from_err(
format!(
"failed to remove prefix {:?} from {:?}: {}",
from_parent, from, err
),
&self.controller,
)
})?;
//TODO: ensure to is inside of to_parent?
@ -127,7 +148,10 @@ impl Context {
let total_ops = ops.len();
for (current_ops, mut op) in ops.into_iter().enumerate() {
self.controller.check().await?;
self.controller
.check()
.await
.map_err(|s| OperationError::from_state(s, &self.controller))?;
let progress = Progress {
current_ops,
@ -137,9 +161,12 @@ impl Context {
};
(self.on_progress)(&op, &progress);
if op.run(self, progress).await.map_err(|err| {
format!(
"failed to {:?} {:?} to {:?}: {}",
op.kind, op.from, op.to, err
OperationError::from_err(
format!(
"failed to {:?} {:?} to {:?}: {}",
op.kind, op.from, op.to, err
),
&self.controller,
)
})? {
// The from path is ignored in the operation selection if it is a top level item
@ -336,9 +363,9 @@ impl Op {
(ctx.on_progress)(self, &progress);
// Also check if the progress was cancelled.
if let Err(why) = ctx.controller.check().await {
if let Err(state) = ctx.controller.check().await {
ctx.buf = buf_out;
return Err(why.into());
return Err(OperationError::from_state(state, &ctx.controller).into());
}
}