Enable extracting zip files to current folder

This commit is contained in:
Nathan Rowe 2024-08-15 12:32:12 -05:00
parent e4c65883fd
commit ef23ff77e1
6 changed files with 360 additions and 0 deletions

View file

@ -71,6 +71,7 @@ pub enum Action {
Cut,
EditHistory,
EditLocation,
ExtractHere,
HistoryNext,
HistoryPrevious,
ItemDown,
@ -119,6 +120,7 @@ impl Action {
Action::Cut => Message::Cut(entity_opt),
Action::EditHistory => Message::ToggleContextPage(ContextPage::EditHistory),
Action::EditLocation => Message::EditLocation(entity_opt),
Action::ExtractHere => Message::ExtractHere(entity_opt),
Action::HistoryNext => Message::TabMessage(entity_opt, tab::Message::GoNext),
Action::HistoryPrevious => Message::TabMessage(entity_opt, tab::Message::GoPrevious),
Action::ItemDown => Message::TabMessage(entity_opt, tab::Message::ItemDown),
@ -216,6 +218,7 @@ pub enum Message {
DialogPush(DialogPage),
DialogUpdate(DialogPage),
EditLocation(Option<Entity>),
ExtractHere(Option<Entity>),
Key(Modifiers, Key),
LaunchUrl(String),
MaybeExit,
@ -1333,6 +1336,18 @@ impl Application for App {
));
}
}
Message::ExtractHere(entity_opt) => {
let paths = self.selected_paths(entity_opt);
if let Some(current_path) = paths.get(0) {
if let Some(destination) = current_path.parent().zip(current_path.file_stem()) {
let destination_path = destination.0.to_path_buf();
self.operation(Operation::Extract {
paths,
to: destination_path,
});
}
}
}
Message::Key(modifiers, key) => {
let entity = self.tab_model.active();
for (key_bind, action) in self.key_binds.iter() {

View file

@ -9,6 +9,7 @@ use cosmic::{
widget::menu::{self, key_bind::KeyBind, ItemHeight, ItemWidth, MenuBar},
Element,
};
use mime_guess::Mime;
use std::collections::HashMap;
use crate::{
@ -79,6 +80,7 @@ pub fn context_menu<'a>(
let mut selected_dir = 0;
let mut selected = 0;
let mut selected_types: Vec<Mime> = vec![];
tab.items_opt().map(|items| {
for item in items.iter() {
if item.selected {
@ -86,9 +88,12 @@ pub fn context_menu<'a>(
if item.metadata.is_dir() {
selected_dir += 1;
}
selected_types.push(item.mime.clone());
}
}
});
selected_types.sort_unstable();
selected_types.dedup();
let mut children: Vec<Element<_>> = Vec::new();
match tab.location {
@ -119,6 +124,16 @@ pub fn context_menu<'a>(
children.push(menu_item(fl!("rename"), Action::Rename).into());
children.push(menu_item(fl!("cut"), Action::Cut).into());
children.push(menu_item(fl!("copy"), Action::Copy).into());
let supported_archive_types = ["application/x-tar", "application/zip"]
.iter()
.filter_map(|mime_type| mime_type.parse::<Mime>().ok())
.collect::<Vec<_>>();
selected_types.retain(|t| !supported_archive_types.contains(t));
if selected_types.is_empty() {
children.push(menu_item(fl!("extract-here"), Action::ExtractHere).into());
}
//TODO: Print?
children.push(container(horizontal_rule(1)).padding([0, 8]).into());
children.push(menu_item(fl!("show-details"), Action::Properties).into());

View file

@ -1,8 +1,11 @@
use cosmic::iced::futures::{channel::mpsc::Sender, executor, SinkExt};
use mime_guess::MimeGuess;
use std::{
borrow::Cow,
fs,
io::{self, Error},
path::{Path, PathBuf},
process,
sync::{
atomic::{self, AtomicU64},
Arc,
@ -138,6 +141,11 @@ pub enum Operation {
},
/// Empty the trash
EmptyTrash,
/// Uncompress files
Extract {
paths: Vec<PathBuf>,
to: PathBuf,
},
/// Move items
Move {
paths: Vec<PathBuf>,
@ -246,6 +254,12 @@ impl Operation {
to = fl!("trash")
),
Self::EmptyTrash => fl!("emptying-trash"),
Self::Extract { paths, to } => fl!(
"extracting",
items = paths.len(),
from = paths_parent_name(paths),
to = file_name(to)
),
Self::Move { paths, to } => fl!(
"moving",
items = paths.len(),
@ -284,6 +298,12 @@ impl Operation {
to = fl!("trash")
),
Self::EmptyTrash => fl!("emptied-trash"),
Self::Extract { paths, to } => fl!(
"extracted",
items = paths.len(),
from = paths_parent_name(paths),
to = file_name(to)
),
Self::Move { paths, to } => fl!(
"moved",
items = paths.len(),
@ -473,6 +493,50 @@ impl Operation {
.send(Message::PendingProgress(id, 100.0))
.await;
}
Self::Extract { paths, to } => {
for path in paths {
let to = to.to_owned();
tokio::task::spawn_blocking(move || -> Result<(), String> {
if let Some(file_stem) = path.file_stem() {
let mut new_dir = to.join(file_stem);
if new_dir.exists() {
let mut extensionless_path = path.to_owned();
extensionless_path.set_extension("");
if let Some(new_dir_parent) = new_dir.parent() {
new_dir = copy_unique_path(&extensionless_path, new_dir_parent);
}
}
if let Some(mime) = mime_guess::from_path(&path).first() {
match mime.essence_str() {
"application/x-tar" => {
return fs::File::open(path)
.map(io::BufReader::new)
.map(tar::Archive::new)
.and_then(|mut archive| archive.unpack(new_dir))
.map_err(err_str)
}
"application/zip" => {
return fs::File::open(path)
.map(io::BufReader::new)
.map(zip::ZipArchive::new)
.map_err(err_str)?
.and_then(|mut archive| archive.extract(new_dir))
.map_err(err_str)
}
_ => Err(format!("unsupported mime type {:?}", mime))?,
}
}
}
Ok(())
})
.await
.map_err(err_str)?
.map_err(err_str)?;
}
}
Self::Move { paths, to } => {
let msg_tx = msg_tx.clone();
tokio::task::spawn_blocking(move || {