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:
parent
cf2e2faf3c
commit
d8acbd2ce0
7 changed files with 341 additions and 189 deletions
|
|
@ -185,6 +185,7 @@ no-history = No items in history.
|
||||||
pending = Pending
|
pending = Pending
|
||||||
progress = {$percent}%
|
progress = {$percent}%
|
||||||
progress-cancelled = {$percent}%, cancelled
|
progress-cancelled = {$percent}%, cancelled
|
||||||
|
progress-failed = {$percent}%, failed
|
||||||
progress-paused = {$percent}%, paused
|
progress-paused = {$percent}%, paused
|
||||||
failed = Failed
|
failed = Failed
|
||||||
complete = Complete
|
complete = Complete
|
||||||
|
|
|
||||||
|
|
@ -47,53 +47,58 @@ pub fn extract(
|
||||||
controller: &Controller,
|
controller: &Controller,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
let mime = mime_for_path(path, None, false);
|
let mime = mime_for_path(path, None, false);
|
||||||
let controller = controller.clone();
|
|
||||||
let password = password.clone();
|
let password = password.clone();
|
||||||
match mime.essence_str() {
|
match mime.essence_str() {
|
||||||
"application/gzip" | "application/x-compressed-tar" => OpReader::new(path, controller)
|
"application/gzip" | "application/x-compressed-tar" => {
|
||||||
.map(io::BufReader::new)
|
OpReader::new(path, controller.clone())
|
||||||
.map(flate2::read::GzDecoder::new)
|
.map(io::BufReader::new)
|
||||||
.map(tar::Archive::new)
|
.map(flate2::read::GzDecoder::new)
|
||||||
.and_then(|mut archive| archive.unpack(&new_dir))
|
.map(tar::Archive::new)
|
||||||
.map_err(OperationError::from_str)?,
|
.and_then(|mut archive| archive.unpack(new_dir))
|
||||||
"application/x-tar" => OpReader::new(path, controller)
|
.map_err(|e| OperationError::from_err(e, controller))?
|
||||||
|
}
|
||||||
|
"application/x-tar" => OpReader::new(path, controller.clone())
|
||||||
.map(io::BufReader::new)
|
.map(io::BufReader::new)
|
||||||
.map(tar::Archive::new)
|
.map(tar::Archive::new)
|
||||||
.and_then(|mut archive| archive.unpack(&new_dir))
|
.and_then(|mut archive| archive.unpack(new_dir))
|
||||||
.map_err(OperationError::from_str)?,
|
.map_err(|e| OperationError::from_err(e, controller))?,
|
||||||
"application/zip" => fs::File::open(path)
|
"application/zip" => fs::File::open(path)
|
||||||
.map(io::BufReader::new)
|
.map(io::BufReader::new)
|
||||||
.map(zip::ZipArchive::new)
|
.map(zip::ZipArchive::new)
|
||||||
.map_err(OperationError::from_str)?
|
.map_err(|e| OperationError::from_err(e, controller))?
|
||||||
.and_then(move |mut archive| zip_extract(&mut archive, &new_dir, password, controller))
|
.and_then(move |mut archive| {
|
||||||
|
zip_extract(&mut archive, new_dir, password, controller.clone())
|
||||||
|
})
|
||||||
.map_err(|e| match e {
|
.map_err(|e| match e {
|
||||||
ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED)
|
ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED)
|
||||||
| ZipError::InvalidPassword => OperationError {
|
| ZipError::InvalidPassword => {
|
||||||
kind: OperationErrorType::PasswordRequired,
|
OperationError::from_kind(OperationErrorType::PasswordRequired, controller)
|
||||||
},
|
}
|
||||||
_ => OperationError::from_str(e),
|
_ => OperationError::from_err(e, controller),
|
||||||
})?,
|
})?,
|
||||||
#[cfg(feature = "bzip2")]
|
#[cfg(feature = "bzip2")]
|
||||||
"application/x-bzip"
|
"application/x-bzip"
|
||||||
| "application/x-bzip-compressed-tar"
|
| "application/x-bzip-compressed-tar"
|
||||||
| "application/x-bzip2"
|
| "application/x-bzip2"
|
||||||
| "application/x-bzip2-compressed-tar" => OpReader::new(path, controller)
|
| "application/x-bzip2-compressed-tar" => OpReader::new(path, controller.clone())
|
||||||
.map(io::BufReader::new)
|
.map(io::BufReader::new)
|
||||||
.map(bzip2::read::BzDecoder::new)
|
.map(bzip2::read::BzDecoder::new)
|
||||||
.map(tar::Archive::new)
|
.map(tar::Archive::new)
|
||||||
.and_then(|mut archive| archive.unpack(&new_dir))
|
.and_then(|mut archive| archive.unpack(new_dir))
|
||||||
.map_err(OperationError::from_str)?,
|
.map_err(|e| OperationError::from_err(e, controller))?,
|
||||||
#[cfg(feature = "xz2")]
|
#[cfg(feature = "xz2")]
|
||||||
"application/x-xz" | "application/x-xz-compressed-tar" => OpReader::new(path, controller)
|
"application/x-xz" | "application/x-xz-compressed-tar" => {
|
||||||
.map(io::BufReader::new)
|
OpReader::new(path, controller.clone())
|
||||||
.map(xz2::read::XzDecoder::new)
|
.map(io::BufReader::new)
|
||||||
.map(tar::Archive::new)
|
.map(xz2::read::XzDecoder::new)
|
||||||
.and_then(|mut archive| archive.unpack(&new_dir))
|
.map(tar::Archive::new)
|
||||||
.map_err(OperationError::from_str)?,
|
.and_then(|mut archive| archive.unpack(new_dir))
|
||||||
_ => Err(OperationError::from_str(format!(
|
.map_err(|e| OperationError::from_err(e, controller))?
|
||||||
"unsupported mime type {:?}",
|
}
|
||||||
mime
|
_ => Err(OperationError::from_err(
|
||||||
)))?,
|
format!("unsupported mime type {:?}", mime),
|
||||||
|
controller,
|
||||||
|
))?,
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +140,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
controller
|
controller
|
||||||
.check()
|
.check()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
.map_err(|s| io::Error::other(OperationError::from_state(s, &controller)))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
controller.set_progress((i as f32) / total_files as f32);
|
controller.set_progress((i as f32) / total_files as f32);
|
||||||
|
|
@ -143,11 +148,10 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
let mut file = match &password {
|
let mut file = match &password {
|
||||||
None => archive.by_index(i),
|
None => archive.by_index(i),
|
||||||
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
|
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
|
||||||
}
|
}?;
|
||||||
.map_err(|e| e)?;
|
|
||||||
let filepath = file
|
let filepath = file
|
||||||
.enclosed_name()
|
.enclosed_name()
|
||||||
.ok_or(ZipError::InvalidArchive("Invalid file path".into()))?;
|
.ok_or(ZipError::InvalidArchive("Invalid file path"))?;
|
||||||
|
|
||||||
let outpath = directory.as_ref().join(filepath);
|
let outpath = directory.as_ref().join(filepath);
|
||||||
|
|
||||||
|
|
@ -206,8 +210,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
let mut file = match &password {
|
let mut file = match &password {
|
||||||
None => archive.by_index(i),
|
None => archive.by_index(i),
|
||||||
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
|
Some(pwd) => archive.by_index_decrypt(i, pwd.as_bytes()),
|
||||||
}
|
}?;
|
||||||
.map_err(|e| e)?;
|
|
||||||
|
|
||||||
// create all pending dirs
|
// create all pending dirs
|
||||||
while let Some(pending_dir) = pending_directory_creates.pop_front() {
|
while let Some(pending_dir) = pending_directory_creates.pop_front() {
|
||||||
|
|
@ -226,7 +229,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
|
||||||
controller
|
controller
|
||||||
.check()
|
.check()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
.map_err(|s| io::Error::other(OperationError::from_state(s, &controller)))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let count = file.read(&mut buffer)?;
|
let count = file.read(&mut buffer)?;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use crate::fl;
|
|
||||||
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::sync::Notify;
|
use tokio::sync::Notify;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ControllerState {
|
pub enum ControllerState {
|
||||||
Cancelled,
|
Cancelled,
|
||||||
|
Failed,
|
||||||
Paused,
|
Paused,
|
||||||
Running,
|
Running,
|
||||||
}
|
}
|
||||||
|
|
@ -37,10 +36,11 @@ impl Default for Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
pub async fn check(&self) -> Result<(), String> {
|
pub async fn check(&self) -> Result<(), ControllerState> {
|
||||||
loop {
|
loop {
|
||||||
match self.state() {
|
match self.state() {
|
||||||
ControllerState::Cancelled => return Err(fl!("cancelled")),
|
ControllerState::Cancelled => return Err(ControllerState::Cancelled),
|
||||||
|
ControllerState::Failed => return Err(ControllerState::Failed),
|
||||||
ControllerState::Paused => (),
|
ControllerState::Paused => (),
|
||||||
ControllerState::Running => return Ok(()),
|
ControllerState::Running => return Ok(()),
|
||||||
}
|
}
|
||||||
|
|
@ -74,6 +74,10 @@ impl Controller {
|
||||||
self.set_state(ControllerState::Cancelled);
|
self.set_state(ControllerState::Cancelled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_failed(&self) -> bool {
|
||||||
|
matches!(self.state(), ControllerState::Failed)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_paused(&self) -> bool {
|
pub fn is_paused(&self) -> bool {
|
||||||
matches!(self.state(), ControllerState::Paused)
|
matches!(self.state(), ControllerState::Paused)
|
||||||
}
|
}
|
||||||
|
|
@ -83,7 +87,7 @@ impl Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unpause(&self) {
|
pub fn unpause(&self) {
|
||||||
if !self.is_cancelled() {
|
if !self.is_cancelled() | !self.is_failed() {
|
||||||
self.set_state(ControllerState::Running);
|
self.set_state(ControllerState::Running);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -100,8 +104,8 @@ impl Clone for Controller {
|
||||||
|
|
||||||
impl Drop for Controller {
|
impl Drop for Controller {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Cancel operations if primary controller is dropped
|
// Cancel operations if primary controller is dropped and controller is still running
|
||||||
if self.primary {
|
if self.primary && self.state() != ControllerState::Failed {
|
||||||
self.cancel();
|
self.cancel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ use crate::{
|
||||||
tab,
|
tab,
|
||||||
};
|
};
|
||||||
use cosmic::iced::futures::{channel::mpsc::Sender, SinkExt};
|
use cosmic::iced::futures::{channel::mpsc::Sender, SinkExt};
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
fmt::Formatter,
|
||||||
fs,
|
fs,
|
||||||
io::{self, Read, Write},
|
io::{self, Read, Write},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
|
|
@ -90,8 +90,10 @@ async fn copy_or_move(
|
||||||
controller: Controller,
|
controller: Controller,
|
||||||
) -> Result<OperationSelection, OperationError> {
|
) -> Result<OperationSelection, OperationError> {
|
||||||
let msg_tx = msg_tx.clone();
|
let msg_tx = msg_tx.clone();
|
||||||
|
let controller_c = controller.clone();
|
||||||
|
|
||||||
compio::runtime::spawn(async move {
|
compio::runtime::spawn(async move {
|
||||||
|
let controller = controller_c;
|
||||||
log::info!(
|
log::info!(
|
||||||
"{} {:?} to {:?}",
|
"{} {:?} to {:?}",
|
||||||
match method {
|
match method {
|
||||||
|
|
@ -150,6 +152,7 @@ async fn copy_or_move(
|
||||||
let mut context = Context::new(controller.clone());
|
let mut context = Context::new(controller.clone());
|
||||||
|
|
||||||
{
|
{
|
||||||
|
let controller = controller.clone();
|
||||||
context = context.on_progress(move |_op, progress| {
|
context = context.on_progress(move |_op, progress| {
|
||||||
let item_progress = match progress.total_bytes {
|
let item_progress = match progress.total_bytes {
|
||||||
Some(total_bytes) => {
|
Some(total_bytes) => {
|
||||||
|
|
@ -177,14 +180,12 @@ async fn copy_or_move(
|
||||||
|
|
||||||
context
|
context
|
||||||
.recursive_copy_or_move(from_to_pairs, method)
|
.recursive_copy_or_move(from_to_pairs, method)
|
||||||
.await
|
.await?;
|
||||||
.map_err(OperationError::from_str)?;
|
|
||||||
|
|
||||||
Result::<OperationSelection, OperationError>::Ok(context.op_sel)
|
Result::<OperationSelection, OperationError>::Ok(context.op_sel)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
.map_err(OperationError::from_str)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn copy_unique_path(from: &Path, to: &Path) -> PathBuf {
|
fn copy_unique_path(from: &Path, to: &Path) -> PathBuf {
|
||||||
|
|
@ -374,13 +375,42 @@ pub struct OperationError {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OperationError {
|
impl OperationError {
|
||||||
pub fn from_str<T: ToString>(err: T) -> Self {
|
pub fn from_state(state: ControllerState, controller: &Controller) -> Self {
|
||||||
|
let message = if state == ControllerState::Failed {
|
||||||
|
controller.set_state(ControllerState::Failed);
|
||||||
|
fl!("failed")
|
||||||
|
} else {
|
||||||
|
controller.cancel();
|
||||||
|
fl!("cancelled")
|
||||||
|
};
|
||||||
|
|
||||||
|
Self {
|
||||||
|
kind: OperationErrorType::Generic(message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_err<T: ToString>(err: T, controller: &Controller) -> Self {
|
||||||
|
controller.set_state(ControllerState::Failed);
|
||||||
|
|
||||||
OperationError {
|
OperationError {
|
||||||
kind: OperationErrorType::Generic(err.to_string()),
|
kind: OperationErrorType::Generic(err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn from_kind(kind: OperationErrorType, controller: &Controller) -> Self {
|
||||||
|
controller.set_state(ControllerState::Failed);
|
||||||
|
OperationError { kind }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_msg(m: impl Into<String>) -> Self {
|
||||||
|
OperationError {
|
||||||
|
kind: OperationErrorType::Generic(m.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for OperationError {}
|
||||||
|
|
||||||
impl std::fmt::Display for OperationError {
|
impl std::fmt::Display for OperationError {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
match &self.kind {
|
match &self.kind {
|
||||||
|
|
@ -397,6 +427,7 @@ impl Operation {
|
||||||
ControllerState::Running => fl!("progress", percent = percent),
|
ControllerState::Running => fl!("progress", percent = percent),
|
||||||
ControllerState::Paused => fl!("progress-paused", percent = percent),
|
ControllerState::Paused => fl!("progress-paused", percent = percent),
|
||||||
ControllerState::Cancelled => fl!("progress-cancelled", percent = percent),
|
ControllerState::Cancelled => fl!("progress-cancelled", percent = percent),
|
||||||
|
ControllerState::Failed => fl!("progress-failed", percent = percent),
|
||||||
};
|
};
|
||||||
match self {
|
match self {
|
||||||
Self::Compress { paths, to, .. } => fl!(
|
Self::Compress { paths, to, .. } => fl!(
|
||||||
|
|
@ -583,13 +614,15 @@ impl Operation {
|
||||||
archive_type,
|
archive_type,
|
||||||
password,
|
password,
|
||||||
} => {
|
} => {
|
||||||
|
let controller_c = controller.clone();
|
||||||
compio::runtime::spawn_blocking(
|
compio::runtime::spawn_blocking(
|
||||||
move || -> Result<OperationSelection, OperationError> {
|
move || -> Result<OperationSelection, OperationError> {
|
||||||
|
let controller = controller_c;
|
||||||
let Some(relative_root) = to.parent() else {
|
let Some(relative_root) = to.parent() else {
|
||||||
return Err(OperationError::from_str(format!(
|
return Err(OperationError::from_err(
|
||||||
"path {:?} has no parent directory",
|
format!("path {:?} has no parent directory", to),
|
||||||
to
|
&controller,
|
||||||
)));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
let op_sel = OperationSelection {
|
let op_sel = OperationSelection {
|
||||||
|
|
@ -602,7 +635,8 @@ impl Operation {
|
||||||
if path.is_dir() {
|
if path.is_dir() {
|
||||||
let new_paths_it = WalkDir::new(path).into_iter();
|
let new_paths_it = WalkDir::new(path).into_iter();
|
||||||
for entry in new_paths_it.skip(1) {
|
for entry in new_paths_it.skip(1) {
|
||||||
let entry = entry.map_err(OperationError::from_str)?;
|
let entry = entry
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
paths.push(entry.into_path());
|
paths.push(entry.into_path());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -619,40 +653,50 @@ impl Operation {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.map(tar::Builder::new)
|
.map(tar::Builder::new)
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
|
|
||||||
let total_paths = paths.len();
|
let total_paths = paths.len();
|
||||||
for (i, path) in paths.iter().enumerate() {
|
for (i, path) in paths.iter().enumerate() {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
controller.check().await.map_err(OperationError::from_str)
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|e| OperationError::from_state(e, &controller))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
controller.set_progress((i as f32) / total_paths as f32);
|
controller.set_progress((i as f32) / total_paths as f32);
|
||||||
|
|
||||||
if let Some(relative_path) = path
|
if let Some(relative_path) = path
|
||||||
.strip_prefix(relative_root)
|
.strip_prefix(relative_root)
|
||||||
.map_err(OperationError::from_str)?
|
.map_err(|e| OperationError::from_err(e, &controller))?
|
||||||
.to_str()
|
.to_str()
|
||||||
{
|
{
|
||||||
archive
|
archive
|
||||||
.append_path_with_name(path, relative_path)
|
.append_path_with_name(path, relative_path)
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| {
|
||||||
|
OperationError::from_err(e, &controller)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
archive.finish().map_err(OperationError::from_str)?;
|
archive
|
||||||
|
.finish()
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
ArchiveType::Zip => {
|
ArchiveType::Zip => {
|
||||||
let mut archive = fs::File::create(&to)
|
let mut archive = fs::File::create(&to)
|
||||||
.map(io::BufWriter::new)
|
.map(io::BufWriter::new)
|
||||||
.map(zip::ZipWriter::new)
|
.map(zip::ZipWriter::new)
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
|
|
||||||
let total_paths = paths.len();
|
let total_paths = paths.len();
|
||||||
let mut buffer = vec![0; 4 * 1024 * 1024];
|
let mut buffer = vec![0; 4 * 1024 * 1024];
|
||||||
for (i, path) in paths.iter().enumerate() {
|
for (i, path) in paths.iter().enumerate() {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
controller.check().await.map_err(OperationError::from_str)
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
controller.set_progress((i as f32) / total_paths as f32);
|
controller.set_progress((i as f32) / total_paths as f32);
|
||||||
|
|
@ -666,15 +710,16 @@ impl Operation {
|
||||||
}
|
}
|
||||||
if let Some(relative_path) = path
|
if let Some(relative_path) = path
|
||||||
.strip_prefix(relative_root)
|
.strip_prefix(relative_root)
|
||||||
.map_err(OperationError::from_str)?
|
.map_err(|e| OperationError::from_err(e, &controller))?
|
||||||
.to_str()
|
.to_str()
|
||||||
{
|
{
|
||||||
if path.is_file() {
|
if path.is_file() {
|
||||||
let mut file = fs::File::open(path)
|
let mut file = fs::File::open(path).map_err(|e| {
|
||||||
.map_err(OperationError::from_str)?;
|
OperationError::from_err(e, &controller)
|
||||||
let metadata = file
|
})?;
|
||||||
.metadata()
|
let metadata = file.metadata().map_err(|e| {
|
||||||
.map_err(OperationError::from_str)?;
|
OperationError::from_err(e, &controller)
|
||||||
|
})?;
|
||||||
let total = metadata.len();
|
let total = metadata.len();
|
||||||
if total >= 4 * 1024 * 1024 * 1024 {
|
if total >= 4 * 1024 * 1024 * 1024 {
|
||||||
// The large file option must be enabled for files above 4 GiB
|
// The large file option must be enabled for files above 4 GiB
|
||||||
|
|
@ -688,25 +733,27 @@ impl Operation {
|
||||||
}
|
}
|
||||||
archive
|
archive
|
||||||
.start_file(relative_path, zip_options)
|
.start_file(relative_path, zip_options)
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| {
|
||||||
|
OperationError::from_err(e, &controller)
|
||||||
|
})?;
|
||||||
let mut current = 0;
|
let mut current = 0;
|
||||||
loop {
|
loop {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
controller
|
controller.check().await.map_err(|s| {
|
||||||
.check()
|
OperationError::from_state(s, &controller)
|
||||||
.await
|
})
|
||||||
.map_err(OperationError::from_str)
|
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let count = file
|
let count =
|
||||||
.read(&mut buffer)
|
file.read(&mut buffer).map_err(|e| {
|
||||||
.map_err(OperationError::from_str)?;
|
OperationError::from_err(e, &controller)
|
||||||
|
})?;
|
||||||
if count == 0 {
|
if count == 0 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
archive
|
archive.write_all(&buffer[..count]).map_err(
|
||||||
.write_all(&buffer[..count])
|
|e| OperationError::from_err(e, &controller),
|
||||||
.map_err(OperationError::from_str)?;
|
)?;
|
||||||
current += count;
|
current += count;
|
||||||
|
|
||||||
let file_progress = current as f32 / total as f32;
|
let file_progress = current as f32 / total as f32;
|
||||||
|
|
@ -717,12 +764,16 @@ impl Operation {
|
||||||
} else {
|
} else {
|
||||||
archive
|
archive
|
||||||
.add_directory(relative_path, zip_options)
|
.add_directory(relative_path, zip_options)
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| {
|
||||||
|
OperationError::from_err(e, &controller)
|
||||||
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
archive.finish().map_err(OperationError::from_str)?;
|
archive
|
||||||
|
.finish()
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -731,7 +782,6 @@ impl Operation {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
.map_err(OperationError::from_str)
|
|
||||||
}
|
}
|
||||||
Self::Copy { paths, to } => {
|
Self::Copy { paths, to } => {
|
||||||
copy_or_move(paths, to, Method::Copy, msg_tx, controller).await
|
copy_or_move(paths, to, Method::Copy, msg_tx, controller).await
|
||||||
|
|
@ -740,15 +790,17 @@ impl Operation {
|
||||||
let total = paths.len();
|
let total = paths.len();
|
||||||
for (i, path) in paths.into_iter().enumerate() {
|
for (i, path) in paths.into_iter().enumerate() {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
controller.check().await.map_err(OperationError::from_str)
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
controller.set_progress((i as f32) / (total as f32));
|
controller.set_progress((i as f32) / (total as f32));
|
||||||
|
|
||||||
let _items_opt = compio::runtime::spawn_blocking(|| trash::delete(path))
|
let _items_opt = compio::runtime::spawn_blocking(|| trash::delete(path))
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?;
|
||||||
.map_err(OperationError::from_str)?;
|
|
||||||
//TODO: items_opt allows for easy restore
|
//TODO: items_opt allows for easy restore
|
||||||
}
|
}
|
||||||
Ok(OperationSelection::default())
|
Ok(OperationSelection::default())
|
||||||
|
|
@ -764,23 +816,28 @@ impl Operation {
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
{
|
{
|
||||||
|
let controller_clone = controller.clone();
|
||||||
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
||||||
|
let controller = controller_clone;
|
||||||
let count = items.len();
|
let count = items.len();
|
||||||
for (i, item) in items.into_iter().enumerate() {
|
for (i, item) in items.into_iter().enumerate() {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
controller.check().await.map_err(OperationError::from_str)
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
controller.set_progress(i as f32 / count as f32);
|
controller.set_progress(i as f32 / count as f32);
|
||||||
|
|
||||||
trash::os_limited::purge_all([item])
|
trash::os_limited::purge_all([item])
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
Ok(OperationSelection::default())
|
Ok(OperationSelection::default())
|
||||||
}
|
}
|
||||||
|
|
@ -795,24 +852,30 @@ impl Operation {
|
||||||
)
|
)
|
||||||
))]
|
))]
|
||||||
{
|
{
|
||||||
|
let controller_clone = controller.clone();
|
||||||
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
||||||
let items = trash::os_limited::list().map_err(OperationError::from_str)?;
|
let controller = controller_clone;
|
||||||
|
let items = trash::os_limited::list()
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
let count = items.len();
|
let count = items.len();
|
||||||
for (i, item) in items.into_iter().enumerate() {
|
for (i, item) in items.into_iter().enumerate() {
|
||||||
futures::executor::block_on(async {
|
futures::executor::block_on(async {
|
||||||
controller.check().await.map_err(OperationError::from_str)
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
controller.set_progress(i as f32 / count as f32);
|
controller.set_progress(i as f32 / count as f32);
|
||||||
|
|
||||||
trash::os_limited::purge_all([item])
|
trash::os_limited::purge_all([item])
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
Ok(OperationSelection::default())
|
Ok(OperationSelection::default())
|
||||||
}
|
}
|
||||||
|
|
@ -820,40 +883,46 @@ impl Operation {
|
||||||
paths,
|
paths,
|
||||||
to,
|
to,
|
||||||
password,
|
password,
|
||||||
} => compio::runtime::spawn_blocking(
|
} => {
|
||||||
move || -> Result<OperationSelection, OperationError> {
|
let controller_clone = controller.clone();
|
||||||
let total_paths = paths.len();
|
compio::runtime::spawn_blocking(
|
||||||
let mut op_sel = OperationSelection::default();
|
move || -> Result<OperationSelection, OperationError> {
|
||||||
for (i, path) in paths.iter().enumerate() {
|
let controller = controller_clone;
|
||||||
futures::executor::block_on(async {
|
let total_paths = paths.len();
|
||||||
controller.check().await.map_err(OperationError::from_str)
|
let mut op_sel = OperationSelection::default();
|
||||||
})?;
|
for (i, path) in paths.iter().enumerate() {
|
||||||
|
futures::executor::block_on(async {
|
||||||
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))
|
||||||
|
})?;
|
||||||
|
|
||||||
controller.set_progress((i as f32) / total_paths as f32);
|
controller.set_progress((i as f32) / total_paths as f32);
|
||||||
|
|
||||||
if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) {
|
if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) {
|
||||||
let dir_name = get_directory_name(file_name);
|
let dir_name = get_directory_name(file_name);
|
||||||
let mut new_dir = to.join(dir_name);
|
let mut new_dir = to.join(dir_name);
|
||||||
|
|
||||||
if new_dir.exists() {
|
if new_dir.exists() {
|
||||||
if let Some(new_dir_parent) = new_dir.parent() {
|
if let Some(new_dir_parent) = new_dir.parent() {
|
||||||
new_dir = copy_unique_path(&new_dir, new_dir_parent);
|
new_dir = copy_unique_path(&new_dir, new_dir_parent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
op_sel.ignored.push(path.clone());
|
||||||
|
op_sel.selected.push(new_dir.clone());
|
||||||
|
|
||||||
|
crate::archive::extract(path, &new_dir, &password, &controller)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
op_sel.ignored.push(path.clone());
|
|
||||||
op_sel.selected.push(new_dir.clone());
|
|
||||||
|
|
||||||
crate::archive::extract(path, &new_dir, &password, &controller)?;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(op_sel)
|
Ok(op_sel)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
}
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?,
|
||||||
.map_err(OperationError::from_str),
|
|
||||||
Self::Move {
|
Self::Move {
|
||||||
paths,
|
paths,
|
||||||
to,
|
to,
|
||||||
|
|
@ -868,36 +937,51 @@ impl Operation {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
Self::NewFolder { path } => compio::runtime::spawn(async move {
|
Self::NewFolder { path } => {
|
||||||
controller.check().await.map_err(OperationError::from_str)?;
|
let controller_clone = controller.clone();
|
||||||
compio::fs::create_dir(&path)
|
compio::runtime::spawn(async move {
|
||||||
.await
|
let controller = controller_clone;
|
||||||
.map_err(OperationError::from_str)?;
|
controller
|
||||||
Result::<_, OperationError>::Ok(OperationSelection {
|
.check()
|
||||||
ignored: Vec::new(),
|
.await
|
||||||
selected: vec![path],
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
|
compio::fs::create_dir(&path)
|
||||||
|
.await
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
|
Result::<_, OperationError>::Ok(OperationSelection {
|
||||||
|
ignored: Vec::new(),
|
||||||
|
selected: vec![path],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?,
|
||||||
.map_err(OperationError::from_str),
|
Self::NewFile { path } => {
|
||||||
Self::NewFile { path } => compio::runtime::spawn(async move {
|
let controller_clone = controller.clone();
|
||||||
controller.check().await.map_err(OperationError::from_str)?;
|
compio::runtime::spawn(async move {
|
||||||
compio::fs::File::create(&path)
|
let controller = controller_clone;
|
||||||
.await
|
controller
|
||||||
.map_err(OperationError::from_str)?;
|
.check()
|
||||||
Result::<_, OperationError>::Ok(OperationSelection {
|
.await
|
||||||
ignored: Vec::new(),
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
selected: vec![path],
|
compio::fs::File::create(&path)
|
||||||
|
.await
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
|
Result::<_, OperationError>::Ok(OperationSelection {
|
||||||
|
ignored: Vec::new(),
|
||||||
|
selected: vec![path],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?,
|
||||||
.map_err(OperationError::from_str),
|
|
||||||
Self::PermanentlyDelete { paths } => {
|
Self::PermanentlyDelete { paths } => {
|
||||||
let total = paths.len();
|
let total = paths.len();
|
||||||
for (idx, path) in paths.into_iter().enumerate() {
|
for (idx, path) in paths.into_iter().enumerate() {
|
||||||
controller.check().await.map_err(OperationError::from_str)?;
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
|
|
||||||
controller.set_progress((idx as f32) / (total as f32));
|
controller.set_progress((idx as f32) / (total as f32));
|
||||||
|
|
||||||
|
|
@ -914,8 +998,8 @@ impl Operation {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(OperationError::from_str)?
|
.map_err(|e| OperationError::from_err(e, &controller))?
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(OperationSelection::default())
|
Ok(OperationSelection::default())
|
||||||
|
|
@ -926,35 +1010,45 @@ impl Operation {
|
||||||
recently_used_xbel::remove_recently_used(&path_refs)
|
recently_used_xbel::remove_recently_used(&path_refs)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(OperationError::from_str)?
|
.map_err(|e| OperationError::from_err(e, &controller))?
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
|
|
||||||
Ok(OperationSelection::default())
|
Ok(OperationSelection::default())
|
||||||
}
|
}
|
||||||
Self::Rename { from, to } => compio::runtime::spawn(async move {
|
Self::Rename { from, to } => {
|
||||||
controller.check().await.map_err(OperationError::from_str)?;
|
let controller_clone = controller.clone();
|
||||||
compio::fs::rename(&from, &to)
|
|
||||||
.await
|
compio::runtime::spawn(async move {
|
||||||
.map_err(OperationError::from_str)?;
|
let controller = controller_clone;
|
||||||
Result::<_, OperationError>::Ok(OperationSelection {
|
controller
|
||||||
ignored: vec![from],
|
.check()
|
||||||
selected: vec![to],
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
|
compio::fs::rename(&from, &to)
|
||||||
|
.await
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
|
Result::<_, OperationError>::Ok(OperationSelection {
|
||||||
|
ignored: vec![from],
|
||||||
|
selected: vec![to],
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?,
|
||||||
.map_err(OperationError::from_str),
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
Self::Restore { .. } => {
|
Self::Restore { .. } => {
|
||||||
// TODO: add support for macos
|
// TODO: add support for macos
|
||||||
return OperationError::from_str("Restoring from trash is not supported on macos");
|
return OperationError::from_msg("Restoring from trash is not supported on macos");
|
||||||
}
|
}
|
||||||
#[cfg(not(target_os = "macos"))]
|
#[cfg(not(target_os = "macos"))]
|
||||||
Self::Restore { items } => {
|
Self::Restore { items } => {
|
||||||
let total = items.len();
|
let total = items.len();
|
||||||
let mut paths = Vec::with_capacity(total);
|
let mut paths = Vec::with_capacity(total);
|
||||||
for (i, item) in items.into_iter().enumerate() {
|
for (i, item) in items.into_iter().enumerate() {
|
||||||
controller.check().await.map_err(OperationError::from_str)?;
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
|
|
||||||
controller.set_progress((i as f32) / (total as f32));
|
controller.set_progress((i as f32) / (total as f32));
|
||||||
|
|
||||||
|
|
@ -963,7 +1057,7 @@ impl Operation {
|
||||||
compio::runtime::spawn_blocking(|| trash::os_limited::restore_all([item]))
|
compio::runtime::spawn_blocking(|| trash::os_limited::restore_all([item]))
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
Ok(OperationSelection {
|
Ok(OperationSelection {
|
||||||
ignored: Vec::new(),
|
ignored: Vec::new(),
|
||||||
|
|
@ -971,50 +1065,63 @@ impl Operation {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Self::SetExecutableAndLaunch { path } => {
|
Self::SetExecutableAndLaunch { path } => {
|
||||||
controller.check().await.map_err(OperationError::from_str)?;
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
|
|
||||||
|
let controller_clone = controller.clone();
|
||||||
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
||||||
|
let controller = controller_clone;
|
||||||
//TODO: what to do on non-Unix systems?
|
//TODO: what to do on non-Unix systems?
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
|
|
||||||
let mut perms = fs::metadata(&path)
|
let mut perms = fs::metadata(&path)
|
||||||
.map_err(OperationError::from_str)?
|
.map_err(|e| OperationError::from_err(e, &controller))?
|
||||||
.permissions();
|
.permissions();
|
||||||
let current_mode = perms.mode();
|
let current_mode = perms.mode();
|
||||||
let new_mode = current_mode | 0o111;
|
let new_mode = current_mode | 0o111;
|
||||||
perms.set_mode(new_mode);
|
perms.set_mode(new_mode);
|
||||||
fs::set_permissions(&path, perms).map_err(OperationError::from_str)?;
|
fs::set_permissions(&path, perms)
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut command = std::process::Command::new(path);
|
let mut command = std::process::Command::new(path);
|
||||||
spawn_detached(&mut command).map_err(OperationError::from_str)?;
|
spawn_detached(&mut command)
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
Ok(OperationSelection::default())
|
Ok(OperationSelection::default())
|
||||||
}
|
}
|
||||||
Self::SetPermissions { path, mode } => {
|
Self::SetPermissions { path, mode } => {
|
||||||
controller.check().await.map_err(OperationError::from_str)?;
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
|
|
||||||
|
let controller_clone = controller.clone();
|
||||||
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
compio::runtime::spawn_blocking(move || -> Result<(), OperationError> {
|
||||||
|
let controller = controller_clone;
|
||||||
//TODO: what to do on non-Unix systems?
|
//TODO: what to do on non-Unix systems?
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
{
|
{
|
||||||
use std::os::unix::fs::PermissionsExt;
|
use std::os::unix::fs::PermissionsExt;
|
||||||
let perms = fs::Permissions::from_mode(mode);
|
let perms = fs::Permissions::from_mode(mode);
|
||||||
fs::set_permissions(&path, perms).map_err(OperationError::from_str)?;
|
fs::set_permissions(&path, perms)
|
||||||
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.map_err(wrap_compio_spawn_error)?
|
.map_err(wrap_compio_spawn_error)?
|
||||||
.map_err(OperationError::from_str)?;
|
.map_err(|e| OperationError::from_err(e, &controller))?;
|
||||||
Ok(OperationSelection::default())
|
Ok(OperationSelection::default())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -1026,13 +1133,18 @@ impl Operation {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn wrap_compio_spawn_error(_unwind: Box<dyn std::any::Any + Send>) -> OperationError {
|
fn wrap_compio_spawn_error(err: Box<dyn std::any::Any + Send>) -> OperationError {
|
||||||
log::error!(
|
log::error!(
|
||||||
"compio runtime spawn failed: {}",
|
"compio runtime spawn failed: {}",
|
||||||
std::backtrace::Backtrace::capture()
|
std::backtrace::Backtrace::capture()
|
||||||
);
|
);
|
||||||
|
|
||||||
OperationError::from_str("compio runtime spawn failed")
|
// Preserve error if it's already an OperationError
|
||||||
|
if let Ok(err) = err.downcast() {
|
||||||
|
*err
|
||||||
|
} else {
|
||||||
|
OperationError::from_msg("compio runtime spawn failed")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
use std::{fs, io, path::Path};
|
use std::{fs, io, path::Path};
|
||||||
|
|
||||||
|
use crate::operation::OperationError;
|
||||||
|
|
||||||
use super::Controller;
|
use super::Controller;
|
||||||
|
|
||||||
// Special reader just for operations, handling cancel and progress
|
// Special reader just for operations, handling cancel and progress
|
||||||
|
|
@ -29,7 +31,7 @@ impl io::Read for OpReader {
|
||||||
self.controller
|
self.controller
|
||||||
.check()
|
.check()
|
||||||
.await
|
.await
|
||||||
.map_err(|err| io::Error::new(io::ErrorKind::Other, err))
|
.map_err(|s| io::Error::other(OperationError::from_state(s, &self.controller)))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let count = self.file.read(buf)?;
|
let count = self.file.read(buf)?;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ use std::time::Instant;
|
||||||
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
|
use std::{cell::Cell, error::Error, fs, ops::ControlFlow, path::PathBuf, rc::Rc};
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
|
use crate::operation::OperationError;
|
||||||
|
|
||||||
use super::{copy_unique_path, Controller, OperationSelection, ReplaceResult};
|
use super::{copy_unique_path, Controller, OperationSelection, ReplaceResult};
|
||||||
|
|
||||||
pub enum Method {
|
pub enum Method {
|
||||||
|
|
@ -52,11 +54,14 @@ impl Context {
|
||||||
&mut self,
|
&mut self,
|
||||||
from_to_pairs: Vec<(PathBuf, PathBuf)>,
|
from_to_pairs: Vec<(PathBuf, PathBuf)>,
|
||||||
method: Method,
|
method: Method,
|
||||||
) -> Result<bool, String> {
|
) -> Result<bool, OperationError> {
|
||||||
let mut ops = Vec::new();
|
let mut ops = Vec::new();
|
||||||
let mut cleanup_ops = Vec::new();
|
let mut cleanup_ops = Vec::new();
|
||||||
for (from_parent, to_parent) in from_to_pairs {
|
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 {
|
if from_parent == to_parent {
|
||||||
// Skip matching source and destination
|
// Skip matching source and destination
|
||||||
|
|
@ -64,10 +69,16 @@ impl Context {
|
||||||
}
|
}
|
||||||
|
|
||||||
for entry in WalkDir::new(&from_parent).into_iter() {
|
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| {
|
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 file_type = entry.file_type();
|
||||||
let from = entry.into_path();
|
let from = entry.into_path();
|
||||||
|
|
@ -79,21 +90,31 @@ impl Context {
|
||||||
Method::Move { cross_device_copy } => OpKind::Move { cross_device_copy },
|
Method::Move { cross_device_copy } => OpKind::Move { cross_device_copy },
|
||||||
}
|
}
|
||||||
} else if file_type.is_symlink() {
|
} else if file_type.is_symlink() {
|
||||||
let target = fs::read_link(&from)
|
let target = fs::read_link(&from).map_err(|err| {
|
||||||
.map_err(|err| format!("failed to read link {:?}: {}", from, err))?;
|
OperationError::from_err(
|
||||||
|
format!("failed to read link {:?}: {}", from, err),
|
||||||
|
&self.controller,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
OpKind::Symlink { target }
|
OpKind::Symlink { target }
|
||||||
} else {
|
} else {
|
||||||
//TODO: present dialog and allow continue
|
//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 {
|
let to = if from == from_parent {
|
||||||
// When copying a file, from matches from_parent, and to_parent must be used
|
// When copying a file, from matches from_parent, and to_parent must be used
|
||||||
to_parent.clone()
|
to_parent.clone()
|
||||||
} else {
|
} else {
|
||||||
let relative = from.strip_prefix(&from_parent).map_err(|err| {
|
let relative = from.strip_prefix(&from_parent).map_err(|err| {
|
||||||
format!(
|
OperationError::from_err(
|
||||||
"failed to remove prefix {:?} from {:?}: {}",
|
format!(
|
||||||
from_parent, from, err
|
"failed to remove prefix {:?} from {:?}: {}",
|
||||||
|
from_parent, from, err
|
||||||
|
),
|
||||||
|
&self.controller,
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
//TODO: ensure to is inside of to_parent?
|
//TODO: ensure to is inside of to_parent?
|
||||||
|
|
@ -127,7 +148,10 @@ impl Context {
|
||||||
|
|
||||||
let total_ops = ops.len();
|
let total_ops = ops.len();
|
||||||
for (current_ops, mut op) in ops.into_iter().enumerate() {
|
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 {
|
let progress = Progress {
|
||||||
current_ops,
|
current_ops,
|
||||||
|
|
@ -137,9 +161,12 @@ impl Context {
|
||||||
};
|
};
|
||||||
(self.on_progress)(&op, &progress);
|
(self.on_progress)(&op, &progress);
|
||||||
if op.run(self, progress).await.map_err(|err| {
|
if op.run(self, progress).await.map_err(|err| {
|
||||||
format!(
|
OperationError::from_err(
|
||||||
"failed to {:?} {:?} to {:?}: {}",
|
format!(
|
||||||
op.kind, op.from, op.to, err
|
"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
|
// 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);
|
(ctx.on_progress)(self, &progress);
|
||||||
|
|
||||||
// Also check if the progress was cancelled.
|
// 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;
|
ctx.buf = buf_out;
|
||||||
return Err(why.into());
|
return Err(OperationError::from_state(state, &ctx.controller).into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
11
src/tab.rs
11
src/tab.rs
|
|
@ -81,7 +81,7 @@ use crate::{
|
||||||
mime_icon::{mime_for_path, mime_icon},
|
mime_icon::{mime_for_path, mime_icon},
|
||||||
mounter::MOUNTERS,
|
mounter::MOUNTERS,
|
||||||
mouse_area,
|
mouse_area,
|
||||||
operation::Controller,
|
operation::{Controller, OperationError},
|
||||||
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
thumbnail_cacher::{CachedThumbnail, ThumbnailCacher, ThumbnailSize},
|
||||||
thumbnailer::thumbnailer,
|
thumbnailer::thumbnailer,
|
||||||
};
|
};
|
||||||
|
|
@ -2469,10 +2469,13 @@ pub struct Tab {
|
||||||
window_id: Option<window::Id>,
|
window_id: Option<window::Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn calculate_dir_size(path: &Path, controller: Controller) -> Result<u64, String> {
|
async fn calculate_dir_size(path: &Path, controller: Controller) -> Result<u64, OperationError> {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
for entry_res in WalkDir::new(path) {
|
for entry_res in WalkDir::new(path) {
|
||||||
controller.check().await?;
|
controller
|
||||||
|
.check()
|
||||||
|
.await
|
||||||
|
.map_err(|s| OperationError::from_state(s, &controller))?;
|
||||||
|
|
||||||
//TODO: report more errors?
|
//TODO: report more errors?
|
||||||
if let Ok(entry) = entry_res {
|
if let Ok(entry) = entry_res {
|
||||||
|
|
@ -5717,7 +5720,7 @@ impl Tab {
|
||||||
);
|
);
|
||||||
Message::DirectorySize(
|
Message::DirectorySize(
|
||||||
path.clone(),
|
path.clone(),
|
||||||
DirSize::Error(err),
|
DirSize::Error(err.to_string()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue