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 "filesystem.hpp"
21 #include "gettext.hpp"
22 #include "log.hpp"
23 #include "preferences/general.hpp"
25 #include "utils/general.hpp"
26 
27 #if !defined(_WIN32) && !defined(__APPLE__)
28 #include <boost/filesystem.hpp>
29 #endif
30 
31 #ifndef _WIN32
32 
33 // For username stuff on Unix:
34 #include <pwd.h>
35 
36 #else // _WIN32
37 
38 #ifndef UNICODE
39 #define UNICODE
40 #endif
41 
42 #define WIN32_LEAN_AND_MEAN
43 
44 #include <windows.h>
45 #include <shlobj.h>
46 
47 #endif
48 
49 static lg::log_domain log_desktop("desktop");
50 #define ERR_DU LOG_STREAM(err, log_desktop)
51 #define LOG_DU LOG_STREAM(info, log_desktop)
52 #define DBG_DU LOG_STREAM(debug, log_desktop)
53 
54 namespace desktop
55 {
56 
57 namespace
58 {
59 
60 void enumerate_storage_devices(std::vector<path_info>& res)
61 {
62 #ifdef _WIN32
63 
64  const DWORD drive_table = GetLogicalDrives();
65 
66  for(unsigned n = 0; n < 26; ++n) {
67  if((drive_table >> n) & 1) {
68  std::string u8drive = "A:";
69  u8drive[0] += n;
70 
71  LOG_DU << "enumerate_win32_drives(): " << u8drive << " is reported to be present";
72 
73  wchar_t drive[] = L"A:\\";
74  drive[0] += n;
75 
76  const DWORD label_bufsize = MAX_PATH + 1;
77  wchar_t label[label_bufsize] { 0 };
78 
79  if(GetVolumeInformation(drive, label, label_bufsize, nullptr, nullptr, nullptr, nullptr, 0) == 0) {
80  // Probably an empty removable drive, just ignore it and carry on.
81  const DWORD err = GetLastError();
82  LOG_DU << "enumerate_win32_drives(): GetVolumeInformation() failed (" << err << ")";
83  continue;
84  }
85 
86  // Trailing slash so that we don't get compatibility per-drive working dirs
87  // involved in path resolution.
88  res.push_back({u8drive, unicode_cast<std::string>(std::wstring{label}), u8drive + '\\'});
89  }
90  }
91 
92 #elif defined(__APPLE__)
93 
94  // Probably as unreliable as /media|/mnt on other platforms, not worth
95  // examining in detail.
96  res.push_back({{ N_("filesystem_path_system^Volumes"), GETTEXT_DOMAIN }, "", "/Volumes"});
97 
98 #else
99 
100  namespace bsys = boost::system;
101  namespace bfs = boost::filesystem;
102 
103  // These are either used as mount points themselves, or host mount points. The
104  // reasoning here is that if any or all of them are non-empty, they are
105  // probably used for _something_ that might be of interest to the user (if not
106  // directly and actively controlled by the user themselves).
107  // Note that the first candidate is actually /run/media/USERNAME -- we need
108  // to fetch the username at runtime from passwd to complete the path.
109  std::vector<std::string> candidates { "/media", "/mnt" };
110 
111  // Fetch passwd entry for the effective user the current process runs as
112  if(const passwd* pw = getpwuid(geteuid()); pw && pw->pw_name && pw->pw_name[0]) {
113  candidates.emplace(candidates.begin(), "/run/media/");
114  candidates.front() += pw->pw_name;
115  }
116 
117  for(const auto& mnt : candidates) {
118  bsys::error_code e;
119  try {
120  if(bfs::is_directory(mnt, e) && !bfs::is_empty(mnt, e) && !e) {
121  DBG_DU << "enumerate_mount_parents(): " << mnt << " appears to be a non-empty dir";
122  res.push_back({mnt, "", mnt});
123  }
124  }
125  catch(...) {
126  //bool is_empty(const path& p, system::error_code& ec) might throw.
127  //For example if you have no permission on that directory. Don't list the file in that case.
128  DBG_DU << "caught exception " << utils::get_unknown_exception_type() << " in enumerate_storage_devices";
129  }
130  }
131 
132 #endif
133 }
134 
135 bool have_path(const std::vector<path_info>& pathset, const std::string& path)
136 {
137  for(const auto& pinfo : pathset) {
138  if(pinfo.path == path) {
139  return true;
140  }
141  }
142 
143  return false;
144 }
145 
146 inline std::string pretty_path(const std::string& path)
147 {
148  return filesystem::normalize_path(path, true, true);
149 }
150 
151 inline config get_bookmarks_config()
152 {
153  auto cfg = preferences::get_child("dir_bookmarks");
154  return cfg ? *cfg : config{};
155 }
156 
157 inline void commit_bookmarks_config(config& cfg)
158 {
159  preferences::set_child("dir_bookmarks", cfg);
160 }
161 
162 } // unnamed namespace
163 
164 std::string user_profile_dir()
165 {
166 #ifndef _WIN32
167 
168  // TODO: The filesystem API uses $HOME for this purpose, which may be
169  // overridden or missing. Not sure which one really makes more sense
170  // for us here.
171  const passwd* const pwd = getpwuid(geteuid());
172 
173  if(!pwd || !pwd->pw_dir || !*pwd->pw_dir) {
174  return "";
175  }
176 
177  return pwd->pw_dir;
178 
179 #else // _WIN32
180 
181  wchar_t profile_path[MAX_PATH];
182  HRESULT res = SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, SHGFP_TYPE_CURRENT, profile_path);
183  return res != S_OK ? "" : unicode_cast<std::string>(std::wstring{profile_path});
184 
185 #endif // _WIN32
186 }
187 
188 std::string path_info::display_name() const
189 {
190  return label.empty() ? name : label + " (" + name + ")";
191 }
192 
193 std::ostream& operator<<(std::ostream& os, const path_info& pinf)
194 {
195  return os << pinf.name << " [" << pinf.label << "] - " << pinf.path;
196 }
197 
198 std::vector<path_info> game_paths(std::set<GAME_PATH_TYPES> paths)
199 {
200  static const std::string& game_bin_dir = pretty_path(filesystem::get_exe_dir());
201  static const std::string& game_data_dir = pretty_path(game_config::path);
202  static const std::string& game_user_data_dir = pretty_path(filesystem::get_user_data_dir());
203  static const std::string& game_user_pref_dir = pretty_path(filesystem::get_user_config_dir());
204  static const std::string& game_editor_map_dir = pretty_path(filesystem::get_legacy_editor_dir() + "/maps");
205 
206  std::vector<path_info> res;
207 
208  if(paths.count(GAME_BIN_DIR) > 0 && !have_path(res, game_bin_dir)) {
209  res.push_back({{ N_("filesystem_path_game^Game executables"), GETTEXT_DOMAIN }, "", game_bin_dir});
210  }
211 
212  if(paths.count(GAME_CORE_DATA_DIR) > 0 && !have_path(res, game_data_dir)) {
213  res.push_back({{ N_("filesystem_path_game^Game data"), GETTEXT_DOMAIN }, "", game_data_dir});
214  }
215 
216  if(paths.count(GAME_USER_DATA_DIR) > 0 && !have_path(res, game_user_data_dir)) {
217  res.push_back({{ N_("filesystem_path_game^User data"), GETTEXT_DOMAIN }, "", game_user_data_dir});
218  }
219 
220  if(paths.count(GAME_USER_PREFS_DIR) > 0 && !have_path(res, game_user_pref_dir)) {
221  res.push_back({{ N_("filesystem_path_game^User preferences"), GETTEXT_DOMAIN }, "", game_user_pref_dir});
222  }
223 
224  if(paths.count(GAME_EDITOR_MAP_DIR) > 0 && !have_path(res, game_editor_map_dir)) {
225  res.push_back({{ N_("filesystem_path_game^Editor maps"), GETTEXT_DOMAIN }, "", game_editor_map_dir});
226  }
227 
228  return res;
229 }
230 
231 std::vector<path_info> system_paths(std::set<SYSTEM_PATH_TYPES> paths)
232 {
233  static const std::string& home_dir = user_profile_dir();
234 
235  std::vector<path_info> res;
236 
237  if(paths.count(SYSTEM_USER_PROFILE) > 0 && !home_dir.empty()) {
238  res.push_back({{ N_("filesystem_path_system^Home"), GETTEXT_DOMAIN }, "", home_dir});
239  }
240 
241  if(paths.count(SYSTEM_ALL_DRIVES) > 0) {
242  enumerate_storage_devices(res);
243  }
244 
245 #ifndef _WIN32
246  if(paths.count(SYSTEM_ROOTFS) > 0) {
247  res.push_back({{ N_("filesystem_path_system^Root"), GETTEXT_DOMAIN }, "", "/"});
248  }
249 #endif
250 
251  return res;
252 }
253 
254 unsigned add_user_bookmark(const std::string& label, const std::string& path)
255 {
256  config cfg = get_bookmarks_config();
257 
258  config& bookmark_cfg = cfg.add_child("bookmark");
259  bookmark_cfg["label"] = label;
260  bookmark_cfg["path"] = path;
261 
262  commit_bookmarks_config(cfg);
263 
264  return cfg.child_count("bookmark");
265 }
266 
268 {
269  config cfg = get_bookmarks_config();
270  const unsigned prev_size = cfg.child_count("bookmark");
271 
272  if(index < prev_size) {
273  cfg.remove_child("bookmark", index);
274  }
275 
276  commit_bookmarks_config(cfg);
277 }
278 
279 std::vector<bookmark_info> user_bookmarks()
280 {
281  const config& cfg = get_bookmarks_config();
282  std::vector<bookmark_info> res;
283 
284  if(cfg.has_child("bookmark")) {
285  for(const config& bookmark_cfg : cfg.child_range("bookmark")) {
286  res.push_back({ bookmark_cfg["label"], bookmark_cfg["path"] });
287  }
288  }
289 
290  return res;
291 }
292 
293 } // 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:645
std::size_t child_count(config_key_type key) const
Definition: config.cpp:297
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:317
child_itors child_range(config_key_type key)
Definition: config.cpp:273
config & add_child(config_key_type key)
Definition: config.cpp:441
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:209
Standard logging facilities (interface).
std::vector< bookmark_info > user_bookmarks()
Definition: paths.cpp:279
unsigned add_user_bookmark(const std::string &label, const std::string &path)
Definition: paths.cpp:254
@ 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:267
std::vector< path_info > system_paths(std::set< SYSTEM_PATH_TYPES > paths)
Returns a list of system-defined paths.
Definition: paths.cpp:231
@ 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:164
std::vector< path_info > game_paths(std::set< GAME_PATH_TYPES > paths)
Returns a list of game-related paths.
Definition: paths.cpp:198
std::ostream & operator<<(std::ostream &os, const path_info &pinf)
Definition: paths.cpp:193
std::string get_legacy_editor_dir()
std::string get_user_config_dir()
Definition: filesystem.cpp:842
std::string get_user_data_dir()
Definition: filesystem.cpp:871
std::string get_exe_dir()
Definition: filesystem.cpp:991
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:84
logger & err()
Definition: log.cpp:302
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:70
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:52
static lg::log_domain log_desktop("desktop")
#define GETTEXT_DOMAIN
Definition: paths.cpp:16
#define LOG_DU
Definition: paths.cpp:51
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:188
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