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}