This commit is contained in:
Jeremy Soller 2025-02-04 15:41:16 -07:00
parent 9e2aa3433a
commit 22bca8632b
No known key found for this signature in database
GPG key ID: 670FDFB5428E05CA
2 changed files with 300 additions and 256 deletions

View file

@ -59,6 +59,7 @@ use trash::TrashItem;
#[cfg(feature = "wayland")] #[cfg(feature = "wayland")]
use wayland_client::{protocol::wl_output::WlOutput, Proxy}; use wayland_client::{protocol::wl_output::WlOutput, Proxy};
use crate::operation::{OperationError, OperationErrorType};
use crate::{ use crate::{
clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste}, clipboard::{ClipboardCopy, ClipboardKind, ClipboardPaste},
config::{AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig}, config::{AppTheme, Config, DesktopConfig, Favorite, IconSizes, TabConfig},
@ -71,7 +72,6 @@ use crate::{
spawn_detached::spawn_detached, spawn_detached::spawn_detached,
tab::{self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION}, tab::{self, HeadingOptions, ItemMetadata, Location, Tab, HOVER_DURATION},
}; };
use crate::operation::{OperationError, OperationErrorType};
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum Mode { pub enum Mode {
@ -418,13 +418,13 @@ pub enum DialogPage {
to: PathBuf, to: PathBuf,
name: String, name: String,
archive_type: ArchiveType, archive_type: ArchiveType,
password: Option<String> password: Option<String>,
}, },
EmptyTrash, EmptyTrash,
FailedOperation(u64), FailedOperation(u64),
ExtractPassword { ExtractPassword {
id: u64, id: u64,
password: String password: String,
}, },
MountError { MountError {
mounter_key: MounterKey, mounter_key: MounterKey,
@ -1890,7 +1890,7 @@ impl Application for App {
to, to,
name, name,
archive_type, archive_type,
password: None password: None,
}); });
return widget::text_input::focus(self.dialog_text_input.clone()); return widget::text_input::focus(self.dialog_text_input.clone());
} }
@ -1968,7 +1968,7 @@ impl Application for App {
to, to,
name, name,
archive_type, archive_type,
password password,
} => { } => {
let extension = archive_type.extension(); let extension = archive_type.extension();
let name = format!("{}{}", name, extension); let name = format!("{}{}", name, extension);
@ -1977,7 +1977,7 @@ impl Application for App {
paths, paths,
to, to,
archive_type, archive_type,
password password,
}) })
} }
DialogPage::EmptyTrash => { DialogPage::EmptyTrash => {
@ -1986,18 +1986,15 @@ impl Application for App {
DialogPage::FailedOperation(id) => { DialogPage::FailedOperation(id) => {
log::warn!("TODO: retry operation {}", id); log::warn!("TODO: retry operation {}", id);
} }
DialogPage::ExtractPassword { DialogPage::ExtractPassword { id, password } => {
id,
password
} => {
let (operation, _, _err) = self.failed_operations.get(&id).unwrap(); let (operation, _, _err) = self.failed_operations.get(&id).unwrap();
let new_op = match &operation { let new_op = match &operation {
Operation::Extract { to, paths, .. } => Operation::Extract { Operation::Extract { to, paths, .. } => Operation::Extract {
to: to.clone(), to: to.clone(),
paths: paths.clone(), paths: paths.clone(),
password: Some(password) password: Some(password),
}, },
_ => unreachable!() _ => unreachable!(),
}; };
self.operation(new_op); self.operation(new_op);
} }
@ -2117,7 +2114,7 @@ impl Application for App {
self.operation(Operation::Extract { self.operation(Operation::Extract {
paths, paths,
to: destination, to: destination,
password: None password: None,
}); });
} }
} }
@ -2598,19 +2595,18 @@ impl Application for App {
if let Some((op, controller)) = self.pending_operations.remove(&id) { if let Some((op, controller)) = self.pending_operations.remove(&id) {
// Only show dialog if not cancelled // Only show dialog if not cancelled
if !controller.is_cancelled() { if !controller.is_cancelled() {
self.dialog_pages.push_back( self.dialog_pages.push_back(match err.kind {
match err.kind { OperationErrorType::Generic(_) => DialogPage::FailedOperation(id),
OperationErrorType::Generic(_) => DialogPage::FailedOperation(id), OperationErrorType::PasswordRequired => DialogPage::ExtractPassword {
OperationErrorType::PasswordRequired => DialogPage::ExtractPassword { id: id,
id: id, password: String::from(""),
password: String::from("") },
} });
}
);
} }
// Remove from progress // Remove from progress
self.progress_operations.remove(&id); self.progress_operations.remove(&id);
self.failed_operations.insert(id, (op, controller, err.to_string())); self.failed_operations
.insert(id, (op, controller, err.to_string()));
} }
// Close progress notification if all relavent operations are finished // Close progress notification if all relavent operations are finished
if !self if !self
@ -3605,7 +3601,7 @@ impl Application for App {
to, to,
name, name,
archive_type, archive_type,
password password,
} => { } => {
let mut dialog = widget::dialog().title(fl!("create-archive")); let mut dialog = widget::dialog().title(fl!("create-archive"));
@ -3683,10 +3679,11 @@ impl Application for App {
if *archive_type == ArchiveType::Zip { if *archive_type == ArchiveType::Zip {
let password_unwrapped = password.clone().unwrap_or_else(String::default); let password_unwrapped = password.clone().unwrap_or_else(String::default);
dialog = dialog.control( dialog = dialog.control(widget::column::with_children(vec![
widget::column::with_children(vec![ widget::text::body(fl!("password")).into(),
widget::text::body(fl!("password")).into(), widget::text_input("", password_unwrapped)
widget::text_input("", password_unwrapped).password().on_input(move |password_unwrapped| { .password()
.on_input(move |password_unwrapped| {
Message::DialogUpdate(DialogPage::Compress { Message::DialogUpdate(DialogPage::Compress {
paths: paths.clone(), paths: paths.clone(),
to: to.clone(), to: to.clone(),
@ -3695,10 +3692,9 @@ impl Application for App {
password: Some(password_unwrapped), password: Some(password_unwrapped),
}) })
}) })
.on_submit_maybe(complete_maybe) .on_submit_maybe(complete_maybe)
.into(), .into(),
]) ]));
);
} }
dialog dialog
@ -3726,21 +3722,18 @@ impl Application for App {
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel), widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
) )
} }
DialogPage::ExtractPassword { DialogPage::ExtractPassword { id, password } => {
id,
password
} => {
widget::dialog() widget::dialog()
.title(fl!("extract-password-required")) .title(fl!("extract-password-required"))
.icon(widget::icon::from_name("dialog-error").size(64)) .icon(widget::icon::from_name("dialog-error").size(64))
.control(widget::text_input("", password).password().on_input(move |password| { .control(widget::text_input("", password).password().on_input(
Message::DialogUpdate(DialogPage::ExtractPassword { move |password| {
id: *id, Message::DialogUpdate(DialogPage::ExtractPassword { id: *id, password })
password },
}) ))
}))
.primary_action( .primary_action(
widget::button::suggested(fl!("extract-here")).on_press(Message::DialogComplete), widget::button::suggested(fl!("extract-here"))
.on_press(Message::DialogComplete),
) )
.secondary_action( .secondary_action(
widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel), widget::button::standard(fl!("cancel")).on_press(Message::DialogCancel),
@ -4777,7 +4770,6 @@ impl Application for App {
.await .await
.send(Message::PendingError(id, err)) .send(Message::PendingError(id, err))
.await; .await;
} }
} }

