diff --git a/.gitignore b/.gitignore index 06aba01..179cd73 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ Cargo.lock /target +**/*.rs.bk +.idea \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index f55b731..6353d85 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,11 +9,9 @@ repository = "https://github.com/soc/directories-rs" maintenance = { status = "actively-developed" } keywords = ["xdg", "basedir", "app_dirs", "path", "folder"] -[target.'cfg(unix)'.dependencies] -libc = "0.2" - -[target.'cfg(windows)'.dependencies] -winapi = { version = "0.3", features = ["knownfolders", "objbase", "shlobj", "winbase", "winerror"] } +[dependencies] +cfg-if = "0.1" +dirs-sys = "" [dev-dependencies] bencher = "0.1.5" diff --git a/README.md b/README.md index 83a0080..2db0400 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ - a tiny mid-level library with a minimal API - that provides the platform-specific, user-accessible locations - for retrieving and storing configuration, cache and other data -- on Linux, Windows (≥ Vista), macOS and other platforms. +- on Linux, Redox, Windows (≥ Vista), macOS and other platforms. The library provides the location of these directories by leveraging the mechanisms defined by - the [XDG base directory](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html) and @@ -23,7 +23,7 @@ The library provides the location of these directories by leveraging the mechani ## Platforms -This library is written in Rust, and supports Linux, macOS and Windows. +This library is written in Rust, and supports Linux, Redox, macOS and Windows. Other platforms are also supported; they use the Linux conventions. _dirs_, the low-level sister library, is available at [dirs-rs](https://github.com/soc/dirs-rs). @@ -194,10 +194,19 @@ Please take this table with a grain of salt: a different crate might very well b - Proj: Supports [project-specific base directories](#projectdirs) - Conv: Follows naming conventions of the operating system it runs on -## Versioning +## Build -After 1.0, the version number of this library consists of a whole number, which is incremented with each release. -(Think semantic versioning without _minor_ and _patch_ versions.) +It's possible to cross-compile this library if the necessary toolchains are installed with rustup. +This is helpful to ensure a change has not broken compilation on a different platform. + +The following commands will build this library on Linux, macOS and Windows: + +``` +cargo build --target=x86_64-unknown-linux-gnu +cargo build --target=x86_64-pc-windows-gnu +cargo build --target=x86_64-apple-darwin +cargo build --target=x86_64-unknown-redox +``` ## License diff --git a/src/lib.rs b/src/lib.rs index fd0bd57..9df53c6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,7 +3,7 @@ //! - a tiny library with a minimal API (3 structs, 4 factory functions, getters) //! - that provides the platform-specific, user-accessible locations //! - for finding and storing configuration, cache and other data -//! - on Linux, Windows (≥ Vista) and macOS. +//! - on Linux, Redox, Windows (≥ Vista) and macOS. //! //! The library provides the location of these directories by leveraging the mechanisms defined by //! @@ -14,17 +14,27 @@ #![deny(missing_docs)] +#[macro_use] +extern crate cfg_if; + use std::path::Path; use std::path::PathBuf; -#[cfg(target_os = "windows")] mod win; -#[cfg(target_os = "macos")] mod mac; -#[cfg(not(any(target_os = "windows", target_os = "macos")))] mod lin; -#[cfg(unix)] mod unix; - -#[cfg(target_os = "windows")] use win as sys; -#[cfg(target_os = "macos")] use mac as sys; -#[cfg(not(any(target_os = "windows", target_os = "macos")))] use lin as sys; +cfg_if! { + if #[cfg(target_os = "windows")] { + mod win; + use win as sys; + } else if #[cfg(target_os = "macos")] { + mod mac; + use mac as sys; + } else if #[cfg(target_os = "wasi")] { + mod wasi; + use wasi as sys; + } else { + mod lin; + use lin as sys; + } +} /// `BaseDirs` provides paths of user-invisible standard directories, following the conventions of the operating system the library is running on. /// diff --git a/src/lin.rs b/src/lin.rs index 720881f..331070a 100644 --- a/src/lin.rs +++ b/src/lin.rs @@ -1,23 +1,21 @@ +extern crate dirs_sys; + use std::env; -use std::ffi::OsString; use std::path::PathBuf; -use std::process::Command; use BaseDirs; use UserDirs; use ProjectDirs; -use unix; - pub fn base_dirs() -> Option { - if let Some(home_dir) = unix::home_dir() { - let cache_dir = env::var_os("XDG_CACHE_HOME") .and_then(is_absolute_path).unwrap_or_else(|| home_dir.join(".cache")); - let config_dir = env::var_os("XDG_CONFIG_HOME").and_then(is_absolute_path).unwrap_or_else(|| home_dir.join(".config")); - let data_dir = env::var_os("XDG_DATA_HOME") .and_then(is_absolute_path).unwrap_or_else(|| home_dir.join(".local/share")); + if let Some(home_dir) = dirs_sys::home_dir() { + let cache_dir = env::var_os("XDG_CACHE_HOME") .and_then(dirs_sys::is_absolute_path).unwrap_or_else(|| home_dir.join(".cache")); + let config_dir = env::var_os("XDG_CONFIG_HOME").and_then(dirs_sys::is_absolute_path).unwrap_or_else(|| home_dir.join(".config")); + let data_dir = env::var_os("XDG_DATA_HOME") .and_then(dirs_sys::is_absolute_path).unwrap_or_else(|| home_dir.join(".local/share")); let data_local_dir = data_dir.clone(); - let runtime_dir = env::var_os("XDG_RUNTIME_DIR").and_then(is_absolute_path); + let runtime_dir = env::var_os("XDG_RUNTIME_DIR").and_then(dirs_sys::is_absolute_path); let executable_dir = - env::var_os("XDG_BIN_HOME").and_then(is_absolute_path).unwrap_or_else(|| { + env::var_os("XDG_BIN_HOME").and_then(dirs_sys::is_absolute_path).unwrap_or_else(|| { let mut new_dir = data_dir.clone(); new_dir.pop(); new_dir.push("bin"); new_dir }); let base_dirs = BaseDirs { @@ -36,21 +34,21 @@ pub fn base_dirs() -> Option { } pub fn user_dirs() -> Option { - if let Some(home_dir) = unix::home_dir() { - let data_dir = env::var_os("XDG_DATA_HOME").and_then(is_absolute_path).unwrap_or_else(|| home_dir.join(".local/share")); + if let Some(home_dir) = dirs_sys::home_dir() { + let data_dir = env::var_os("XDG_DATA_HOME").and_then(dirs_sys::is_absolute_path).unwrap_or_else(|| home_dir.join(".local/share")); let font_dir = data_dir.join("fonts"); let user_dirs = UserDirs { home_dir: home_dir, - audio_dir: run_xdg_user_dir_command("MUSIC"), - desktop_dir: run_xdg_user_dir_command("DESKTOP"), - document_dir: run_xdg_user_dir_command("DOCUMENTS"), - download_dir: run_xdg_user_dir_command("DOWNLOAD"), + audio_dir: dirs_sys::user_dir("MUSIC"), + desktop_dir: dirs_sys::user_dir("DESKTOP"), + document_dir: dirs_sys::user_dir("DOCUMENTS"), + download_dir: dirs_sys::user_dir("DOWNLOAD"), font_dir: Some(font_dir), - picture_dir: run_xdg_user_dir_command("PICTURES"), - public_dir: run_xdg_user_dir_command("PUBLICSHARE"), - template_dir: run_xdg_user_dir_command("TEMPLATES"), - video_dir: run_xdg_user_dir_command("VIDEOS") + picture_dir: dirs_sys::user_dir("PICTURES"), + public_dir: dirs_sys::user_dir("PUBLICSHARE"), + template_dir: dirs_sys::user_dir("TEMPLATES"), + video_dir: dirs_sys::user_dir("VIDEOS") }; Some(user_dirs) } else { @@ -59,12 +57,12 @@ pub fn user_dirs() -> Option { } pub fn project_dirs_from_path(project_path: PathBuf) -> Option { - if let Some(home_dir) = unix::home_dir() { - let cache_dir = env::var_os("XDG_CACHE_HOME") .and_then(is_absolute_path).unwrap_or_else(|| home_dir.join(".cache")).join(&project_path); - let config_dir = env::var_os("XDG_CONFIG_HOME").and_then(is_absolute_path).unwrap_or_else(|| home_dir.join(".config")).join(&project_path); - let data_dir = env::var_os("XDG_DATA_HOME") .and_then(is_absolute_path).unwrap_or_else(|| home_dir.join(".local/share")).join(&project_path); + if let Some(home_dir) = dirs_sys::home_dir() { + let cache_dir = env::var_os("XDG_CACHE_HOME") .and_then(dirs_sys::is_absolute_path).unwrap_or_else(|| home_dir.join(".cache")).join(&project_path); + let config_dir = env::var_os("XDG_CONFIG_HOME").and_then(dirs_sys::is_absolute_path).unwrap_or_else(|| home_dir.join(".config")).join(&project_path); + let data_dir = env::var_os("XDG_DATA_HOME") .and_then(dirs_sys::is_absolute_path).unwrap_or_else(|| home_dir.join(".local/share")).join(&project_path); let data_local_dir = data_dir.clone(); - let runtime_dir = env::var_os("XDG_RUNTIME_DIR").and_then(is_absolute_path).map(|o| o.join(&project_path)); + let runtime_dir = env::var_os("XDG_RUNTIME_DIR").and_then(dirs_sys::is_absolute_path).map(|o| o.join(&project_path)); let project_dirs = ProjectDirs { project_path: project_path, @@ -84,28 +82,6 @@ pub fn project_dirs_from(_qualifier: &str, _organization: &str, application: &st ProjectDirs::from_path(PathBuf::from(&trim_and_lowercase_then_replace_spaces(application, ""))) } -// we don't need to explicitly handle empty strings in the code above, -// because an empty string is not considered to be a absolute path here. -fn is_absolute_path(path: OsString) -> Option { - let path = PathBuf::from(path); - if path.is_absolute() { - Some(path) - } else { - None - } -} - -fn run_xdg_user_dir_command(arg: &str) -> Option { - use std::os::unix::ffi::OsStringExt; - let mut out = match Command::new("xdg-user-dir").arg(arg).output() { - Ok(output) => output.stdout, - Err(_) => return None, - }; - let out_len = out.len(); - out.truncate(out_len - 1); - Some(PathBuf::from(OsString::from_vec(out))) -} - fn trim_and_lowercase_then_replace_spaces(name: &str, replacement: &str) -> String { let mut buf = String::with_capacity(name.len()); let mut parts = name.split_whitespace(); diff --git a/src/mac.rs b/src/mac.rs index a4cbbcb..ad42db5 100644 --- a/src/mac.rs +++ b/src/mac.rs @@ -1,13 +1,13 @@ +extern crate dirs_sys; + use std::path::PathBuf; use BaseDirs; use UserDirs; use ProjectDirs; -use unix; - pub fn base_dirs() -> Option { - if let Some(home_dir) = unix::home_dir() { + if let Some(home_dir) = dirs_sys::home_dir() { let cache_dir = home_dir.join("Library/Caches"); let config_dir = home_dir.join("Library/Preferences"); let data_dir = home_dir.join("Library/Application Support"); @@ -29,7 +29,7 @@ pub fn base_dirs() -> Option { } pub fn user_dirs() -> Option { - if let Some(home_dir) = unix::home_dir() { + if let Some(home_dir) = dirs_sys::home_dir() { let audio_dir = home_dir.join("Music"); let desktop_dir = home_dir.join("Desktop"); let document_dir = home_dir.join("Documents"); @@ -58,7 +58,7 @@ pub fn user_dirs() -> Option { } pub fn project_dirs_from_path(project_path: PathBuf) -> Option { - if let Some(home_dir) = unix::home_dir() { + if let Some(home_dir) = dirs_sys::home_dir() { let cache_dir = home_dir.join("Library/Caches").join(&project_path); let config_dir = home_dir.join("Library/Preferences").join(&project_path); let data_dir = home_dir.join("Library/Application Support").join(&project_path); diff --git a/src/unix.rs b/src/unix.rs deleted file mode 100644 index 3b16b06..0000000 --- a/src/unix.rs +++ /dev/null @@ -1,49 +0,0 @@ -#![cfg(unix)] - -use std::env; -use std::path::PathBuf; -use std::mem; -use std::ptr; -use std::ffi::CStr; -use std::ffi::OsString; -use std::os::unix::ffi::OsStringExt; - -extern crate 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); - - #[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, - } - } -} - diff --git a/src/wasi.rs b/src/wasi.rs new file mode 100644 index 0000000..f97c6d4 --- /dev/null +++ b/src/wasi.rs @@ -0,0 +1,12 @@ +// Stub definitions to make things *compile*. + +use std::path::PathBuf; + +use BaseDirs; +use UserDirs; +use ProjectDirs; + +pub fn base_dirs() -> Option { None } +pub fn user_dirs() -> Option { None } +pub fn project_dirs_from_path(project_path: PathBuf) -> Option { None } +pub fn project_dirs_from(qualifier: &str, organization: &str, application: &str) -> Option { None } diff --git a/src/win.rs b/src/win.rs index 57d6765..4adf380 100644 --- a/src/win.rs +++ b/src/win.rs @@ -1,25 +1,16 @@ -use std; +extern crate dirs_sys; + use std::path::PathBuf; use std::iter::FromIterator; -extern crate winapi; -use self::winapi::shared::winerror; -use self::winapi::um::knownfolders; -use self::winapi::um::combaseapi; -use self::winapi::um::shlobj; -use self::winapi::um::shtypes; -use self::winapi::um::winbase; -use self::winapi::um::winnt; - - use BaseDirs; use UserDirs; use ProjectDirs; pub fn base_dirs() -> Option { - let home_dir = known_folder(&knownfolders::FOLDERID_Profile); - let data_dir = known_folder(&knownfolders::FOLDERID_RoamingAppData); - let data_local_dir = known_folder(&knownfolders::FOLDERID_LocalAppData); + let home_dir = dirs_sys::known_folder_profile(); + let data_dir = dirs_sys::known_folder_roaming_app_data(); + let data_local_dir = dirs_sys::known_folder_local_app_data(); if let (Some(home_dir), Some(data_dir), Some(data_local_dir)) = (home_dir, data_dir, data_local_dir) { let cache_dir = data_local_dir.clone(); let config_dir = data_dir.clone(); @@ -40,15 +31,15 @@ pub fn base_dirs() -> Option { } pub fn user_dirs() -> Option { - if let Some(home_dir) = known_folder(&knownfolders::FOLDERID_Profile) { - let audio_dir = known_folder(&knownfolders::FOLDERID_Music); - let desktop_dir = known_folder(&knownfolders::FOLDERID_Desktop); - let document_dir = known_folder(&knownfolders::FOLDERID_Documents); - let download_dir = known_folder(&knownfolders::FOLDERID_Downloads); - let picture_dir = known_folder(&knownfolders::FOLDERID_Pictures); - let public_dir = known_folder(&knownfolders::FOLDERID_Public); - let template_dir = known_folder(&knownfolders::FOLDERID_Templates); - let video_dir = known_folder(&knownfolders::FOLDERID_Videos); + if let Some(home_dir) = dirs_sys::known_folder_profile() { + let audio_dir = dirs_sys::known_folder_music(); + let desktop_dir = dirs_sys::known_folder_desktop(); + let document_dir = dirs_sys::known_folder_documents(); + let download_dir = dirs_sys::known_folder_downloads(); + let picture_dir = dirs_sys::known_folder_pictures(); + let public_dir = dirs_sys::known_folder_public(); + let template_dir = dirs_sys::known_folder_templates(); + let video_dir = dirs_sys::known_folder_videos(); let user_dirs = UserDirs { home_dir: home_dir, @@ -69,8 +60,8 @@ pub fn user_dirs() -> Option { } pub fn project_dirs_from_path(project_path: PathBuf) -> Option { - let app_data_local = known_folder(&knownfolders::FOLDERID_LocalAppData); - let app_data_roaming = known_folder(&knownfolders::FOLDERID_RoamingAppData); + let app_data_local = dirs_sys::known_folder_local_app_data(); + let app_data_roaming = dirs_sys::known_folder_roaming_app_data(); if let (Some(app_data_local), Some(app_data_roaming)) = (app_data_local, app_data_roaming) { let app_data_local = app_data_local.join(&project_path); let app_data_roaming = app_data_roaming.join(&project_path); @@ -97,19 +88,3 @@ pub fn project_dirs_from_path(project_path: PathBuf) -> Option { pub fn project_dirs_from(_qualifier: &str, organization: &str, application: &str) -> Option { ProjectDirs::from_path(PathBuf::from_iter(&[organization, application])) } - -fn known_folder(folder_id: shtypes::REFKNOWNFOLDERID) -> Option { - unsafe { - let mut path_ptr: winnt::PWSTR = std::ptr::null_mut(); - let result = shlobj::SHGetKnownFolderPath(folder_id, 0, std::ptr::null_mut(), &mut path_ptr); - if result == winerror::S_OK { - let len = winbase::lstrlenW(path_ptr) as usize; - let path = std::slice::from_raw_parts(path_ptr, len); - let ostr: std::ffi::OsString = std::os::windows::ffi::OsStringExt::from_wide(path); - combaseapi::CoTaskMemFree(path_ptr as *mut winapi::ctypes::c_void); - Some(PathBuf::from(ostr)) - } else { - None - } - } -} \ No newline at end of file