std/os/unix/fs/dirs.rs
1use crate::env::{self, SplitPathsRef};
2use crate::ffi::{OsStr, OsString};
3use crate::fs::{self, HomeDirs, MediaDirs};
4use crate::io::{self, ErrorKind, const_error};
5use crate::path::{Path, PathBuf};
6
7#[derive(Debug, Default, Clone)]
8#[non_exhaustive]
9pub struct ExtraHomeDirs {
10 runtime: Option<PathBuf>,
11 config_path: Option<OsString>,
12 data_path: Option<OsString>,
13}
14
15#[derive(Debug, Default, Clone)]
16#[non_exhaustive]
17pub struct ExtraMediaDirs {
18 templates: Option<PathBuf>,
19}
20
21/// XDG-specific extensions to [`fs::HomeDirs`](HomeDirs).
22///
23/// The XDG conventions are defined by the Freedesktop.org project in the
24/// [XDG Base Directory Specification][xdg-basedir]. These conventions have
25/// been largely adopted by Linux distributions.
26///
27/// The XDG conventions are written to be usable on any Unix-like filesystem,
28/// thus this extension being provided in `os::unix` rather than `os::linux`.
29/// However, while some tooling does use XDG conventions on macOS, note that
30/// macOS has its own separate conventions for user directories. Consider
31/// carefully what conventions your users will expect your application to
32/// follow along with any legacy path compatibility you might need to support.
33///
34/// [xdg-basedir]: https://specifications.freedesktop.org/basedir/
35#[unstable(feature = "fs_home_dirs", issue = "162082")]
36pub impl(self) trait HomeDirsExt: Sized {
37 /// Load the user directory paths according to the
38 /// [XDG Base Directory Specification][xdg-basedir].
39 ///
40 /// Each base directory path is set to the value of its corresponding
41 /// `XDG_*` environment variable (if it is set and non-empty), else to
42 /// the default value defined by the specification.
43 ///
44 /// | Field | Environment Variable | Default Value |
45 /// | ----- | -------------------- | ------------- |
46 /// | [`cache_home`] | `XDG_CACHE_HOME` | `$HOME/.cache` |
47 /// | [`config_home`] | `XDG_CONFIG_HOME` | `$HOME/.config` |
48 /// | [`data_home`] | `XDG_DATA_HOME` | `$HOME/.local/share` |
49 /// | [`state_home`] | `XDG_STATE_HOME` | `$HOME/.local/state` |
50 /// | [`runtime_home`] | `XDG_RUNTIME_DIR` | (see method docs) |
51 /// | [`config_dirs`] | `XDG_CONFIG_DIRS` | `/etc/xdg` |
52 /// | [`data_dirs`] | `XDG_DATA_DIRS` | `/usr/local/share/`, `/usr/share/` |
53 ///
54 /// Note that `$HOME` here means [`env::home_dir`], which uses
55 /// `$HOME` if set and non-empty, but falls back to the system password
56 /// database if it isn't set.
57 ///
58 /// All paths are required to be absolute. If a relative path is configured
59 /// by the environment, it is ignored and the default value is used instead.
60 ///
61 /// `config_dirs` and `data_dirs` are a list of delimited paths using the
62 /// [`env::split_paths`] delimiter. If some but not all paths in the list are
63 /// relative, those relative paths are ignored and the remaining absolute
64 /// paths are used. If there are no valid absolute paths, the default value
65 /// is used instead.
66 ///
67 /// # Errors
68 ///
69 /// Errors if the user's home directory cannot be determined.
70 ///
71 /// [xdg-basedir]: https://specifications.freedesktop.org/basedir/
72 /// [`cache_home`]: HomeDirs::cache_home
73 /// [`config_home`]: HomeDirs::config_home
74 /// [`data_home`]: HomeDirs::data_home
75 /// [`state_home`]: HomeDirs::state_home
76 /// [`runtime_home`]: HomeDirsExt::runtime_home
77 /// [`config_dirs`]: HomeDirsExt::config_dirs
78 /// [`data_dirs`]: HomeDirsExt::data_dirs
79 /// [`env::split_paths`]: crate::env::split_paths
80 #[unstable(feature = "fs_home_dirs", issue = "162082")]
81 fn xdg() -> io::Result<Self>;
82
83 /// A base directory relative to which user-specific runtime files
84 /// (such as sockets, named pipes, etc) should be stored.
85 ///
86 /// Files in this directory may be subjected to periodic clean-up.
87 /// Larger files should not be placed here, since it might reside in
88 /// runtime memory and cannot necessarily be swapped out to disk.
89 ///
90 /// This path does not have a default if not set. If it isn't set,
91 /// applications should fall back to a replacement directory with
92 /// similar capabilities and print a warning message.
93 #[unstable(feature = "fs_home_dirs", issue = "162082")]
94 fn runtime_home(&self) -> Option<&Path>;
95
96 /// A preference-ordered list of base directories to search for config
97 /// files *in addition to* [`config_home`].
98 ///
99 /// The order of directories denotes their importance; the first directory
100 /// is the most important. Information defined relative to the more
101 /// important base directory takes precedent. [`config_home`] is not
102 /// necessarily present in this list, and is considered more important
103 /// than any base directory in this list.
104 ///
105 /// [`config_home`]: HomeDirs::config_home
106 #[unstable(feature = "fs_home_dirs", issue = "162082")]
107 fn config_dirs(&self) -> Option<XdgDirs<'_>>;
108
109 /// A preference-ordered list of base directories to search for data
110 /// files *in addition to* [`data_home`].
111 ///
112 /// The order of directories denotes their importance; the first directory
113 /// is the most important. Information defined relative to the more
114 /// important base directory takes precedent. [`data_home`] is not
115 /// necessarily present in this list, and is considered more important
116 /// than any base directory in this list.
117 ///
118 /// [`data_home`]: HomeDirs::data_home
119 #[unstable(feature = "fs_home_dirs", issue = "162082")]
120 fn data_dirs(&self) -> Option<XdgDirs<'_>>;
121
122 /// Set the path for [Self::runtime_home].
123 ///
124 /// # Panics
125 ///
126 /// Panics if the provided path is not absolute.
127 #[unstable(feature = "fs_home_dirs", issue = "162082")]
128 fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self;
129
130 /// Set the paths for [Self::config_dirs].
131 ///
132 /// Takes one or more paths joined appropriately for the `PATH` environment
133 /// variable, as by [`env::join_paths`].
134 ///
135 /// # Panics
136 ///
137 /// Panics if any of the provided paths are not absolute.
138 #[unstable(feature = "fs_home_dirs", issue = "162082")]
139 fn set_config_dirs(&mut self, paths: OsString) -> &mut Self;
140
141 /// Set the paths for [Self::data_dirs].
142 ///
143 /// Takes one or more paths joined appropriately for the `PATH` environment
144 /// variable, as by [`env::join_paths`].
145 ///
146 /// # Panics
147 ///
148 /// Panics if any of the provided paths are not absolute.
149 #[unstable(feature = "fs_home_dirs", issue = "162082")]
150 fn set_data_dirs(&mut self, paths: OsString) -> &mut Self;
151}
152
153/// XDG-specific extensions to [`fs::MediaDirs`](MediaDirs).
154///
155/// The XDG conventions are defined by the Freedesktop.org project through the
156/// [xdg-user-dirs] tool. This configuration is generally present on desktop
157/// Linux distributions, although adoption is less widespread than the base
158/// directory specification.
159///
160/// The XDG conventions are written to be usable on any Unix-like filesystem,
161/// thus this extension being provided in `os::unix` rather than `os::linux`.
162/// However, while some tooling does use XDG conventions on macOS, note that
163/// macOS has its own separate conventions for user directories. Consider
164/// carefully what conventions your users will expect your application to
165/// follow along with any legacy path compatibility you might need to support.
166///
167/// [xdg-user-dirs]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
168#[unstable(feature = "fs_media_dirs", issue = "162083")]
169pub impl(self) trait MediaDirsExt: Sized {
170 /// Load the user directory paths according to the [xdg-user-dirs] tool.
171 ///
172 /// This directly reads and parses the `$XDG_CONFIG_HOME/user-dirs.dirs`
173 /// file as defined and maintained by the [xdg-user-dirs] tool.
174 ///
175 /// # Errors
176 ///
177 /// Errors if the user's home directory cannot be determined or if the
178 /// `$XDG_CONFIG_HOME/user-dirs.dirs` file cannot be read.
179 ///
180 /// # Implementation-specific behavior
181 ///
182 /// Only the format maintained by xdg-user-dirs-update is supported. Any
183 /// configuration that does not match the expected format will result in
184 /// loading an unspecified path or `None` for that directory. To be more
185 /// specific:
186 ///
187 /// - Any line not in the format of `XDG_{NAME}_DIR={path}` where `{NAME}`
188 /// is one of `DESKTOP`, `DOWNLOAD`, `TEMPLATES`, `PUBLICSHARE`,
189 /// `DOCUMENTS`, `MUSIC`, `PICTURES`, or `VIDEOS` is ignored.
190 /// - `{path}` must be a `"`-quoted shell-escaped path.
191 /// - `{path}` may only start with `/` or `$HOME/`. A home-relative path
192 /// is returned relative to [`env::home_dir`]; shell expansion is not
193 /// performed.
194 /// - A directory set to just `$HOME` marks it as removed, and results in
195 /// a `None` value for that path.
196 /// - If shell expansion syntax other than a leading `$HOME` is present,
197 /// the produced directory path is unspecified. This is invalid config
198 /// according to the xdg-user-dirs tooling.
199 ///
200 /// This behavior may change in the future. One example change that we
201 /// explicitly reserve the right to make is to load paths that we currently
202 /// ignore, such as path formats that are not canonically supported by
203 /// xdg-user-dirs but which may occur in manually-edited `user-dirs.dirs`.
204 ///
205 /// [xdg-user-dirs]: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
206 #[unstable(feature = "fs_media_dirs", issue = "162083")]
207 fn xdg() -> io::Result<Self>;
208
209 /// The OS-privileged user "Templates" directory, often the `Templates`
210 /// folder in the user's home directory.
211 ///
212 /// As a media directory, this should typically be used as a default path
213 /// for file selection dialogs, not for automatically accessed file paths.
214 #[unstable(feature = "fs_media_dirs", issue = "162083")]
215 fn templates(&self) -> Option<&Path>;
216
217 /// Set the paths for [Self::templates].
218 ///
219 /// # Panics
220 ///
221 /// Panics if the provided path is not absolute.
222 #[unstable(feature = "fs_media_dirs", issue = "162083")]
223 fn set_templates(&mut self, path: PathBuf) -> &mut Self;
224}
225
226#[unstable(feature = "fs_home_dirs", issue = "162082")]
227#[derive(Debug)]
228pub struct XdgDirs<'a>(SplitPathsRef<'a>);
229
230#[unstable(feature = "fs_home_dirs", issue = "162082")]
231impl HomeDirsExt for HomeDirs {
232 fn xdg() -> io::Result<Self> {
233 let mut dirs = HomeDirs::empty();
234 let user_home = xdg::user_home()?;
235
236 dirs.set_cache_home(xdg::dir_or_else(|| user_home.join(".cache"), "XDG_CACHE_HOME"));
237 dirs.set_config_home(xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME"));
238 dirs.set_data_home(xdg::dir_or_else(|| user_home.join(".local/share"), "XDG_DATA_HOME"));
239 dirs.set_state_home(xdg::dir_or_else(|| user_home.join(".local/state"), "XDG_STATE_HOME"));
240 if let Some(runtime) = xdg::dir("XDG_RUNTIME_DIR") {
241 dirs.set_runtime_home(runtime);
242 }
243
244 dirs.set_config_dirs(xdg::dirs_or("/etc/xdg", "XDG_CONFIG_DIRS"));
245 dirs.set_data_dirs(xdg::dirs_or("/usr/local/share/:/usr/share/", "XDG_DATA_DIRS"));
246
247 Ok(dirs)
248 }
249
250 fn runtime_home(&self) -> Option<&Path> {
251 self.extra.runtime.as_deref()
252 }
253
254 fn config_dirs(&self) -> Option<XdgDirs<'_>> {
255 self.extra.config_path.as_deref().map(XdgDirs::new)
256 }
257
258 fn data_dirs(&self) -> Option<XdgDirs<'_>> {
259 self.extra.data_path.as_deref().map(XdgDirs::new)
260 }
261
262 fn set_runtime_home(&mut self, path: PathBuf) -> &mut Self {
263 assert!(path.is_absolute(), "runtime directory path must be absolute");
264 self.extra.runtime = Some(path);
265 self
266 }
267
268 fn set_config_dirs(&mut self, paths: OsString) -> &mut Self {
269 for path in split_paths_ref(&paths) {
270 assert!(path.is_absolute(), "config directory paths must be absolute");
271 }
272 self.extra.config_path = Some(paths);
273 self
274 }
275
276 fn set_data_dirs(&mut self, paths: OsString) -> &mut Self {
277 for path in split_paths_ref(&paths) {
278 assert!(path.is_absolute(), "data directory paths must be absolute");
279 }
280 self.extra.data_path = Some(paths);
281 self
282 }
283}
284
285#[unstable(feature = "fs_media_dirs", issue = "162083")]
286impl MediaDirsExt for MediaDirs {
287 fn xdg() -> io::Result<Self> {
288 let user_home = xdg::user_home()?;
289 let config_home = xdg::dir_or_else(|| user_home.join(".config"), "XDG_CONFIG_HOME");
290
291 let spec = match fs::read(config_home.join("user-dirs.dirs")) {
292 Ok(spec) => spec,
293 Err(e) if e.kind() == ErrorKind::NotFound => {
294 return Err(const_error!(
295 ErrorKind::NotFound,
296 "missing `$XDG_CONFIG_HOME/user-dirs.dirs`",
297 ));
298 }
299 Err(e) => return Err(e),
300 };
301
302 Ok(xdg::parse_user_dirs(&spec, &user_home))
303 }
304
305 fn templates(&self) -> Option<&Path> {
306 self.extra.templates.as_deref()
307 }
308
309 fn set_templates(&mut self, path: PathBuf) -> &mut Self {
310 assert!(path.is_absolute(), "templates directory path must be absolute");
311 self.extra.templates = Some(path);
312 self
313 }
314}
315
316impl<'a> XdgDirs<'a> {
317 fn new(paths: &'a OsStr) -> Self {
318 XdgDirs(split_paths_ref(paths))
319 }
320}
321
322#[unstable(feature = "fs_home_dirs", issue = "162082")]
323impl<'a> Iterator for XdgDirs<'a> {
324 type Item = &'a Path;
325
326 fn next(&mut self) -> Option<Self::Item> {
327 self.0.next()
328 }
329}
330
331fn split_paths_ref<'a>(paths: &'a OsStr) -> SplitPathsRef<'a> {
332 // returns Some on cfg(unix)
333 env::split_paths_ref(paths).unwrap()
334}
335
336mod xdg {
337 use super::{MediaDirs, split_paths_ref};
338 use crate::env::{self, join_paths, var_os};
339 use crate::ffi::OsString;
340 use crate::io::{self, ErrorKind, const_error};
341 use crate::ops::Deref;
342 use crate::os::unix::ffi::{OsStrExt, OsStringExt};
343 use crate::path::{Path, PathBuf};
344
345 pub fn user_home() -> io::Result<PathBuf> {
346 env::home_dir()
347 .filter(|p| p.is_absolute())
348 .ok_or(const_error!(ErrorKind::InvalidData, "user home directory path not absolute"))
349 }
350
351 pub fn dir(env: &str) -> Option<PathBuf> {
352 var_os(env).filter(|s| !s.is_empty()).map(PathBuf::from).filter(|path| path.is_absolute())
353 }
354
355 pub fn dir_or_else(fallback: impl FnOnce() -> PathBuf, env: &str) -> PathBuf {
356 dir(env).unwrap_or_else(fallback)
357 }
358
359 fn dirs(env: &str) -> Option<OsString> {
360 let dirs = var_os(env).filter(|s| !s.is_empty())?;
361 if split_paths_ref(&dirs).all(|p| p.is_absolute()) {
362 return Some(dirs);
363 }
364
365 struct DerefAsRef<T>(pub T);
366 impl<T: Deref<Target: AsRef<U>>, U: ?Sized> AsRef<U> for DerefAsRef<T> {
367 fn as_ref(&self) -> &U {
368 (*self.0).as_ref()
369 }
370 }
371
372 let paths = split_paths_ref(&dirs).filter(|p| p.is_absolute()).map(DerefAsRef);
373 join_paths(paths).ok().filter(|s| !s.is_empty())
374 }
375
376 pub fn dirs_or(fallback: impl Into<OsString>, env: &str) -> OsString {
377 dirs(env).unwrap_or_else(|| fallback.into())
378 }
379
380 pub fn parse_user_dirs<'a>(spec: &'a [u8], user_home: &Path) -> MediaDirs {
381 let mut dirs = MediaDirs::empty();
382
383 for (xdg, path) in
384 spec.split(|&b| b == b'\n').flat_map(|line| parse_user_dirs_line(line, user_home))
385 {
386 // load the known user directories
387 match xdg {
388 b"XDG_DESKTOP_DIR" => dirs.desktop = path,
389 b"XDG_DOCUMENTS_DIR" => dirs.documents = path,
390 b"XDG_DOWNLOAD_DIR" => dirs.downloads = path,
391 b"XDG_MUSIC_DIR" => dirs.music = path,
392 b"XDG_PICTURES_DIR" => dirs.pictures = path,
393 b"XDG_VIDEOS_DIR" => dirs.videos = path,
394 b"XDG_TEMPLATES_DIR" => dirs.extra.templates = path,
395 b"XDG_PUBLICSHARE_DIR" => {
396 // we don't expose this directory yet as Windows also has a "public" directory
397 // and it's not yet clear if we want to expose this at the target-agnostic level
398 }
399 _ => {
400 // ignore unknown variable assignment
401 }
402 }
403 }
404
405 dirs
406 }
407
408 fn parse_user_dirs_line<'a>(
409 line: &'a [u8],
410 user_home: &Path,
411 ) -> Option<(&'a [u8], Option<PathBuf>)> {
412 // trim whitespace
413 let trimmed = line.trim_ascii();
414 // skip empty lines and comments
415 if trimmed.is_empty() || trimmed.starts_with(&[b'#']) {
416 return None;
417 }
418
419 // only variable assignment lines are allowed; split on `=`
420 let mut split = trimmed.splitn(2, |&b| b == b'=');
421 // extract assignment parts; ignore lines not in this format
422 let var = split.next()?;
423 let val = split.next()?;
424 debug_assert_eq!(split.next(), None);
425
426 // the path value is quoted; unquote it
427 let path = unquote(val, user_home)?;
428
429 let path = Some(path)
430 // ignore non-absolute paths
431 .filter(|path| path.is_absolute())
432 // setting to the home dir disables the directory configuration
433 .filter(|path| path != user_home);
434
435 Some((var, path))
436 }
437
438 fn unquote(bytes: &[u8], user_home: &Path) -> Option<PathBuf> {
439 let [b'"', rest @ .., b'"'] = bytes else { return None };
440
441 // setting to the home dir disables the directory configuration;
442 // if done symbolically, return None here and bypass later work
443 if matches!(rest, b"$HOME" | b"$HOME/") {
444 return None;
445 }
446
447 let mut rest = rest;
448 let mut s = Vec::with_capacity(rest.len());
449
450 // expand leading $HOME
451 if rest.starts_with(b"$HOME/") {
452 s.extend_from_slice(user_home.as_os_str().as_bytes());
453 if !user_home.has_trailing_sep() {
454 s.push(b'/');
455 }
456 rest = &rest[6..];
457 }
458
459 loop {
460 let i = rest
461 .iter()
462 .position(|&b| matches!(b, b'"' | b'\\' | b'$' | b'`'))
463 .unwrap_or(rest.len());
464 s.extend_from_slice(&rest[..i]);
465 match &rest[i..] {
466 [] => break,
467 [b'\\', c @ (b'"' | b'\\' | b'$' | b'`'), tail @ ..] => {
468 // supported escapes
469 s.push(*c);
470 rest = tail;
471 }
472 [b'"' | b'\\' | b'$' | b'`', ..] => {
473 // unsupported shell syntax
474 return None;
475 }
476 _ => {
477 if cfg!(debug_assertions) {
478 unreachable!()
479 } else {
480 return None;
481 }
482 }
483 }
484 }
485
486 Some(PathBuf::from(OsString::from_vec(s)))
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493
494 #[test]
495 fn can_fetch_xdg_base_dirs() {
496 let dirs = HomeDirs::xdg().unwrap();
497
498 assert!(dirs.cache_home().is_some());
499 assert!(dirs.config_home().is_some());
500 assert!(dirs.data_home().is_some());
501 assert!(dirs.state_home().is_some());
502 // dirs.runtime() may not exist
503 assert!(dirs.config_dirs().is_some());
504 assert!(dirs.data_dirs().is_some());
505 }
506
507 #[test]
508 fn can_fetch_xdg_media_dirs() {
509 let dirs = match MediaDirs::xdg() {
510 Ok(dirs) => dirs,
511 Err(e) if e.kind() == ErrorKind::NotFound => {
512 // xdg-user-dirs not initialized on this system, skip the test
513 return;
514 }
515 Err(e) => panic!("failed to fetch xdg user dirs: {e:?}"),
516 };
517
518 // even when user-dirs.dirs is present, directories may be unset,
519 // so we can't assert anything about the dir paths here
520 let _ = dirs;
521 }
522
523 #[test]
524 fn test_well_user_dirs_parsing() {
525 const TEST_USERDIRS: &'static str = r#"
526# This file is written by xdg-user-dirs-update
527# If you want to change or add directories, just edit the line you're
528# interested in. All local changes will be retained on the next run.
529# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
530# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
531# absolute path. No other format is supported.
532#
533XDG_DESKTOP_DIR="$HOME/Desktop"
534XDG_DOWNLOAD_DIR="$HOME/Downloads"
535XDG_TEMPLATES_DIR="/pub/Templates"
536XDG_PUBLICSHARE_DIR="/pub"
537XDG_DOCUMENTS_DIR="$HOME/Documents"
538XDG_MUSIC_DIR="$HOME/My \"Music\""
539XDG_PICTURES_DIR="$HOME/Pictures"
540XDG_VIDEOS_DIR="$HOME"
541"#;
542
543 let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris"));
544
545 assert_eq!(dirs.desktop(), Some("/home/ferris/Desktop".as_ref()));
546 assert_eq!(dirs.downloads(), Some("/home/ferris/Downloads".as_ref()));
547 assert_eq!(dirs.templates(), Some("/pub/Templates".as_ref()));
548 assert_eq!(dirs.documents(), Some("/home/ferris/Documents".as_ref()));
549 assert_eq!(dirs.music(), Some("/home/ferris/My \"Music\"".as_ref()));
550 assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures".as_ref()));
551 assert_eq!(dirs.videos(), None);
552 }
553
554 #[test]
555 fn test_ill_user_dirs_parsing() {
556 const TEST_USERDIRS: &'static str = r#"
557# unquoted
558XDG_DESKTOP_DIR=$HOME/Desktop
559# mixed quoting
560XDG_DOWNLOAD_DIR="$HOME"/"Downloads"
561# non-HOME env
562XDG_TEMPLATES_DIR="/home/$USER/Templates"
563# missing PUBLICSHARE
564# command substitution
565XDG_DOCUMENTS_DIR="`pwd`/Documents"
566# non-symbolic homedir disabled
567XDG_MUSIC_DIR="/home/ferris"
568# set multiple times
569XDG_PICTURES_DIR="$HOME/Pictures1"
570XDG_PICTURES_DIR="$HOME/Pictures2"
571# relative path
572XDG_VIDEOS_DIR="Videos"
573"#;
574
575 let dirs = xdg::parse_user_dirs(TEST_USERDIRS.as_bytes(), Path::new("/home/ferris"));
576
577 assert_eq!(dirs.desktop(), None);
578 assert_eq!(dirs.downloads(), None);
579 assert_eq!(dirs.templates(), None);
580 assert_eq!(dirs.documents(), None);
581 assert_eq!(dirs.music(), None);
582 assert_eq!(dirs.pictures(), Some("/home/ferris/Pictures2".as_ref()));
583 assert_eq!(dirs.videos(), None);
584 }
585}