veloren_common_base/
userdata_dir.rs

1use std::path::PathBuf;
2use tracing::warn;
3
4const VELOREN_USERDATA_ENV: &str = "VELOREN_USERDATA";
5
6// TODO: consider expanding this to a general install strategy variable that is
7// also used for finding assets
8// TODO: Ensure there are no NUL (\0) characters in userdata_dir (possible on
9// MacOS but not Windows or Linux) as SQLite requires the database path does not
10// include this character.
11/// Determines common user data directory used by veloren frontends.
12///
13/// The first specified in this list is used.
14///   1. The VELOREN_USERDATA environment variable
15///   2. The VELOREN_USERDATA_STRATEGY environment variable
16///   3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata
17///      depending on if a workspace if being used
18///
19/// ### `VELOREN_USERDATA_STRATEGY` environment variable
20///
21/// Read during compilation and useful to set when compiling for distribution.
22///
23/// Available options are:
24/// * "system" => system specific project data directory
25/// * "executable" => <executable dir>/userdata
26///
27/// > **Note:** _case insensitive_
28pub fn userdata_dir(workspace: bool, strategy: Option<&str>, manifest_dir: &str) -> PathBuf {
29    // 1. The VELOREN_USERDATA environment variable
30    std::env::var_os(VELOREN_USERDATA_ENV)
31        .map(PathBuf::from)
32        // 2. The VELOREN_USERDATA_STRATEGY environment variable
33        .or_else(|| match strategy {
34            // "system" => system specific project data directory
35            Some(s) if s.eq_ignore_ascii_case("system") => Some(directories_next::ProjectDirs::from("net", "veloren", "veloren")
36                .expect("System's $HOME directory path not found!")
37                .data_dir()
38                .join("userdata")
39            ),
40            // "executable" => <executable dir>/userdata
41            Some(s) if s.eq_ignore_ascii_case("executable") => {
42                let mut path = std::env::current_exe()
43                    .expect("Failed to retrieve executable path!");
44                path.pop();
45                path.push("userdata");
46                Some(path)
47            },
48            Some(s) => {
49                warn!(
50                    "Compiled with an invalid VELOREN_USERDATA_STRATEGY: \"{}\". \
51                    Valid values are unset, \"system\", and \"executable\". \
52                    Falling back to unset case.",
53                    s,
54                );
55                None
56            },
57            _ => None,
58        })
59        // 3. The CARGO_MANIFEST_DIR/userdata or CARGO_MANIFEST_DIR/../userdata depending on if a
60        //    workspace if being used
61        .unwrap_or_else(|| {
62            let mut path = PathBuf::from(manifest_dir);
63            if workspace {
64                path.pop();
65            }
66            let exe_path = std::env::current_exe()
67                .expect("Failed to retrieve executable path!");
68            // If this path exists
69            // and the binary path is prefixed by this path
70            // put the userdata folder there
71            if path.exists() && exe_path.starts_with(&path) {
72                path.push("userdata");
73                path
74            } else {
75                // otherwise warn and fallback to the executable strategy
76                let project_path = path;
77                let mut path = exe_path;
78                path.pop();
79                path.push("userdata");
80                warn!(
81                    "This binary is outside the project folder where it was compiled ({}) \
82                    and was not compiled with VELOREN_USERDATA_STRATEGY set to \"system\" or \"executable\". \
83                    Falling back the to the \"executable\" strategy (the userdata folder will be placed in the \
84                    same folder as the executable: {}) \n\
85                    NOTE: You can manually select a userdata folder (overriding this automatic selection) by \
86                    setting the environment variable {} to the desired directory before running. \n\
87                    NOTE: If you have not moved the executable this can occur when using a custom cargo \
88                    target-dir that is not inside the project folder.",
89                    project_path.display(),
90                    path.display(),
91                    VELOREN_USERDATA_ENV,
92                );
93                path
94            }
95        })
96}
97
98#[macro_export]
99macro_rules! userdata_dir_workspace {
100    () => {
101        $crate::userdata_dir::userdata_dir(
102            true,
103            option_env!("VELOREN_USERDATA_STRATEGY"),
104            env!("CARGO_MANIFEST_DIR"),
105        )
106    };
107}
108
109#[macro_export]
110macro_rules! userdata_dir_no_workspace {
111    () => {
112        $crate::userdata_dir::userdata_dir(
113            false,
114            option_env!("VELOREN_USERDATA_STRATEGY"),
115            env!("CARGO_MANIFEST_DIR"),
116        )
117    };
118}