View file

@ -1,17 +1,3 @@
use cosmic::iced::futures::{channel::mpsc::Sender, executor, SinkExt};
use std::{
borrow::Cow,
fs,
io::{self, Read, Write},
path::{Path, PathBuf},
sync::Arc,
};
use std::collections::VecDeque;
use std::fmt::{Formatter};
use tokio::sync::{mpsc, Mutex as TokioMutex};
use walkdir::WalkDir;
use zip::AesMode::Aes256;
use zip::result::ZipError;
use crate::{ use crate::{
app::{ArchiveType, DialogPage, Message}, app::{ArchiveType, DialogPage, Message},
config::IconSizes, config::IconSizes,
@ -20,6 +6,20 @@ use crate::{
spawn_detached::spawn_detached, spawn_detached::spawn_detached,
tab, tab,
}; };
use cosmic::iced::futures::{channel::mpsc::Sender, executor, SinkExt};
use std::collections::VecDeque;
use std::fmt::Formatter;
use std::{
borrow::Cow,
fs,
io::{self, Read, Write},
path::{Path, PathBuf},
sync::Arc,
};
use tokio::sync::{mpsc, Mutex as TokioMutex};
use walkdir::WalkDir;
use zip::result::ZipError;
use zip::AesMode::Aes256;
pub use self::controller::{Controller, ControllerState}; pub use self::controller::{Controller, ControllerState};
pub mod controller; pub mod controller;
@ -94,7 +94,7 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
archive: &mut zip::ZipArchive<R>, archive: &mut zip::ZipArchive<R>,
directory: P, directory: P,
controller: Controller, controller: Controller,
password: Option<String> password: Option<String>,
) -> zip::result::ZipResult<()> { ) -> zip::result::ZipResult<()> {
use std::{ffi::OsString, fs}; use std::{ffi::OsString, fs};
use zip::result::ZipError; use zip::result::ZipError;
@ -115,7 +115,6 @@ fn zip_extract<R: io::Read + io::Seek, P: AsRef<Path>>(
Ok(()) Ok(())
} }
#[cfg(unix)] #[cfg(unix)]
let mut files_by_unix_mode = Vec::new(); let mut files_by_unix_mode = Vec::new();
let mut buffer = vec![0; 4 * 1024 * 1024]; let mut buffer = vec![0; 4 * 1024 * 1024];
@ -131,8 +130,9 @@ 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)?; }
.map_err(|e| e)?;
let filepath = file let filepath = file
.enclosed_name() .enclosed_name()
.ok_or(ZipError::InvalidArchive("Invalid file path"))?; .ok_or(ZipError::InvalidArchive("Invalid file path"))?;
@ -193,8 +193,9 @@ 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)?; }
.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() {
@ -322,7 +323,9 @@ async fn copy_or_move(
}); });
} }
context.recursive_copy_or_move(from_to_pairs, moving).map_err(OperationError::from_str)?; context
.recursive_copy_or_move(from_to_pairs, moving)
.map_err(OperationError::from_str)?;
Ok(context.op_sel) Ok(context.op_sel)
}) })
@ -446,7 +449,7 @@ pub enum Operation {
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
to: PathBuf, to: PathBuf,
archive_type: ArchiveType, archive_type: ArchiveType,
password: Option<String> password: Option<String>,
}, },
/// Copy items /// Copy items
Copy { Copy {
@ -463,7 +466,7 @@ pub enum Operation {
Extract { Extract {
paths: Vec<PathBuf>, paths: Vec<PathBuf>,
to: PathBuf, to: PathBuf,
password: Option<String> password: Option<String>,
}, },
/// Move items /// Move items
Move { Move {
@ -493,7 +496,7 @@ pub enum Operation {
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum OperationErrorType { pub enum OperationErrorType {
Generic(String), Generic(String),
PasswordRequired PasswordRequired,
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct OperationError { pub struct OperationError {
@ -512,7 +515,7 @@ 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 {
OperationErrorType::Generic(s) => s.fmt(f), OperationErrorType::Generic(s) => s.fmt(f),
OperationErrorType::PasswordRequired => f.write_str("Password required") OperationErrorType::PasswordRequired => f.write_str("Password required"),
} }
} }
} }
@ -548,7 +551,11 @@ impl Operation {
progress = progress() progress = progress()
), ),
Self::EmptyTrash => fl!("emptying-trash", progress = progress()), Self::EmptyTrash => fl!("emptying-trash", progress = progress()),
Self::Extract { paths, to, password: _ } => fl!( Self::Extract {
paths,
to,
password: _,
} => fl!(
"extracting", "extracting",
items = paths.len(), items = paths.len(),
from = paths_parent_name(paths), from = paths_parent_name(paths),
@ -603,7 +610,11 @@ impl Operation {
to = fl!("trash") to = fl!("trash")
), ),
Self::EmptyTrash => fl!("emptied-trash"), Self::EmptyTrash => fl!("emptied-trash"),
Self::Extract { paths, to, password: _ } => fl!( Self::Extract {
paths,
to,
password: _,
} => fl!(
"extracted", "extracted",
items = paths.len(), items = paths.len(),
from = paths_parent_name(paths), from = paths_parent_name(paths),
@ -674,123 +685,147 @@ impl Operation {
paths, paths,
to, to,
archive_type, archive_type,
password password,
} => { } => {
tokio::task::spawn_blocking(move || -> Result<OperationSelection, OperationError> { tokio::task::spawn_blocking(
let Some(relative_root) = to.parent() else { move || -> Result<OperationSelection, OperationError> {
return Err(OperationError::from_str(format!("path {:?} has no parent directory", to))); let Some(relative_root) = to.parent() else {
}; return Err(OperationError::from_str(format!(
"path {:?} has no parent directory",
to
)));
};
let op_sel = OperationSelection { let op_sel = OperationSelection {
ignored: paths.clone(), ignored: paths.clone(),
selected: vec![to.clone()], selected: vec![to.clone()],
}; };
let mut paths = paths; let mut paths = paths;
for path in paths.clone().iter() { for path in paths.clone().iter() {
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(OperationError::from_str)?;
paths.push(entry.into_path()); paths.push(entry.into_path());
}
}
}
match archive_type {
ArchiveType::Tgz => {
let mut archive = fs::File::create(&to)
.map(io::BufWriter::new)
.map(|w| {
flate2::write::GzEncoder::new(w, flate2::Compression::default())
})
.map(tar::Builder::new)
.map_err(OperationError::from_str)?;
let total_paths = paths.len();
for (i, path) in paths.iter().enumerate() {
controller.check().map_err(OperationError::from_str)?;
controller.set_progress((i as f32) / total_paths as f32);
if let Some(relative_path) =
path.strip_prefix(relative_root).map_err(OperationError::from_str)?.to_str()
{
archive
.append_path_with_name(path, relative_path)
.map_err(OperationError::from_str)?;
} }
} }
archive.finish().map_err(OperationError::from_str)?;
} }
ArchiveType::Zip => {
let mut archive = fs::File::create(&to)
.map(io::BufWriter::new)
.map(zip::ZipWriter::new)
.map_err(OperationError::from_str)?;
let total_paths = paths.len(); match archive_type {
let mut buffer = vec![0; 4 * 1024 * 1024]; ArchiveType::Tgz => {
for (i, path) in paths.iter().enumerate() { let mut archive = fs::File::create(&to)
controller.check().map_err(OperationError::from_str)?; .map(io::BufWriter::new)
.map(|w| {
flate2::write::GzEncoder::new(
w,
flate2::Compression::default(),
)
})
.map(tar::Builder::new)
.map_err(OperationError::from_str)?;
controller.set_progress((i as f32) / total_paths as f32); let total_paths = paths.len();
for (i, path) in paths.iter().enumerate() {
controller.check().map_err(OperationError::from_str)?;
let mut zip_options = zip::write::SimpleFileOptions::default(); controller.set_progress((i as f32) / total_paths as f32);
if password.is_some() {
zip_options = zip_options.with_aes_encryption(Aes256, password.as_deref().unwrap()); if let Some(relative_path) = path
} .strip_prefix(relative_root)
if let Some(relative_path) = .map_err(OperationError::from_str)?
path.strip_prefix(relative_root).map_err(OperationError::from_str)?.to_str() .to_str()
{ {
if path.is_file() {
let mut file = fs::File::open(path).map_err(OperationError::from_str)?;
let metadata = file.metadata().map_err(OperationError::from_str)?;
let total = metadata.len();
if total >= 4 * 1024 * 1024 * 1024 {
// The large file option must be enabled for files above 4 GiB
zip_options = zip_options.large_file(true);
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let mode = metadata.mode();
zip_options = zip_options.unix_permissions(mode);
}
archive archive
.start_file(relative_path, zip_options) .append_path_with_name(path, relative_path)
.map_err(OperationError::from_str)?;
let mut current = 0;
loop {
controller.check().map_err(OperationError::from_str)?;
let count = file.read(&mut buffer).map_err(OperationError::from_str)?;
if count == 0 {
break;
}
archive.write_all(&buffer[..count]).map_err(OperationError::from_str)?;
current += count;
let file_progress = current as f32 / total as f32;
let total_progress =
(i as f32 + file_progress) / total_paths as f32;
controller.set_progress(total_progress);
}
} else {
archive
.add_directory(relative_path, zip_options)
.map_err(OperationError::from_str)?; .map_err(OperationError::from_str)?;
} }
} }
archive.finish().map_err(OperationError::from_str)?;
} }
ArchiveType::Zip => {
let mut archive = fs::File::create(&to)
.map(io::BufWriter::new)
.map(zip::ZipWriter::new)
.map_err(OperationError::from_str)?;
archive.finish().map_err(OperationError::from_str)?; let total_paths = paths.len();
let mut buffer = vec![0; 4 * 1024 * 1024];
for (i, path) in paths.iter().enumerate() {
controller.check().map_err(OperationError::from_str)?;
controller.set_progress((i as f32) / total_paths as f32);
let mut zip_options = zip::write::SimpleFileOptions::default();
if password.is_some() {
zip_options = zip_options.with_aes_encryption(
Aes256,
password.as_deref().unwrap(),
);
}
if let Some(relative_path) = path
.strip_prefix(relative_root)
.map_err(OperationError::from_str)?
.to_str()
{
if path.is_file() {
let mut file = fs::File::open(path)
.map_err(OperationError::from_str)?;
let metadata = file
.metadata()
.map_err(OperationError::from_str)?;
let total = metadata.len();
if total >= 4 * 1024 * 1024 * 1024 {
// The large file option must be enabled for files above 4 GiB
zip_options = zip_options.large_file(true);
}
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
let mode = metadata.mode();
zip_options = zip_options.unix_permissions(mode);
}
archive
.start_file(relative_path, zip_options)
.map_err(OperationError::from_str)?;
let mut current = 0;
loop {
controller
.check()
.map_err(OperationError::from_str)?;
let count = file
.read(&mut buffer)
.map_err(OperationError::from_str)?;
if count == 0 {
break;
}
archive
.write_all(&buffer[..count])
.map_err(OperationError::from_str)?;
current += count;
let file_progress = current as f32 / total as f32;
let total_progress =
(i as f32 + file_progress) / total_paths as f32;
controller.set_progress(total_progress);
}
} else {
archive
.add_directory(relative_path, zip_options)
.map_err(OperationError::from_str)?;
}
}
}
archive.finish().map_err(OperationError::from_str)?;
}
} }
}
Ok(op_sel) Ok(op_sel)
}) },
)
.await .await
.map_err(OperationError::from_str)? .map_err(OperationError::from_str)?
//.map_err(|e| e)? //.map_err(|e| e)?
@ -830,7 +865,8 @@ impl Operation {
controller.set_progress(i as f32 / count as f32); controller.set_progress(i as f32 / count as f32);
trash::os_limited::purge_all([item]).map_err(OperationError::from_str)?; trash::os_limited::purge_all([item])
.map_err(OperationError::from_str)?;
} }
Ok(()) Ok(())
}) })
@ -839,127 +875,141 @@ impl Operation {
} }
Ok(OperationSelection::default()) Ok(OperationSelection::default())
} }
Self::Extract { paths, to, password } => { Self::Extract {
tokio::task::spawn_blocking(move || -> Result<OperationSelection, OperationError> { paths,
let total_paths = paths.len(); to,
let mut op_sel = OperationSelection::default(); password,
for (i, path) in paths.iter().enumerate() { } => {
controller.check().map_err(OperationError::from_str)?; tokio::task::spawn_blocking(
move || -> Result<OperationSelection, OperationError> {
let total_paths = paths.len();
let mut op_sel = OperationSelection::default();
for (i, path) in paths.iter().enumerate() {
controller.check().map_err(OperationError::from_str)?;
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.ignored.push(path.clone());
op_sel.selected.push(new_dir.clone()); op_sel.selected.push(new_dir.clone());
let controller = controller.clone(); let controller = controller.clone();
let mime = mime_for_path(path); let mime = mime_for_path(path);
let password = password.clone(); let password = password.clone();
match mime.essence_str() { match mime.essence_str() {
"application/gzip" | "application/x-compressed-tar" => { "application/gzip" | "application/x-compressed-tar" => {
OpReader::new(path, controller) OpReader::new(path, controller)
.map(io::BufReader::new)
.map(flate2::read::GzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(&new_dir))
.map_err(OperationError::from_str)?
}
"application/x-tar" => OpReader::new(path, controller)
.map(io::BufReader::new) .map(io::BufReader::new)
.map(flate2::read::GzDecoder::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)?,
"application/zip" => fs::File::open(path)
.map(io::BufReader::new)
.map(zip::ZipArchive::new)
.map_err(OperationError::from_str)? .map_err(OperationError::from_str)?
} .and_then(move |mut archive| {
"application/x-tar" => OpReader::new(path, controller) zip_extract(
.map(io::BufReader::new) &mut archive,
.map(tar::Archive::new) &new_dir,
.and_then(|mut archive| archive.unpack(&new_dir)) controller,
.map_err(OperationError::from_str)?, password,
"application/zip" => fs::File::open(path) )
.map(io::BufReader::new) })
.map(zip::ZipArchive::new) .map_err(|e| match e {
.map_err(OperationError::from_str)? ZipError::UnsupportedArchive(
.and_then(move |mut archive| { ZipError::PASSWORD_REQUIRED,
zip_extract(&mut archive, &new_dir, controller, password) )
}) | ZipError::InvalidPassword => OperationError {
.map_err(|e| match e {
ZipError::UnsupportedArchive(ZipError::PASSWORD_REQUIRED) |
ZipError::InvalidPassword => {
OperationError {
kind: OperationErrorType::PasswordRequired, kind: OperationErrorType::PasswordRequired,
} },
}, _ => OperationError::from_str(e),
_ => OperationError::from_str(e) })?,
})?, #[cfg(feature = "bzip2")]
#[cfg(feature = "bzip2")] "application/x-bzip" | "application/x-bzip-compressed-tar" => {
"application/x-bzip" | "application/x-bzip-compressed-tar" => { OpReader::new(path, controller)
OpReader::new(path, controller) .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(OperationError::from_str)? }
#[cfg(feature = "liblzma")]
"application/x-xz" | "application/x-xz-compressed-tar" => {
OpReader::new(path, controller)
.map(io::BufReader::new)
.map(liblzma::read::XzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(&new_dir))
.map_err(OperationError::from_str)?
}
_ => Err(OperationError::from_str(format!(
"unsupported mime type {:?}",
mime
)))?,
} }
#[cfg(feature = "liblzma")]
"application/x-xz" | "application/x-xz-compressed-tar" => {
OpReader::new(path, controller)
.map(io::BufReader::new)
.map(liblzma::read::XzDecoder::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(&new_dir))
.map_err(OperationError::from_str)?
}
_ => Err(OperationError::from_str(format!("unsupported mime type {:?}", mime)))?,
} }
} }
}
Ok(op_sel) Ok(op_sel)
}) },
)
.await .await
.map_err(OperationError::from_str)? .map_err(OperationError::from_str)?
//.map_err(OperationError::from_str)? //.map_err(OperationError::from_str)?
} }
Self::Move { paths, to } => copy_or_move(paths, to, true, msg_tx, controller).await, Self::Move { paths, to } => copy_or_move(paths, to, true, msg_tx, controller).await,
Self::NewFolder { path } => { Self::NewFolder { path } => tokio::task::spawn_blocking(
tokio::task::spawn_blocking(move || -> Result<OperationSelection, OperationError> { move || -> Result<OperationSelection, OperationError> {
controller.check().map_err(OperationError::from_str)?; controller.check().map_err(OperationError::from_str)?;
fs::create_dir(&path).map_err(OperationError::from_str)?; fs::create_dir(&path).map_err(OperationError::from_str)?;
Ok(OperationSelection { Ok(OperationSelection {
ignored: Vec::new(), ignored: Vec::new(),
selected: vec![path], selected: vec![path],
}) })
}) },
.await )
.map_err(OperationError::from_str)? .await
} .map_err(OperationError::from_str)?,
Self::NewFile { path } => { Self::NewFile { path } => tokio::task::spawn_blocking(
tokio::task::spawn_blocking(move || -> Result<OperationSelection, OperationError> { move || -> Result<OperationSelection, OperationError> {
controller.check().map_err(OperationError::from_str)?; controller.check().map_err(OperationError::from_str)?;
fs::File::create(&path).map_err(OperationError::from_str)?; fs::File::create(&path).map_err(OperationError::from_str)?;
Ok(OperationSelection { Ok(OperationSelection {
ignored: Vec::new(), ignored: Vec::new(),
selected: vec![path], selected: vec![path],
}) })
}) },
.await )
.map_err(OperationError::from_str)? .await
} .map_err(OperationError::from_str)?,
Self::Rename { from, to } => { Self::Rename { from, to } => tokio::task::spawn_blocking(
tokio::task::spawn_blocking(move || -> Result<OperationSelection, OperationError> { move || -> Result<OperationSelection, OperationError> {
controller.check().map_err(OperationError::from_str)?; controller.check().map_err(OperationError::from_str)?;
fs::rename(&from, &to).map_err(OperationError::from_str)?; fs::rename(&from, &to).map_err(OperationError::from_str)?;
Ok(OperationSelection { Ok(OperationSelection {
ignored: vec![from], ignored: vec![from],
selected: vec![to], selected: vec![to],
}) })
}) },
.await )
.map_err(OperationError::from_str)? .await
} .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
@ -995,7 +1045,9 @@ impl Operation {
controller.check().map_err(OperationError::from_str)?; controller.check().map_err(OperationError::from_str)?;
let mut perms = fs::metadata(&path).map_err(OperationError::from_str)?.permissions(); let mut perms = fs::metadata(&path)
.map_err(OperationError::from_str)?
.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);