The Battle for Wesnoth  1.19.0-dev
paths.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2024
3  by Iris Morelle <shadowm2006@gmail.com>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "desktop/paths.hpp"
19 
20 #include "game_config.hpp"
21 #include "filesystem.hpp"
22 #include "gettext.hpp"
23 #include "log.hpp"
24 #include "preferences/general.hpp"
26 #include "utils/general.hpp"
27 
28 #if !defined(_WIN32) && !defined(__APPLE__)
29 #include <boost/filesystem.hpp>
30 #endif
31 
32 #ifndef _WIN32
33 
34 // For username stuff on Unix:
35 #include <pwd.h>
36 #include <sys/types.h>
37 
38 #else // _WIN32
39 
40 #ifndef UNICODE
41 #define UNICODE
42 #endif
43 
44 #define WIN32_LEAN_AND_MEAN
45 
46 #include <windows.h>
47 #include <shlobj.h>
48 
49 #endif
50 
51 static lg::log_domain log_desktop("desktop");
52 #define ERR_DU LOG_STREAM(err, log_desktop)
53 #define LOG_DU LOG_STREAM(info, log_desktop)
54 #define DBG_DU LOG_STREAM(debug, log_desktop)
55 
56 namespace desktop
57 {
58 
59 namespace
60 {
61 
62 void enumerate_storage_devices(std::vector<path_info>& res)
63 {
64 #ifdef _WIN32
65 
66  const DWORD drive_table = GetLogicalDrives();
67 
68  for(unsigned n = 0; n < 26; ++n) {
69  if((drive_table >> n) & 1) {
70  std::string u8drive = "A:";
71  u8drive[0] += n;
72 
73  LOG_DU << "enumerate_win32_drives(): " << u8drive << " is reported to be present";
74 
75  wchar_t drive[] = L"A:\\";
76  drive[0] += n;
77 
78  const DWORD label_bufsize = MAX_PATH + 1;
79  wchar_t label[label_bufsize] { 0 };
80 
81  if(GetVolumeInformation(drive, label, label_bufsize, nullptr, nullptr, nullptr, nullptr, 0) == 0) {
82  // Probably an empty removable drive, just ignore it and carry on.
83  const DWORD err = GetLastError();
84  LOG_DU << "enumerate_win32_drives(): GetVolumeInformation() failed (" << err << ")";
85  continue;
86  }
87 
88  // Trailing slash so that we don't get compatibility per-drive working dirs
89  // involved in path resolution.
90  res.push_back({u8drive, unicode_cast<std::string>(std::wstring{label}), u8drive + '\\'});
91  }
92  }
93 
94 #elif defined(__APPLE__)
95 
96  // Probably as unreliable as /media|/mnt on other platforms, not worth
97  // examining in detail.
98  res.push_back({{ N_("filesystem_path_system^Volumes"), GETTEXT_DOMAIN }, "", "/Volumes"});
99 
100 #else
101 
102  namespace bsys = boost::system;
103  namespace bfs = boost::filesystem;
104 
105  // These are either used as mount points themselves, or host mount points. The
106  // reasoning here is that if any or all of them are non-empty, they are
107  // probably used for _something_ that might be of interest to the user (if not
108  // directly and actively controlled by the user themselves).
109  // Note that the first candidate is actually /run/media/USERNAME -- we need
110  // to fetch the username at runtime from passwd to complete the path.
111  std::vector<std::string> candidates { "/media", "/mnt" };
112 
113  // Fetch passwd entry for the effective user the current process runs as
114  if(const passwd* pw = getpwuid(geteuid()); pw && pw->pw_name && pw->pw_name[0]) {
115  candidates.emplace(candidates.begin(), "/run/media/");
116  candidates.front() += pw->pw_name;
117  }
118 
119  for(const auto& mnt : candidates) {
120  bsys::error_code e;
121  try {
122  if(bfs::is_directory(mnt, e) && !bfs::is_empty(mnt, e) && !e) {
123  DBG_DU << "enumerate_mount_parents(): " << mnt << " appears to be a non-empty dir";
124  res.push_back({mnt, "", mnt});
125  }
126  }
127  catch(...) {
128  //bool is_empty(const path& p, system::error_code& ec) might throw.
129  //For example if you have no permission on that directory. Don't list the file in that case.
130  DBG_DU << "caught exception " << utils::get_unknown_exception_type() << " in enumerate_storage_devices";
131  }
132  }
133 
134 #endif
135 }
136 
137 bool have_path(const std::vector<path_info>& pathset, const std::string& path)
138 {
139  for(const auto& pinfo : pathset) {
140  if(pinfo.path == path) {
141  return true;
142  }
143  }
144 
145  return false;
146 }
147 
148 inline std::string pretty_path(const std::string& path)
149 {
150  return filesystem::normalize_path(path, true, true);
151 }
152 
153 inline config get_bookmarks_config()
154 {
155  auto cfg = preferences::get_child("dir_bookmarks");
156  return cfg ? *cfg : config{};
157 }
158 
159 inline void commit_bookmarks_config(config& cfg)
160 {
161  preferences::set_child("dir_bookmarks", cfg);
162 }
163 
164 } // unnamed namespace
165 
166 std::string user_profile_dir()
167 {
168 #ifndef _WIN32
169 
170  // TODO: The filesystem API uses $HOME for this purpose, which may be
171  // overridden or missing. Not sure which one really makes more sense
172  // for us here.
173  const passwd* const pwd = getpwuid(geteuid());
174 
175  if(!pwd || !pwd->pw_dir || !*pwd->pw_dir) {
176  return "";
177  }
178 
179  return pwd->pw_dir;
180 
181 #else // _WIN32
182 
183  wchar_t profile_path[MAX_PATH];
184  HRESULT res = SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, SHGFP_TYPE_CURRENT, profile_path);
185  return res != S_OK ? "" : unicode_cast<std::string>(std::wstring{profile_path});
186 
187 #endif // _WIN32
188 }
189 
190 std::string path_info::display_name() const
191 {
192  return label.empty() ? name : label + " (" + name + ")";
193 }
194 
195 std::ostream& operator<<(std::ostream& os, const path_info& pinf)
196 {
197  return os << pinf.name << " [" << pinf.label << "] - " << pinf.path;
198 }
199 
200 std::vector<path_info> game_paths(std::set<GAME_PATH_TYPES> paths)
201 {
202  static const std::string& game_bin_dir = pretty_path(filesystem::get_exe_dir());
203  static const std::string& game_data_dir = pretty_path(game_config::path);
204  static const std::string& game_user_data_dir = pretty_path(filesystem::get_user_data_dir());
205  static const std::string& game_user_pref_dir = pretty_path(filesystem::get_user_config_dir());
206  static const std::string& game_editor_map_dir = pretty_path(filesystem::get_legacy_editor_dir() + "/maps");
207 
208  std::vector<path_info> res;
209 
210  if(paths.count(GAME_BIN_DIR) > 0 && !have_path(res, game_bin_dir)) {
211  res.push_back({{ N_("filesystem_path_game^Game executables"), GETTEXT_DOMAIN }, "", game_bin_dir});
212  }
213 
214  if(paths.count(GAME_CORE_DATA_DIR) > 0 && !have_path(res, game_data_dir)) {
215  res.push_back({{ N_("filesystem_path_game^Game data"), GETTEXT_DOMAIN }, "", game_data_dir});
216  }
217 
218  if(paths.count(GAME_USER_DATA_DIR) > 0 && !have_path(res, game_user_data_dir)) {
219  res.push_back({{ N_("filesystem_path_game^User data"), GETTEXT_DOMAIN }, "", game_user_data_dir});
220  }
221 
222  if(paths.count(GAME_USER_PREFS_DIR) > 0 && !have_path(res, game_user_pref_dir)) {
223  res.push_back({{ N_("filesystem_path_game^User preferences"), GETTEXT_DOMAIN }, "", game_user_pref_dir});
224  }
225 
226  if(paths.count(GAME_EDITOR_MAP_DIR) > 0 && !have_path(res, game_editor_map_dir)) {
227  res.push_back({{ N_("filesystem_path_game^Editor maps"), GETTEXT_DOMAIN }, "", game_editor_map_dir});
228  }
229 
230  return res;
231 }
232 
233 std::vector<path_info> system_paths(std::set<SYSTEM_PATH_TYPES> paths)
234 {
235  static const std::string& home_dir = user_profile_dir();
236 
237  std::vector<path_info> res;
238 
239  if(paths.count(SYSTEM_USER_PROFILE) > 0 && !home_dir.empty()) {
240  res.push_back({{ N_("filesystem_path_system^Home"), GETTEXT_DOMAIN }, "", home_dir});
241  }
242 
243  if(paths.count(SYSTEM_ALL_DRIVES) > 0) {
244  enumerate_storage_devices(res);
245  }
246 
247 #ifndef _WIN32
248  if(paths.count(SYSTEM_ROOTFS) > 0) {
249  res.push_back({{ N_("filesystem_path_system^Root"), GETTEXT_DOMAIN }, "", "/"});
250  }
251 #endif
252 
253  return res;
254 }
255 
256 unsigned add_user_bookmark(const std::string& label, const std::string& path)
257 {
258  config cfg = get_bookmarks_config();
259 
260  config& bookmark_cfg = cfg.add_child("bookmark");
261  bookmark_cfg["label"] = label;
262  bookmark_cfg["path"] = path;
263 
264  commit_bookmarks_config(cfg);
265 
266  return cfg.child_count("bookmark");
267 }
268 
270 {
271  config cfg = get_bookmarks_config();
272  const unsigned prev_size = cfg.child_count("bookmark");
273 
274  if(index < prev_size) {
275  cfg.remove_child("bookmark", index);
276  }
277 
278  commit_bookmarks_config(cfg);
279 }
280 
281 std::vector<bookmark_info> user_bookmarks()
282 {
283  const config& cfg = get_bookmarks_config();
284  std::vector<bookmark_info> res;
285 
286  if(cfg.has_child("bookmark")) {
287  for(const config& bookmark_cfg : cfg.child_range("bookmark")) {
288  res.push_back({ bookmark_cfg["label"], bookmark_cfg["path"] });
289  }
290  }
291 
292  return res;
293 }
294 
295 } // namespace desktop
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
void remove_child(config_key_type key, std::size_t index)
Definition: config.cpp:646
std::size_t child_count(config_key_type key) const
Definition: config.cpp:298
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:318
child_itors child_range(config_key_type key)
Definition: config.cpp:274
config & add_child(config_key_type key)
Definition: config.cpp:442
Declarations for File-IO.
#define N_(String)
Definition: gettext.hpp:101
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:217
Standard logging facilities (interface).
std::vector< bookmark_info > user_bookmarks()
Definition: paths.cpp:281
unsigned add_user_bookmark(const std::string &label, const std::string &path)
Definition: paths.cpp:256
@ GAME_EDITOR_MAP_DIR
Editor map dir.
Definition: paths.hpp:62
@ GAME_USER_DATA_DIR
User data dir.
Definition: paths.hpp:61
@ GAME_CORE_DATA_DIR
Game data dir.
Definition: paths.hpp:59
@ GAME_USER_PREFS_DIR
User preferences dir.
Definition: paths.hpp:60
@ GAME_BIN_DIR
Game executable dir.
Definition: paths.hpp:58
void remove_user_bookmark(unsigned index)
Definition: paths.cpp:269
std::vector< path_info > system_paths(std::set< SYSTEM_PATH_TYPES > paths)
Returns a list of system-defined paths.
Definition: paths.cpp:233
@ SYSTEM_USER_PROFILE
Path to the user's profile dir (e.g.
Definition: paths.hpp:68
@ SYSTEM_ALL_DRIVES
Paths for each storage media found (Windows), /media and/or /mnt (X11, if non-empty).
Definition: paths.hpp:67
@ SYSTEM_ROOTFS
Path to the root of the filesystem hierarchy (ignored on Windows).
Definition: paths.hpp:69
std::string user_profile_dir()
Returns the path to the user profile dir (e.g.
Definition: paths.cpp:166
std::vector< path_info > game_paths(std::set< GAME_PATH_TYPES > paths)
Returns a list of game-related paths.
Definition: paths.cpp:200
std::ostream & operator<<(std::ostream &os, const path_info &pinf)
Definition: paths.cpp:195
std::string get_legacy_editor_dir()
std::string get_user_config_dir()
Definition: filesystem.cpp:841
std::string get_user_data_dir()
Definition: filesystem.cpp:870
std::string get_exe_dir()
Definition: filesystem.cpp:990
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
std::string path
Definition: filesystem.cpp:83
logger & err()
Definition: log.cpp:305
optional_const_config get_child(const std::string &key)
Definition: general.cpp:200
void set_child(const std::string &key, const config &val)
Definition: general.cpp:195
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
#define DBG_DU
Definition: paths.cpp:54
static lg::log_domain log_desktop("desktop")
#define GETTEXT_DOMAIN
Definition: paths.cpp:16
#define LOG_DU
Definition: paths.cpp:53
Desktop paths, storage media and bookmark functions.
std::string path
Real path.
Definition: paths.hpp:46
std::string display_name() const
Formats this path for UI display.
Definition: paths.cpp:190
std::string label
System-defined label, if the path is a drive or mount point.
Definition: paths.hpp:44
t_string name
Path name or drive letter/mount point path; may be a translatable string if it's a game resources pat...
Definition: paths.hpp:42
static map_location::DIRECTION n
#define e