|
| 1 | +//! XDG (X Desktop Group) related functionality for Unix platforms. |
| 2 | +//! |
| 3 | +//! The [XDG Base Directory Specification][basedir] defines a set of base |
| 4 | +//! directories, relative to which user-specific files should be looked for. 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::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::{home_dir, split_paths, var_os}; |
| 25 | +use crate::ffi::{OsStr, OsString}; |
| 26 | +use crate::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: &str) -> 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 (cached) data 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 | +#[derive(Debug, Clone)] |
| 93 | +pub struct XdgDirsIter { |
| 94 | + list: OsString, |
| 95 | + off: usize, |
| 96 | +} |
| 97 | + |
| 98 | +impl XdgDirsIter { |
| 99 | + fn new(env: &str, default: &str) -> Self { |
| 100 | + let dirs = var_os(env).filter(|s| !s.is_empty()).unwrap_or_else(|| default.into()); |
| 101 | + Self { list: dirs, off: 0 } |
| 102 | + } |
| 103 | + |
| 104 | + fn remaining(&self) -> Option<&OsStr> { |
| 105 | + self.list.as_encoded_bytes().get(self.off..).map(|bytes| { |
| 106 | + // SAFETY: `self.off` is the index after a path separator (or the |
| 107 | + // start of the string), so is a valid OsStr boundary. |
| 108 | + unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } |
| 109 | + }) |
| 110 | + } |
| 111 | +} |
| 112 | + |
| 113 | +impl Iterator for XdgDirsIter { |
| 114 | + type Item = PathBuf; |
| 115 | + |
| 116 | + fn next(&mut self) -> Option<Self::Item> { |
| 117 | + let rest = self.remaining()?; |
| 118 | + let next = split_paths(rest).next()?; |
| 119 | + let len = next.as_os_str().len(); |
| 120 | + self.off += len + 1; // Offset after this path and the separator after it. |
| 121 | + Some(next) |
| 122 | + } |
| 123 | + |
| 124 | + fn size_hint(&self) -> (usize, Option<usize>) { |
| 125 | + let Some(dirs) = self.remaining() else { return (0, Some(0)) }; |
| 126 | + split_paths(dirs).size_hint() |
| 127 | + } |
| 128 | +} |
| 129 | + |
| 130 | +/// A set of preference ordered directories relative to which data files should be searched. |
| 131 | +/// |
| 132 | +/// If an application defines a data file to be at `$XDG_DATA_DIRS/appid/file.name`, this means that: |
| 133 | +/// |
| 134 | +/// - The initial data file should be installed to `{system_data_dir}/appid/file.name`. |
| 135 | +/// - A user-specific version of the data file may be created at |
| 136 | +/// <code>{[data_home_dir][]()}/appid/file.name</code>. |
| 137 | +/// - Lookups for the data file should search for `./appid/file.name` relative to |
| 138 | +/// `data_home_dir` and each directory in `data_dirs`, giving preference to |
| 139 | +/// files found relative to an earlier directory in the search order. |
| 140 | +/// |
| 141 | +/// An application may choose to handle a file being located under multiple base |
| 142 | +/// directories however it sees fit, so long as it respects the search order. |
| 143 | +/// For example, it could say that only the first file found is used, or that |
| 144 | +/// data within the files is merged in some way. |
| 145 | +pub fn data_dirs() -> XdgDirsIter { |
| 146 | + // NB: the spec uses trailing slashes only for this default, for some reason |
| 147 | + XdgDirsIter::new("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/") |
| 148 | +} |
| 149 | + |
| 150 | +/// A set of preference ordered directories relative to which configuration files should be searched. |
| 151 | +/// |
| 152 | +/// If an application defines a configuration file to be at `$XDG_CONFIG_DIRS/appid/file.name`, this means that: |
| 153 | +/// |
| 154 | +/// - The initial configuration file should be installed to `{system_config_dir}/xdg/appid/file.name`. |
| 155 | +/// - A user-specific version of the configuration file may be created at |
| 156 | +/// <code>{[config_home_dir][]()}/appid/file.name</code>. |
| 157 | +/// - Lookups for the configuration file should search for `./appid/file.name` |
| 158 | +/// relative to `config_home_dir` and each directory in `config_dirs`, giving |
| 159 | +/// preference to files found relative to an earlier directory in the search order. |
| 160 | +/// |
| 161 | +/// An application may choose to handle a file being located under multiple base |
| 162 | +/// directories however it sees fit, so long as it respects the search order. |
| 163 | +/// For example, it could say that only the first file found is used, or that |
| 164 | +/// data within the files is merged in some way. |
| 165 | +pub fn config_dirs() -> XdgDirsIter { |
| 166 | + XdgDirsIter::new("XDG_CONFIG_DIRS", "/etc/xdg") |
| 167 | +} |
0 commit comments