|
| 1 | +//! XDG (X Desktop Group) related functionality for Unix platforms. |
| 2 | +//! |
| 3 | +//! The [XDG Base Directory Specification][basedir] defines where user-specific |
| 4 | +//! files should be looked for relative to a set of base directories. The |
| 5 | +//! functions in this module provide those directory paths as configured by |
| 6 | +//! the environment. |
| 7 | +//! |
| 8 | +//! Note that the use of these functions is not enforced by the system, and as |
| 9 | +//! such, not all programs will necessarily respect all details of the XDG path |
| 10 | +//! environment. This is a set of guidelines, and each program is ultimately |
| 11 | +//! responsible for defining where and how it both reads and writes files. |
| 12 | +//! |
| 13 | +//! Use of XDG paths can be generally considered the conventional expectation |
| 14 | +//! on Linux-based systems. Other Unix-based systems may or may not play well |
| 15 | +//! with the XDG conventions. |
| 16 | +//! |
| 17 | +//! Directories returned by this module are not guaranteed to exist yet. If the |
| 18 | +//! directory does not exist, an application should attempt to create it with |
| 19 | +//! [permissions mode][super::super::fs::PermissionsExt::from_mode] `0o700`. |
| 20 | +//! |
| 21 | +//! [basedir]: https://specifications.freedesktop.org/basedir/latest/ |
| 22 | +#![unstable(feature = "xdg_basedir", issue = "157515")] |
| 23 | + |
| 24 | +use crate::env::{SplitPaths, home_dir, split_paths, var_os}; |
| 25 | +use crate::ffi::{OsStr, OsString}; |
| 26 | +use crate::path::{Path, PathBuf}; |
| 27 | + |
| 28 | +fn xdg_home_dir() -> PathBuf { |
| 29 | + // Note: home_dir can return `Some("")` in some cases. We assume that in |
| 30 | + // this case the expected behavior is for `$HOME/path` to become `/path`, |
| 31 | + // i.e. the home directory is effectively `/`. |
| 32 | + match home_dir() { |
| 33 | + None => panic!("an XDG environment should have a home directory"), |
| 34 | + Some(home) if home.is_empty() => PathBuf::from("/"), |
| 35 | + Some(home) => home, |
| 36 | + } |
| 37 | +} |
| 38 | + |
| 39 | +fn xdg_dir(env: &str, fallback_home_subdir: impl AsRef<Path>) -> PathBuf { |
| 40 | + var_os(env) |
| 41 | + .filter(|s| !s.is_empty()) |
| 42 | + .map(PathBuf::from) |
| 43 | + .unwrap_or_else(|| xdg_home_dir().join(fallback_home_subdir)) |
| 44 | +} |
| 45 | + |
| 46 | +/// A base directory relative to which user-specific data files should be written. |
| 47 | +/// |
| 48 | +/// An application `appid` would typically be expected to write its data files |
| 49 | +/// to `{data_home_dir}/{appid}/**/*`. |
| 50 | +pub fn data_home_dir() -> PathBuf { |
| 51 | + xdg_dir("XDG_DATA_HOME", ".local/share") |
| 52 | +} |
| 53 | + |
| 54 | +/// A base directory relative to which user-specific configuration files should be written. |
| 55 | +/// |
| 56 | +/// An application `appid` would typically be expected to write its configuration |
| 57 | +/// files to `{config_home_dir}/{appid}/**/*`. |
| 58 | +pub fn config_home_dir() -> PathBuf { |
| 59 | + xdg_dir("XDG_CONFIG_HOME", ".config") |
| 60 | +} |
| 61 | + |
| 62 | +/// A base directory relative to which user-specific state data should be written. |
| 63 | +/// |
| 64 | +/// An application `appid` would typically be expected to write its state data to |
| 65 | +/// `{state_home_dir}/{appid}/**/*`. |
| 66 | +/// |
| 67 | +/// Common kinds of state data include actions history (such as logs, history, |
| 68 | +/// recently used files, etc.) and state of the application that can be reused |
| 69 | +/// after application restart (such as view, layout, open files, undo history, |
| 70 | +/// etc.). |
| 71 | +pub fn state_home_dir() -> PathBuf { |
| 72 | + xdg_dir("XDG_STATE_HOME", ".local/state") |
| 73 | +} |
| 74 | + |
| 75 | +/// A base directory relative to which user-specific non-essential caches should be written. |
| 76 | +/// |
| 77 | +/// An application `appid` would typically be expected to write its cache data to |
| 78 | +/// `{cache_home_dir}/{appid}/**/*`. |
| 79 | +pub fn cache_home_dir() -> PathBuf { |
| 80 | + xdg_dir("XDG_CACHE_HOME", ".cache") |
| 81 | +} |
| 82 | + |
| 83 | +/// An iterator that produces directory paths from XDG environment configuration. |
| 84 | +/// |
| 85 | +/// The iterator element type is [`PathBuf`]. |
| 86 | +/// |
| 87 | +/// This structure is created by [`xdg::data_dirs`] and [`xdg::config_dirs`]. |
| 88 | +/// See the documentation of those functions for more. |
| 89 | +/// |
| 90 | +/// [`xdg::data_dirs`]: data_dirs |
| 91 | +/// [`xdg::config_dirs`]: config_dirs |
| 92 | +// |
| 93 | +// This stores Option so we can track when we have a trailing empty component. |
| 94 | +// None is an exhausted iterator, Some("") is a trailing empty component. |
| 95 | +#[derive(Debug, Clone)] |
| 96 | +pub struct XdgDirsIter(Option<OsString>); |
| 97 | + |
| 98 | +impl XdgDirsIter { |
| 99 | + fn new(env: &str, default: impl AsRef<OsStr>) -> Self { |
| 100 | + let dirs = var_os(env).filter(|s| !s.is_empty()).unwrap_or_else(|| default.as_ref().into()); |
| 101 | + Self(Some(dirs)) |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +impl Iterator for XdgDirsIter { |
| 106 | + type Item = PathBuf; |
| 107 | + |
| 108 | + fn next(&mut self) -> Option<Self::Item> { |
| 109 | + let dirs = self.0.take()?; |
| 110 | + let next = split_paths(&dirs).next()?; |
| 111 | + let len = next.as_os_str().len(); |
| 112 | + let mut bytes = dirs.into_encoded_bytes(); |
| 113 | + if len < bytes.len() { |
| 114 | + // Remove the path about to be returned and the separator after it. |
| 115 | + bytes.drain(..len + 1); |
| 116 | + // SAFETY: UNIX guarantees that the path separator is b':'. As `bytes` |
| 117 | + // now holds the suffix after the separator, it's a valid OsStr. |
| 118 | + self.0 = Some(unsafe { OsString::from_encoded_bytes_unchecked(bytes) }); |
| 119 | + } |
| 120 | + Some(next) |
| 121 | + } |
| 122 | + |
| 123 | + fn size_hint(&self) -> (usize, Option<usize>) { |
| 124 | + split_paths(&self.0).size_hint() |
| 125 | + } |
| 126 | +} |
| 127 | + |
| 128 | +/// A set of preference ordered directories relative to which data files should be searched. |
| 129 | +/// |
| 130 | +/// If an application defines a data file to be at `$XDG_DATA_DIRS/appid/file.name`, this means that: |
| 131 | +/// |
| 132 | +/// - The initial data file should be installed to `{system_data_dir}/appid/file.name`. |
| 133 | +/// - A user-specific version of the data file may be created at |
| 134 | +/// <code>{[data_home_dir][]()}/appid/file.name</code>. |
| 135 | +/// - Lookups for the data file should search for `./appid/file.name` relative to |
| 136 | +/// `data_home_dir` and each directory in `data_dirs`, giving preference to |
| 137 | +/// files found relative to an earlier directory in the search order. |
| 138 | +/// |
| 139 | +/// An application may choose to handle a file being located under multiple base |
| 140 | +/// directories however it sees fit, so long as it respects the search order. |
| 141 | +/// For example, it could say that only the first file found is used, or that |
| 142 | +/// data within the files is merged in some way. |
| 143 | +pub fn data_dirs() -> XdgDirsIter { |
| 144 | + // NB: the spec uses trailing slashes only for this default, for some reason |
| 145 | + XdgDirsIter::new("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/") |
| 146 | +} |
| 147 | + |
| 148 | +/// A set of preference ordered directories relative to which configuration files should be searched. |
| 149 | +/// |
| 150 | +/// If an application defines a configuration file to be at `$XDG_CONFIG_DIRS/appid/file.name`, this means that: |
| 151 | +/// |
| 152 | +/// - The initial configuration file should be installed to `{system_config_dir}/xdg/appid/file.name`. |
| 153 | +/// - A user-specific version of the configuration file may be created at |
| 154 | +/// <code>{[config_home_dir][]()}/appid/file.name</code>. |
| 155 | +/// - Lookups for the configuration file should search for `./appid/file.name` |
| 156 | +/// relative to `config_home_dir` and each directory in `config_dirs`, giving |
| 157 | +/// preference to files found relative to an earlier directory in the search order. |
| 158 | +/// |
| 159 | +/// An application may choose to handle a file being located under multiple base |
| 160 | +/// directories however it sees fit, so long as it respects the search order. |
| 161 | +/// For example, it could say that only the first file found is used, or that |
| 162 | +/// data within the files is merged in some way. |
| 163 | +pub fn config_dirs() -> XdgDirsIter { |
| 164 | + XdgDirsIter::new("XDG_CONFIG_DIRS", "/etc/xdg") |
| 165 | +} |
0 commit comments