From 91e5d4a1d62257d7c74e271e16718958f88180a8 Mon Sep 17 00:00:00 2001 From: Jeremy Knope Date: Sat, 11 Jul 2020 14:44:34 -0400 Subject: [PATCH 1/5] add apple (macos/ios) APIs, update edition --- Cargo.toml | 4 + src/apple.rs | 102 ++++++++++++++++++++ src/lib.rs | 266 ++++++++++++++++++++++++++------------------------- 3 files changed, 241 insertions(+), 131 deletions(-) create mode 100644 src/apple.rs diff --git a/Cargo.toml b/Cargo.toml index f8167b6..1d24690 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,10 +7,14 @@ readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/dirs-dev/dirs-sys-rs" maintenance = { status = "as-is" } +edition = "2018" [target.'cfg(unix)'.dependencies] libc = "0.2" +[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] +objc = "0.2" + [target.'cfg(target_os = "redox")'.dependencies] redox_users = "0.3.0" diff --git a/src/apple.rs b/src/apple.rs new file mode 100644 index 0000000..adb77e3 --- /dev/null +++ b/src/apple.rs @@ -0,0 +1,102 @@ +use objc::rc::autoreleasepool; +use objc::runtime::Object; +use objc::{class, msg_send, sel, sel_impl}; +use std::ffi::CStr; +use std::os::raw::c_char; +use std::path::PathBuf; + +/// Type of directory to lookup from macOS/iOS +#[repr(u64)] +pub enum SearchPathDirectory { + /// Applications directory, depending on domain. /Applications or ~/Applications typically + Application = 1, + AdminApplication = 4, + /// Library folder, can be /Library (system) or ~/Library (user) + Library = 5, + /// Location of usere's home directories, typically /Users + Users = 7, + /// Documentation, not sure if used... + Documentation = 8, + /// Documents folder, typically ~/Documents + Documents = 9, + AutosavedInformation = 11, + /// User's desktop folder, typically ~/Desktop + Desktop = 12, + /// Caches folder, Library/Caches + Caches = 13, + /// Applicatino support, Library/Application Support + /// + /// Typical home of non-userdefaults app data & settings + ApplicationSupport = 14, + /// Downloads folder, ~/Downloads + Downloads = 15, + /// Movies folder, ~/Movies + Movies = 17, + /// Music folder, ~/Music + Music = 18, + /// Pictures folder, ~/Pictures + Pictures = 19, + PrinterDescription = 20, + SharedPublic = 21, + PreferencePanes = 22, + ApplicationScripts = 23, + Trash = 102, +} + +#[repr(u64)] +pub enum SearchPathDomainMask { + UserDomain = 1, + LocalDomain = 2, + NetworkDomain = 4, + SystemDomain = 8, + AllDomains = 65535, +} + +/// Returns first path found on macOS/iOS systems given the requested type of path, within the domain +/// +/// Even if a path is returned, it may not exist yet and require creation +pub fn path_for_dir(dir: SearchPathDirectory, domain: SearchPathDomainMask) -> Option { + let mut result = None; + autoreleasepool(|| { + let cls = class!(NSFileManager); + unsafe { + let obj: *mut Object = msg_send![cls, defaultManager]; + let url: *mut Object = msg_send![obj, URLForDirectory:dir inDomain:domain as u64 appropriateForURL:0 create:false error:0]; + if !url.is_null() { + let path: *mut Object = msg_send![url, path]; + let s: *const c_char = msg_send![path, UTF8String]; + let c_str = CStr::from_ptr(s); + match c_str.to_str() { + Err(error) => { + println!("Error getting home dir string: {}", error); + } + Ok(string) => result = Some(PathBuf::from(string.to_owned())), + }; + } else { + println!("Failed to get dir"); + } + } + }); + result +} +/// Returns user's home directory, or sandbox if called within sandboxed app +pub fn home_dir() -> Option { + unsafe { + let mut result = None; + autoreleasepool(|| { + let cls = class!(NSFileManager); + let obj: *mut Object = msg_send![cls, defaultManager]; + let url: *mut Object = msg_send![obj, homeDirectoryForCurrentUser]; + let path: *mut Object = msg_send![url, path]; + let s: *const c_char = msg_send![path, UTF8String]; + let c_str = CStr::from_ptr(s); + match c_str.to_str() { + Err(error) => { + println!("Error getting home dir string: {}", error); + } + Ok(string) => result = Some(PathBuf::from(string.to_owned())), + }; + }); + result + } +} diff --git a/src/lib.rs b/src/lib.rs index 9061def..1c22ef1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,61 +15,63 @@ pub fn is_absolute_path(path: OsString) -> Option { #[cfg(all(unix, not(target_os = "redox")))] extern crate libc; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub mod apple; + #[cfg(all(unix, not(target_os = "redox")))] mod target_unix_not_redox { -use std::env; -use std::ffi::{CStr, OsString}; -use std::mem; -use std::os::unix::ffi::OsStringExt; -use std::path::PathBuf; -use std::ptr; + use std::env; + use std::ffi::{CStr, OsString}; + use std::mem; + use std::os::unix::ffi::OsStringExt; + use std::path::PathBuf; + use std::ptr; -use super::libc; + use super::libc; -// https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/os.rs#L498 -pub fn home_dir() -> Option { - return env::var_os("HOME") - .and_then(|h| if h.is_empty() { None } else { Some(h) }) - .or_else(|| unsafe { fallback() }) - .map(PathBuf::from); + // https://github.com/rust-lang/rust/blob/master/src/libstd/sys/unix/os.rs#L498 + pub fn home_dir() -> Option { + return env::var_os("HOME") + .and_then(|h| if h.is_empty() { None } else { Some(h) }) + .or_else(|| unsafe { fallback() }) + .map(PathBuf::from); - #[cfg(any(target_os = "android", target_os = "ios", target_os = "emscripten"))] - unsafe fn fallback() -> Option { - None - } - #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "emscripten")))] - unsafe fn fallback() -> Option { - let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) { - n if n < 0 => 512 as usize, - n => n as usize, - }; - let mut buf = Vec::with_capacity(amt); - let mut passwd: libc::passwd = mem::zeroed(); - let mut result = ptr::null_mut(); - match libc::getpwuid_r( - libc::getuid(), - &mut passwd, - buf.as_mut_ptr(), - buf.capacity(), - &mut result, - ) { - 0 if !result.is_null() => { - let ptr = passwd.pw_dir as *const _; - let bytes = CStr::from_ptr(ptr).to_bytes(); - if bytes.is_empty() { - None - } else { - Some(OsStringExt::from_vec(bytes.to_vec())) + #[cfg(any(target_os = "android", target_os = "ios", target_os = "emscripten"))] + unsafe fn fallback() -> Option { + None + } + #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "emscripten")))] + unsafe fn fallback() -> Option { + let amt = match libc::sysconf(libc::_SC_GETPW_R_SIZE_MAX) { + n if n < 0 => 512 as usize, + n => n as usize, + }; + let mut buf = Vec::with_capacity(amt); + let mut passwd: libc::passwd = mem::zeroed(); + let mut result = ptr::null_mut(); + match libc::getpwuid_r( + libc::getuid(), + &mut passwd, + buf.as_mut_ptr(), + buf.capacity(), + &mut result, + ) { + 0 if !result.is_null() => { + let ptr = passwd.pw_dir as *const _; + let bytes = CStr::from_ptr(ptr).to_bytes(); + if bytes.is_empty() { + None + } else { + Some(OsStringExt::from_vec(bytes.to_vec())) + } } + _ => None, } - _ => None, } } } -} - #[cfg(all(unix, not(target_os = "redox")))] pub use self::target_unix_not_redox::home_dir; @@ -79,18 +81,17 @@ extern crate redox_users; #[cfg(target_os = "redox")] mod target_redox { -use std::path::PathBuf; - -use super::redox_users::{All, AllUsers, Config}; + use std::path::PathBuf; -pub fn home_dir() -> Option { - let current_uid = redox_users::get_uid().ok()?; - let users = AllUsers::new(Config::default()).ok()?; - let user = users.get_by_id(current_uid)?; + use super::redox_users::{All, AllUsers, Config}; - Some(PathBuf::from(user.home.clone())) -} + pub fn home_dir() -> Option { + let current_uid = redox_users::get_uid().ok()?; + let users = AllUsers::new(Config::default()).ok()?; + let user = users.get_by_id(current_uid)?; + Some(PathBuf::from(user.home.clone())) + } } #[cfg(target_os = "redox")] @@ -102,30 +103,33 @@ mod xdg_user_dirs; #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] mod target_unix_not_mac { -use std::collections::HashMap; -use std::env; -use std::path::{Path, PathBuf}; - -use super::{home_dir, is_absolute_path}; -use super::xdg_user_dirs; + use std::collections::HashMap; + use std::env; + use std::path::{Path, PathBuf}; -fn user_dir_file(home_dir: &Path) -> PathBuf { - env::var_os("XDG_CONFIG_HOME").and_then(is_absolute_path).unwrap_or_else(|| home_dir.join(".config")).join("user-dirs.dirs") -} + use super::xdg_user_dirs; + use super::{home_dir, is_absolute_path}; -// this could be optimized further to not create a map and instead retrieve the requested path only -pub fn user_dir(user_dir_name: &str) -> Option { - if let Some(home_dir) = home_dir() { - xdg_user_dirs::single(&home_dir, &user_dir_file(&home_dir), user_dir_name).remove(user_dir_name) - } else { - None + fn user_dir_file(home_dir: &Path) -> PathBuf { + env::var_os("XDG_CONFIG_HOME") + .and_then(is_absolute_path) + .unwrap_or_else(|| home_dir.join(".config")) + .join("user-dirs.dirs") } -} -pub fn user_dirs(home_dir_path: &Path) -> HashMap { - xdg_user_dirs::all(home_dir_path, &user_dir_file(home_dir_path)) -} + // this could be optimized further to not create a map and instead retrieve the requested path only + pub fn user_dir(user_dir_name: &str) -> Option { + if let Some(home_dir) = home_dir() { + xdg_user_dirs::single(&home_dir, &user_dir_file(&home_dir), user_dir_name) + .remove(user_dir_name) + } else { + None + } + } + pub fn user_dirs(home_dir_path: &Path) -> HashMap { + xdg_user_dirs::all(home_dir_path, &user_dir_file(home_dir_path)) + } } #[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))] @@ -137,79 +141,79 @@ extern crate winapi; #[cfg(target_os = "windows")] mod target_windows { -use std::ffi::OsString; -use std::os::windows::ffi::OsStringExt; -use std::path::PathBuf; -use std::ptr; -use std::slice; - -use super::winapi; -use super::winapi::shared::winerror; -use super::winapi::um::{combaseapi, knownfolders, shlobj, shtypes, winbase, winnt}; - -pub fn known_folder(folder_id: shtypes::REFKNOWNFOLDERID) -> Option { - unsafe { - let mut path_ptr: winnt::PWSTR = ptr::null_mut(); - let result = shlobj::SHGetKnownFolderPath(folder_id, 0, ptr::null_mut(), &mut path_ptr); - if result == winerror::S_OK { - let len = winbase::lstrlenW(path_ptr) as usize; - let path = slice::from_raw_parts(path_ptr, len); - let ostr: OsString = OsStringExt::from_wide(path); - combaseapi::CoTaskMemFree(path_ptr as *mut winapi::ctypes::c_void); - Some(PathBuf::from(ostr)) - } else { - None + use std::ffi::OsString; + use std::os::windows::ffi::OsStringExt; + use std::path::PathBuf; + use std::ptr; + use std::slice; + + use super::winapi; + use super::winapi::shared::winerror; + use super::winapi::um::{combaseapi, knownfolders, shlobj, shtypes, winbase, winnt}; + + pub fn known_folder(folder_id: shtypes::REFKNOWNFOLDERID) -> Option { + unsafe { + let mut path_ptr: winnt::PWSTR = ptr::null_mut(); + let result = shlobj::SHGetKnownFolderPath(folder_id, 0, ptr::null_mut(), &mut path_ptr); + if result == winerror::S_OK { + let len = winbase::lstrlenW(path_ptr) as usize; + let path = slice::from_raw_parts(path_ptr, len); + let ostr: OsString = OsStringExt::from_wide(path); + combaseapi::CoTaskMemFree(path_ptr as *mut winapi::ctypes::c_void); + Some(PathBuf::from(ostr)) + } else { + None + } } } -} -pub fn known_folder_profile() -> Option { - known_folder(&knownfolders::FOLDERID_Profile) -} - -pub fn known_folder_roaming_app_data() -> Option { - known_folder(&knownfolders::FOLDERID_RoamingAppData) -} + pub fn known_folder_profile() -> Option { + known_folder(&knownfolders::FOLDERID_Profile) + } -pub fn known_folder_local_app_data() -> Option { - known_folder(&knownfolders::FOLDERID_LocalAppData) -} + pub fn known_folder_roaming_app_data() -> Option { + known_folder(&knownfolders::FOLDERID_RoamingAppData) + } -pub fn known_folder_music() -> Option { - known_folder(&knownfolders::FOLDERID_Music) -} + pub fn known_folder_local_app_data() -> Option { + known_folder(&knownfolders::FOLDERID_LocalAppData) + } -pub fn known_folder_desktop() -> Option { - known_folder(&knownfolders::FOLDERID_Desktop) -} + pub fn known_folder_music() -> Option { + known_folder(&knownfolders::FOLDERID_Music) + } -pub fn known_folder_documents() -> Option { - known_folder(&knownfolders::FOLDERID_Documents) -} + pub fn known_folder_desktop() -> Option { + known_folder(&knownfolders::FOLDERID_Desktop) + } -pub fn known_folder_downloads() -> Option { - known_folder(&knownfolders::FOLDERID_Downloads) -} + pub fn known_folder_documents() -> Option { + known_folder(&knownfolders::FOLDERID_Documents) + } -pub fn known_folder_pictures() -> Option { - known_folder(&knownfolders::FOLDERID_Pictures) -} + pub fn known_folder_downloads() -> Option { + known_folder(&knownfolders::FOLDERID_Downloads) + } -pub fn known_folder_public() -> Option { - known_folder(&knownfolders::FOLDERID_Public) -} -pub fn known_folder_templates() -> Option { - known_folder(&knownfolders::FOLDERID_Templates) -} -pub fn known_folder_videos() -> Option { - known_folder(&knownfolders::FOLDERID_Videos) -} + pub fn known_folder_pictures() -> Option { + known_folder(&knownfolders::FOLDERID_Pictures) + } + pub fn known_folder_public() -> Option { + known_folder(&knownfolders::FOLDERID_Public) + } + pub fn known_folder_templates() -> Option { + known_folder(&knownfolders::FOLDERID_Templates) + } + pub fn known_folder_videos() -> Option { + known_folder(&knownfolders::FOLDERID_Videos) + } } #[cfg(target_os = "windows")] pub use self::target_windows::{ - known_folder, known_folder_profile, known_folder_roaming_app_data, known_folder_local_app_data, - known_folder_music, known_folder_desktop, known_folder_documents, known_folder_downloads, - known_folder_pictures, known_folder_public, known_folder_templates, known_folder_videos + known_folder, known_folder_desktop, known_folder_documents, known_folder_downloads, + known_folder_local_app_data, known_folder_music, known_folder_pictures, known_folder_profile, + known_folder_public, known_folder_roaming_app_data, known_folder_templates, + known_folder_videos, }; From 3b1bfaa8b5b56ef2dcb23b93b8a9faf88db2c347 Mon Sep 17 00:00:00 2001 From: Jeremy Knope Date: Sat, 11 Jul 2020 19:20:40 -0400 Subject: [PATCH 2/5] fix panic due to missing linked framework --- src/apple.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/apple.rs b/src/apple.rs index adb77e3..7c2c91f 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -5,6 +5,10 @@ use std::ffi::CStr; use std::os::raw::c_char; use std::path::PathBuf; +// We need to link to Foundation to access the NSFileManager class +#[link(name = "Foundation", kind = "framework")] +extern "C" {} + /// Type of directory to lookup from macOS/iOS #[repr(u64)] pub enum SearchPathDirectory { From 9a56c39cf1aa4e2fb4b053b8eb8f9a5889f13b47 Mon Sep 17 00:00:00 2001 From: Jeremy Knope Date: Sat, 11 Jul 2020 23:18:44 -0400 Subject: [PATCH 3/5] cleanup, update cfg checks for new apple APIs --- src/apple.rs | 14 +++++++++++++- src/lib.rs | 14 +++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/apple.rs b/src/apple.rs index 7c2c91f..a576d8e 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -40,23 +40,34 @@ pub enum SearchPathDirectory { Music = 18, /// Pictures folder, ~/Pictures Pictures = 19, + /// PPDs folder, Library/Printers/PPDs PrinterDescription = 20, + /// Public folder, ~/Public SharedPublic = 21, + /// Preference Panes, Library/PreferencePanes PreferencePanes = 22, + /// User scripts folder for calling application, ~/Library/Application Scripts/code-signing-id ApplicationScripts = 23, + /// Trash folder Trash = 102, } +/// Domain for path to return, dirs currently mostly deals with user dirs so likely want UserDomain #[repr(u64)] pub enum SearchPathDomainMask { + /// Looks up directory in user's domain, so ~ UserDomain = 1, + /// Local system domain, which is folders typically found in /Library LocalDomain = 2, + /// Publically available locations on the local network NetworkDomain = 4, + /// Read only system locations, /System (may be completely unavailable on newer systems?) SystemDomain = 8, + /// Looks up directories in all of the current domains and future ones apple may add AllDomains = 65535, } -/// Returns first path found on macOS/iOS systems given the requested type of path, within the domain +/// Returns first path found on macOS/iOS systems given the requested type of path, within given domain /// /// Even if a path is returned, it may not exist yet and require creation pub fn path_for_dir(dir: SearchPathDirectory, domain: SearchPathDomainMask) -> Option { @@ -83,6 +94,7 @@ pub fn path_for_dir(dir: SearchPathDirectory, domain: SearchPathDomainMask) -> O }); result } + /// Returns user's home directory, or sandbox if called within sandboxed app pub fn home_dir() -> Option { unsafe { diff --git a/src/lib.rs b/src/lib.rs index 1c22ef1..59e763c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,9 +16,14 @@ pub fn is_absolute_path(path: OsString) -> Option { extern crate libc; #[cfg(any(target_os = "macos", target_os = "ios"))] -pub mod apple; +mod apple; +#[cfg(any(target_os = "macos", target_os = "ios"))] +pub use apple::{home_dir, path_for_dir, SearchPathDirectory, SearchPathDomainMask}; -#[cfg(all(unix, not(target_os = "redox")))] +#[cfg(all( + unix, + not(any(target_os = "redox", target_os = "macos", target_os = "ios")) +))] mod target_unix_not_redox { use std::env; @@ -72,7 +77,10 @@ mod target_unix_not_redox { } } -#[cfg(all(unix, not(target_os = "redox")))] +#[cfg(all( + unix, + not(any(target_os = "redox", target_os = "macos", target_os = "ios")) +))] pub use self::target_unix_not_redox::home_dir; #[cfg(target_os = "redox")] From d817267ced94aa8991fa82d6874d3c6915ad5379 Mon Sep 17 00:00:00 2001 From: Jeremy Knope Date: Mon, 13 Jul 2020 15:22:32 -0400 Subject: [PATCH 4/5] fix build issue due to older rust usage --- Cargo.toml | 1 - src/lib.rs | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1d24690..79281d9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://github.com/dirs-dev/dirs-sys-rs" maintenance = { status = "as-is" } -edition = "2018" [target.'cfg(unix)'.dependencies] libc = "0.2" diff --git a/src/lib.rs b/src/lib.rs index 59e763c..63d35e5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,9 @@ pub fn is_absolute_path(path: OsString) -> Option { #[cfg(all(unix, not(target_os = "redox")))] extern crate libc; +#[cfg(any(target_os = "macos", target_os = "ios"))] +extern crate objc; + #[cfg(any(target_os = "macos", target_os = "ios"))] mod apple; #[cfg(any(target_os = "macos", target_os = "ios"))] From 57850e47051c2eea13354be98165925c50e8c3e7 Mon Sep 17 00:00:00 2001 From: Jeremy Knope Date: Mon, 13 Jul 2020 22:40:04 -0400 Subject: [PATCH 5/5] fix macro build issues with older rust --- src/apple.rs | 1 - src/lib.rs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apple.rs b/src/apple.rs index a576d8e..592a8e1 100644 --- a/src/apple.rs +++ b/src/apple.rs @@ -1,6 +1,5 @@ use objc::rc::autoreleasepool; use objc::runtime::Object; -use objc::{class, msg_send, sel, sel_impl}; use std::ffi::CStr; use std::os::raw::c_char; use std::path::PathBuf; diff --git a/src/lib.rs b/src/lib.rs index 63d35e5..4e3bc15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -16,6 +16,7 @@ pub fn is_absolute_path(path: OsString) -> Option { extern crate libc; #[cfg(any(target_os = "macos", target_os = "ios"))] +#[macro_use] extern crate objc; #[cfg(any(target_os = "macos", target_os = "ios"))]