From 5624f37a36b903b1add8bebb62b167361b6fddcb Mon Sep 17 00:00:00 2001 From: Jeremy Soller Date: Thu, 3 Oct 2024 11:53:01 -0600 Subject: [PATCH] Adjust executable permission to use Operation and adjust text per UX --- i18n/en/cosmic_files.ftl | 10 +-- src/app.rs | 133 +++++++++++++++++++-------------------- src/operation.rs | 38 +++++++++++ 3 files changed, 107 insertions(+), 74 deletions(-) diff --git a/i18n/en/cosmic_files.ftl b/i18n/en/cosmic_files.ftl index 505c103..f5ab7cf 100644 --- a/i18n/en/cosmic_files.ftl +++ b/i18n/en/cosmic_files.ftl @@ -66,10 +66,10 @@ apply-to-all = Apply to all keep-both = Keep both skip = Skip -## Execution permission Dialog -add-permission-title = Add execution permission -add-permission-control = Add execution permission to {$path} -add-permission = Add permission +## Set as Executable and Launch Dialog +set-executable-and-launch = Set as executable and launch +set-executable-and-launch-description = Do you want to set "{$name}" as executable and launch it? +set-and-launch = Set and launch ## Metadata Dialog owner = Owner @@ -145,6 +145,8 @@ extracted = Extracted {$items} {$items -> [one] item *[other] items } from {$from} to {$to} +setting-executable-and-launching = Setting "{$name}" as executable and launching +set-executable-and-launched = Set "{$name}" as executable and launched moving = Moving {$items} {$items -> [one] item *[other] items diff --git a/src/app.rs b/src/app.rs index 4580dec..b5da43f 100644 --- a/src/app.rs +++ b/src/app.rs @@ -43,13 +43,10 @@ use slotmap::Key as SlotMapKey; use std::{ any::TypeId, collections::{BTreeMap, HashMap, HashSet, VecDeque}, - env, - ffi::OsStr, - fmt, fs, + env, fmt, fs, future::pending, - io::{BufRead, BufReader}, + io, num::NonZeroU16, - os::unix::fs::PermissionsExt, path::PathBuf, process, sync::{Arc, Mutex}, @@ -415,10 +412,6 @@ pub enum DialogPage { name: String, dir: bool, }, - AddExecutablePermission { - file_path: PathBuf, - run: bool, - }, Replace { from: tab::Item, to: tab::Item, @@ -426,6 +419,9 @@ pub enum DialogPage { apply_to_all: bool, tx: mpsc::Sender, }, + SetExecutableAndLaunch { + path: PathBuf, + }, } pub struct FavoriteIndex(usize); @@ -1465,25 +1461,12 @@ impl Application for App { let to = parent.join(name); self.operation(Operation::Rename { from, to }); } - DialogPage::AddExecutablePermission { file_path, run } => { - let mut perms = fs::metadata(&file_path) - .expect("Failed to get metadata") - .permissions(); - - let current_mode = perms.mode(); - let new_mode = current_mode | 0o100; - perms.set_mode(new_mode); - fs::set_permissions(&file_path, perms) - .expect("Failed to set permissions"); - - if run { - log::info!("running app: {:?}", file_path); - let _ = std::process::Command::new(file_path).spawn(); - } - } DialogPage::Replace { .. } => { log::warn!("replace dialog should be completed with replace result"); } + DialogPage::SetExecutableAndLaunch { path } => { + self.operation(Operation::SetExecutableAndLaunch { path }); + } } } } @@ -2242,7 +2225,8 @@ impl Application for App { } tab::Command::OpenFile(path) => { let mut found_desktop_exec = false; - if mime_icon::mime_for_path(&path) == "application/x-desktop" { + let mime = mime_icon::mime_for_path(&path); + if mime == "application/x-desktop" { match freedesktop_entry_parser::parse_entry(&path) { Ok(entry) => { match entry.section("Desktop Entry").attr("Exec") { @@ -2275,37 +2259,43 @@ impl Application for App { Err(err) => { log::warn!("failed to parse {:?}: {}", path, err); } - }; - } - if !found_desktop_exec { - let file_extension = path.extension(); - match file_extension { - Some(ext) if ext == OsStr::new("AppImage") => { - let mut perms = fs::metadata(&path) - .expect("Failed to get metadata") - .permissions(); - - self.dialog_pages.push_back( - DialogPage::AddExecutablePermission { - file_path: path.clone(), - run: true, - }, - ); - } - _ => match open::that_detached(&path) { - Ok(()) => { - let _ = recently_used_xbel::update_recently_used( - &path, - App::APP_ID.to_string(), - "cosmic-files".to_string(), - None, + } + } else if mime == "application/x-executable" + || mime == "application/vnd.appimage" + { + let mut command = std::process::Command::new(&path); + match spawn_detached(&mut command) { + Ok(()) => {} + Err(err) => match err.kind() { + io::ErrorKind::PermissionDenied => { + // If permission is denied, try marking as executable, then running + self.dialog_pages.push_back( + DialogPage::SetExecutableAndLaunch { + path: path.clone(), + }, ); } - Err(err) => { - log::warn!("failed to open {:?}: {}", path, err); + _ => { + log::warn!("failed to execute {:?}: {}", path, err); } }, } + found_desktop_exec = true; + } + if !found_desktop_exec { + match open::that_detached(&path) { + Ok(()) => { + let _ = recently_used_xbel::update_recently_used( + &path, + App::APP_ID.to_string(), + "cosmic-files".to_string(), + None, + ); + } + Err(err) => { + log::warn!("failed to open {:?}: {}", path, err); + } + } } } tab::Command::OpenInNewTab(path) => { @@ -3088,24 +3078,6 @@ impl Application for App { .spacing(space_xxs), ) } - DialogPage::AddExecutablePermission { file_path, run } => { - let mut dialog = widget::dialog(fl!("add-permission-title")) - .primary_action( - widget::button::text(fl!("add-permission")) - .style(theme::Button::Suggested) - .on_press(Message::DialogComplete), - ) - .secondary_action( - widget::button::text(fl!("cancel")) - .style(theme::Button::Destructive) - .on_press(Message::DialogCancel), - ) - .control(widget::column().push(widget::text::text(fl!( - "add-permission-control", - path = file_path.as_os_str().to_str() - )))); - dialog - } DialogPage::RenameItem { from, parent, @@ -3231,6 +3203,27 @@ impl Application for App { ) } } + DialogPage::SetExecutableAndLaunch { path } => { + let name = match path.file_name() { + Some(file_name) => file_name.to_str(), + None => path.as_os_str().to_str(), + }; + widget::dialog(fl!("set-executable-and-launch")) + .primary_action( + widget::button::text(fl!("set-and-launch")) + .style(theme::Button::Suggested) + .on_press(Message::DialogComplete), + ) + .secondary_action( + widget::button::text(fl!("cancel")) + .style(theme::Button::Standard) + .on_press(Message::DialogCancel), + ) + .control(widget::text::text(fl!( + "set-executable-and-launch-description", + name = name + ))) + } }; Some(dialog.into()) diff --git a/src/operation.rs b/src/operation.rs index db31212..2226f12 100644 --- a/src/operation.rs +++ b/src/operation.rs @@ -14,6 +14,7 @@ use crate::{ config::IconSizes, err_str, fl, mime_icon::mime_for_path, + spawn_detached::spawn_detached, tab, }; @@ -176,6 +177,10 @@ pub enum Operation { Restore { paths: Vec, }, + /// Set executable and launch + SetExecutableAndLaunch { + path: PathBuf, + }, } async fn copy_or_move( @@ -473,6 +478,9 @@ impl Operation { fl!("renaming", from = file_name(from), to = file_name(to)) } Self::Restore { paths } => fl!("restoring", items = paths.len()), + Self::SetExecutableAndLaunch { path } => { + fl!("setting-executable-and-launching", name = file_name(path)) + } } } @@ -521,6 +529,9 @@ impl Operation { ), Self::Rename { from, to } => fl!("renamed", from = file_name(from), to = file_name(to)), Self::Restore { paths } => fl!("restored", items = paths.len()), + Self::SetExecutableAndLaunch { path } => { + fl!("set-executable-and-launched", name = file_name(path)) + } } } @@ -841,6 +852,33 @@ impl Operation { .await; } } + Self::SetExecutableAndLaunch { path } => { + tokio::task::spawn_blocking(move || -> io::Result<()> { + //TODO: what to do on non-Unix systems? + #[cfg(unix)] + { + use std::os::unix::fs::PermissionsExt; + let mut perms = fs::metadata(&path)?.permissions(); + let current_mode = perms.mode(); + let new_mode = current_mode | 0o111; + perms.set_mode(new_mode); + fs::set_permissions(&path, perms)?; + } + + let mut command = std::process::Command::new(path); + spawn_detached(&mut command)?; + + Ok(()) + }) + .await + .map_err(err_str)? + .map_err(err_str)?; + let _ = msg_tx + .lock() + .await + .send(Message::PendingProgress(id, 100.0)) + .await; + } } let _ = msg_tx