utils/
directories.rs

1//! Contains methods for working with directories that have special meanings to the software.
2//!
3//! This module attempts to follow the conventions for each platform's guidelines, and as a result
4//! the paths generated for each platform may differ.
5//!
6//! Inspiration of this library was taken from [directories](https://crates.io/crates/directories).
7//! The library wasn't used due to licensing issues.
8//!
9//! Below are the conventions followed for each platform:
10//!
11//! - Windows: [Known Folder](https://msdn.microsoft.com/en-us/library/windows/desktop/dd378457.aspx) API
12//! - Linux: [XDG Base Directory](https://specifications.freedesktop.org/basedir-spec/latest/) specifications.
13//! - macOS: [Standard Directories](https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html#//apple_ref/doc/uid/TP40010672-CH2-SW6).
14
15use std::path::PathBuf;
16use thiserror::Error;
17
18/// Contains the errors that can occur when attempting to retrieve one of the known directories.
19#[non_exhaustive]
20#[derive(Error, Debug)]
21pub enum DirectoryError {
22    /// The path could not be retrieved because it (or one if it's descendants) does not exist.
23    #[error("Could not find the requested {0} directory on the system")]
24    NotFound(&'static str),
25}
26
27/// Attempts to retrieve the current platform's configuration directory.
28///
29/// # Errors
30///
31/// The underlying reason for this method failing depends on the platform, however it always boils down
32/// to the base directory (`$HOME`, `%APPDATA%`, ...) not being found.
33pub fn config_path() -> Result<PathBuf, DirectoryError> {
34    #[cfg(target_os = "macos")]
35    return config_path_macos();
36
37    #[cfg(target_os = "linux")]
38    return config_path_linux();
39
40    #[cfg(target_os = "windows")]
41    return config_path_windows();
42}
43
44/// Attempts to retrieve the current platform's cache directory.
45///
46/// # Errors
47///
48/// The underlying reason for this method failing depends on the platform, however it always boils down
49/// to the base directory (`$HOME`, `%APPDATA%`, ...) not being found.
50pub fn cache_path() -> Result<PathBuf, DirectoryError> {
51    #[cfg(target_os = "macos")]
52    return cache_path_macos();
53
54    #[cfg(target_os = "linux")]
55    return cache_path_linux();
56
57    #[cfg(target_os = "windows")]
58    return cache_path_windows();
59}
60
61#[inline]
62#[cfg(target_os = "macos")]
63#[allow(clippy::missing_docs_in_private_items, clippy::missing_errors_doc)]
64fn config_path_macos() -> Result<PathBuf, DirectoryError> {
65    let home = std::env::home_dir().ok_or(DirectoryError::NotFound("home"))?;
66
67    Ok(home.join("Library/Application Support/DungeonRS/config"))
68}
69
70#[inline]
71#[cfg(target_os = "linux")]
72#[allow(clippy::missing_docs_in_private_items, clippy::missing_errors_doc)]
73fn config_path_linux() -> Result<PathBuf, DirectoryError> {
74    let path = std::env::var("XDG_CONFIG_HOME")
75        .map(|home| PathBuf::from(home).join("DungeonRS"))
76        .map_err(|_| {
77            std::env::var("HOME").map(|home| PathBuf::from(home).join(".config/DungeonRS"))
78        })
79        .map_err(|_| DirectoryError::NotFound("XDG_CONFIG_HOME"))?;
80
81    Ok(path)
82}
83
84#[inline]
85#[cfg(target_os = "windows")]
86#[allow(clippy::missing_docs_in_private_items, clippy::missing_errors_doc)]
87fn config_path_windows() -> Result<PathBuf, DirectoryError> {
88    let home = known_folders::get_known_folder_path(known_folders::KnownFolder::RoamingAppData)
89        .ok_or_else(|| DirectoryError::NotFound("RoamingAppData"))?;
90
91    Ok(home.join("DungeonRS/config"))
92}
93
94#[inline]
95#[cfg(target_os = "macos")]
96#[allow(clippy::missing_docs_in_private_items, clippy::missing_errors_doc)]
97fn cache_path_macos() -> Result<PathBuf, DirectoryError> {
98    let home = std::env::home_dir().ok_or(DirectoryError::NotFound("home"))?;
99
100    Ok(home.join("Library/Cache/DungeonRS"))
101}
102
103#[inline]
104#[cfg(target_os = "linux")]
105#[allow(clippy::missing_docs_in_private_items, clippy::missing_errors_doc)]
106fn cache_path_linux() -> Result<PathBuf, DirectoryError> {
107    let path = std::env::var("XDG_CACHE_HOME")
108        .map(|home| PathBuf::from(home).join("DungeonRS"))
109        .map_err(|_| std::env::var("HOME").map(|home| PathBuf::from(home).join(".cache/DungeonRS")))
110        .map_err(|_| DirectoryError::NotFound("XDG_CACHE_HOME"))?;
111
112    Ok(path)
113}
114
115#[inline]
116#[cfg(target_os = "windows")]
117#[allow(clippy::missing_docs_in_private_items, clippy::missing_errors_doc)]
118fn cache_path_windows() -> Result<PathBuf, DirectoryError> {
119    let home = known_folders::get_known_folder_path(known_folders::KnownFolder::LocalAppData)
120        .ok_or_else(|| DirectoryError::NotFound("LocalAppData"))?;
121
122    Ok(home.join("DungeonRS/cache"))
123}