-
-
Notifications
You must be signed in to change notification settings - Fork 15k
Add xdg_basedir API #157518
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add xdg_basedir API #157518
Changes from all commits
073e649
9534d0f
2b3d273
7b46306
593a847
6e670de
378da80
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| //! XDG (X Desktop Group) related functionality for Unix platforms. | ||
| //! | ||
| //! The [XDG Base Directory Specification][basedir] defines a set of base | ||
| //! directories, relative to which user-specific files should be looked for. The | ||
| //! functions in this module provide those directory paths as configured by | ||
| //! the environment. | ||
| //! | ||
| //! Note that the use of these functions is not enforced by the system, and as | ||
| //! such, not all programs will necessarily respect all details of the XDG path | ||
| //! environment. This is a set of guidelines, and each program is ultimately | ||
| //! responsible for defining where and how it both reads and writes files. | ||
| //! | ||
| //! Use of XDG paths can be generally considered the conventional expectation | ||
| //! on Linux-based systems. Other Unix-based systems may or may not play well | ||
| //! with the XDG conventions. | ||
| //! | ||
| //! Directories returned by this module are not guaranteed to exist yet. If the | ||
| //! directory does not exist, an application should attempt to create it with | ||
| //! [permissions mode][super::fs::PermissionsExt::from_mode] `0o700`. | ||
| //! | ||
| //! [basedir]: https://specifications.freedesktop.org/basedir/latest/ | ||
| #![unstable(feature = "xdg_basedir", issue = "157515")] | ||
|
|
||
| use crate::env::{home_dir, split_paths, var_os}; | ||
| use crate::ffi::{OsStr, OsString}; | ||
| use crate::path::PathBuf; | ||
|
|
||
| fn xdg_home_dir() -> PathBuf { | ||
| // Note: home_dir can return `Some("")` in some cases. We assume that in | ||
|
aapoalas marked this conversation as resolved.
|
||
| // this case the expected behavior is for `$HOME/path` to become `/path`, | ||
| // i.e. the home directory is effectively `/`. | ||
| match home_dir() { | ||
|
aapoalas marked this conversation as resolved.
|
||
| None => panic!("an XDG environment should have a home directory"), | ||
| Some(home) if home.is_empty() => PathBuf::from("/"), | ||
| Some(home) => home, | ||
| } | ||
| } | ||
|
|
||
| fn xdg_dir(env: &str, fallback_home_subdir: &str) -> PathBuf { | ||
| var_os(env) | ||
| .filter(|s| !s.is_empty()) | ||
| .map(PathBuf::from) | ||
| .unwrap_or_else(|| xdg_home_dir().join(fallback_home_subdir)) | ||
| } | ||
|
|
||
| /// A base directory relative to which user-specific data files should be written. | ||
| /// | ||
| /// An application `appid` would typically be expected to write its data files | ||
| /// to `{data_home_dir}/{appid}/**/*`. | ||
| pub fn data_home_dir() -> PathBuf { | ||
| xdg_dir("XDG_DATA_HOME", ".local/share") | ||
| } | ||
|
|
||
| /// A base directory relative to which user-specific configuration files should be written. | ||
| /// | ||
| /// An application `appid` would typically be expected to write its configuration | ||
| /// files to `{config_home_dir}/{appid}/**/*`. | ||
| pub fn config_home_dir() -> PathBuf { | ||
| xdg_dir("XDG_CONFIG_HOME", ".config") | ||
| } | ||
|
|
||
| /// A base directory relative to which user-specific state data should be written. | ||
| /// | ||
| /// An application `appid` would typically be expected to write its state data to | ||
| /// `{state_home_dir}/{appid}/**/*`. | ||
| /// | ||
| /// Common kinds of state data include actions history (such as logs, history, | ||
|
CAD97 marked this conversation as resolved.
|
||
| /// recently used files, etc.) and state of the application that can be reused | ||
| /// after application restart (such as view, layout, open files, undo history, | ||
| /// etc.). | ||
| pub fn state_home_dir() -> PathBuf { | ||
| xdg_dir("XDG_STATE_HOME", ".local/state") | ||
| } | ||
|
|
||
| /// A base directory relative to which user-specific non-essential (cached) data should be written. | ||
| /// | ||
| /// An application `appid` would typically be expected to write its cache data to | ||
| /// `{cache_home_dir}/{appid}/**/*`. | ||
| pub fn cache_home_dir() -> PathBuf { | ||
| xdg_dir("XDG_CACHE_HOME", ".cache") | ||
| } | ||
|
|
||
| /// An iterator that produces directory paths from XDG environment configuration. | ||
| /// | ||
| /// The iterator element type is [`PathBuf`]. | ||
| /// | ||
| /// This structure is created by [`xdg::data_dirs`] and [`xdg::config_dirs`]. | ||
| /// See the documentation of those functions for more. | ||
| /// | ||
| /// [`xdg::data_dirs`]: data_dirs | ||
| /// [`xdg::config_dirs`]: config_dirs | ||
| #[derive(Debug, Clone)] | ||
| pub struct XdgDirsIter { | ||
| list: OsString, | ||
| off: usize, | ||
| } | ||
|
|
||
| impl XdgDirsIter { | ||
| fn new(env: &str, default: &str) -> Self { | ||
| let dirs = var_os(env).filter(|s| !s.is_empty()).unwrap_or_else(|| default.into()); | ||
| Self { list: dirs, off: 0 } | ||
| } | ||
|
|
||
| fn remaining(&self) -> Option<&OsStr> { | ||
|
CAD97 marked this conversation as resolved.
|
||
| self.list.as_encoded_bytes().get(self.off..).map(|bytes| { | ||
| // SAFETY: `self.off` is the index after a path separator (or the | ||
| // start of the string), so is a valid OsStr boundary. | ||
| unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| impl Iterator for XdgDirsIter { | ||
| type Item = PathBuf; | ||
|
|
||
| fn next(&mut self) -> Option<Self::Item> { | ||
| let rest = self.remaining()?; | ||
| let next = split_paths(rest).next()?; | ||
| let len = next.as_os_str().len(); | ||
| self.off += len + 1; // Offset after this path and the separator after it. | ||
|
aapoalas marked this conversation as resolved.
|
||
| Some(next) | ||
| } | ||
|
|
||
| fn size_hint(&self) -> (usize, Option<usize>) { | ||
| let Some(dirs) = self.remaining() else { return (0, Some(0)) }; | ||
| split_paths(dirs).size_hint() | ||
| } | ||
| } | ||
|
|
||
| /// A set of preference ordered directories relative to which data files should be searched. | ||
| /// | ||
| /// If an application defines a data file to be at `$XDG_DATA_DIRS/appid/file.name`, this means that: | ||
| /// | ||
| /// - The initial data file should be installed to `{system_data_dir}/appid/file.name`. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
AIUI, It feels like a cop-out to provide the XDG paths API but still leave an undefined template variable here, but I don't know how we could do better. This specifically is a concern of the installer, not of the installed program, as a properly configured system will ensure that this path is in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this suggest that there should be an API to create an installer datadir path, given a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The current API surface is deliberately not giving app-specific paths, only the base directories that the app-specific folders go in. So such an API would just spit Another way to put the difference is that only the program that creates installable packages or whatever delivery mechanism needs to understand the conventions for installation, whereas every program that isn't just a single stateless executable needs to understand how to access auxiliary files. |
||
| /// - A user-specific version of the data file may be created at | ||
| /// <code>{[data_home_dir][]()}/appid/file.name</code>. | ||
| /// - Lookups for the data file should search for `./appid/file.name` relative to | ||
| /// `data_home_dir` and each directory in `data_dirs`, giving preference to | ||
| /// files found relative to an earlier directory in the search order. | ||
| /// | ||
| /// An application may choose to handle a file being located under multiple base | ||
| /// directories however it sees fit, so long as it respects the search order. | ||
| /// For example, it could say that only the first file found is used, or that | ||
| /// data within the files is merged in some way. | ||
| pub fn data_dirs() -> XdgDirsIter { | ||
| // NB: the spec uses trailing slashes only for this default, for some reason | ||
| XdgDirsIter::new("XDG_DATA_DIRS", "/usr/local/share/:/usr/share/") | ||
| } | ||
|
|
||
| /// A set of preference ordered directories relative to which configuration files should be searched. | ||
| /// | ||
| /// If an application defines a configuration file to be at `$XDG_CONFIG_DIRS/appid/file.name`, this means that: | ||
| /// | ||
| /// - The initial configuration file should be installed to `{system_config_dir}/xdg/appid/file.name`. | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
AIUI, It feels like a cop-out to provide the XDG paths API but still leave an undefined template variable here, but I don't know how we could do better. This specifically is a concern of the installer, not of the installed program, as a properly configured system will ensure that this path is in |
||
| /// - A user-specific version of the configuration file may be created at | ||
| /// <code>{[config_home_dir][]()}/appid/file.name</code>. | ||
| /// - Lookups for the configuration file should search for `./appid/file.name` | ||
| /// relative to `config_home_dir` and each directory in `config_dirs`, giving | ||
| /// preference to files found relative to an earlier directory in the search order. | ||
| /// | ||
| /// An application may choose to handle a file being located under multiple base | ||
| /// directories however it sees fit, so long as it respects the search order. | ||
| /// For example, it could say that only the first file found is used, or that | ||
| /// data within the files is merged in some way. | ||
| pub fn config_dirs() -> XdgDirsIter { | ||
| XdgDirsIter::new("XDG_CONFIG_DIRS", "/etc/xdg") | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.