diff --git a/espanso/src/path/macos.rs b/espanso/src/path/macos.rs index 58f020a..f6732dd 100644 --- a/espanso/src/path/macos.rs +++ b/espanso/src/path/macos.rs @@ -17,11 +17,11 @@ * along with espanso. If not, see . */ -use std::io::ErrorKind; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; +use std::{fs::create_dir_all, io::ErrorKind}; use thiserror::Error; -use anyhow::Result; +use anyhow::{Context, Result}; use log::{error, warn}; pub fn is_espanso_in_path() -> bool { @@ -32,35 +32,33 @@ pub fn add_espanso_to_path(prompt_when_necessary: bool) -> Result<()> { let target_link_dir = PathBuf::from("/usr/local/bin"); let exec_path = std::env::current_exe()?; - if !target_link_dir.is_dir() { - return Err(PathError::UsrLocalBinDirDoesNotExist.into()); - } - let target_link_path = target_link_dir.join("espanso"); + if !target_link_dir.is_dir() { + warn!("/usr/local/bin folder does not exist, attempting to create one..."); + + if let Err(error) = create_dir_all(&target_link_dir) { + match error.kind() { + ErrorKind::PermissionDenied if prompt_when_necessary => { + warn!("target link file can't be accessed with current permissions, requesting elevated ones through AppleScript."); + + create_link_with_applescript(&exec_path, &target_link_path) + .context("unable to create link with AppleScript")?; + } + _other_error => { + return Err(PathError::SymlinkError(error).into()); + } + } + } + } + if let Err(error) = std::os::unix::fs::symlink(&exec_path, &target_link_path) { match error.kind() { - ErrorKind::PermissionDenied => { - if prompt_when_necessary { - warn!("target link file can't be accessed with current permissions, requesting elevated ones through AppleScript."); + ErrorKind::PermissionDenied if prompt_when_necessary => { + warn!("target link file can't be accessed with current permissions, requesting elevated ones through AppleScript."); - let params = format!( - r##"do shell script "mkdir -p /usr/local/bin && ln -sf '{}' '{}'" with administrator privileges"##, - exec_path.to_string_lossy(), - target_link_path.to_string_lossy(), - ); - - let mut child = std::process::Command::new("osascript") - .args(&["-e", ¶ms]) - .spawn()?; - - let result = child.wait()?; - if !result.success() { - return Err(PathError::ElevationRequestFailure.into()); - } - } else { - return Err(PathError::SymlinkError(error).into()); - } + create_link_with_applescript(&exec_path, &target_link_path) + .context("unable to create link with AppleScript")?; } _other_error => { return Err(PathError::SymlinkError(error).into()); @@ -71,6 +69,26 @@ pub fn add_espanso_to_path(prompt_when_necessary: bool) -> Result<()> { Ok(()) } +fn create_link_with_applescript(exec_path: &Path, target_link_path: &Path) -> Result<()> { + let params = format!( + r##"do shell script "mkdir -p /usr/local/bin && ln -sf '{}' '{}'" with administrator privileges"##, + exec_path.to_string_lossy(), + target_link_path.to_string_lossy(), + ); + + let mut child = std::process::Command::new("osascript") + .args(&["-e", ¶ms]) + .spawn()?; + + let result = child.wait()?; + + if !result.success() { + return Err(PathError::ElevationRequestFailure.into()); + } + + Ok(()) +} + pub fn remove_espanso_from_path(prompt_when_necessary: bool) -> Result<()> { let target_link_path = PathBuf::from("/usr/local/bin/espanso"); @@ -112,9 +130,6 @@ pub fn remove_espanso_from_path(prompt_when_necessary: bool) -> Result<()> { #[derive(Error, Debug)] pub enum PathError { - #[error("/usr/local/bin directory doesn't exist")] - UsrLocalBinDirDoesNotExist, - #[error("symlink error: `{0}`")] SymlinkError(std::io::Error),