The Battle for Wesnoth  1.19.2+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"
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 = prefs::get().dir_bookmarks();
154  return cfg ? *cfg : config{};
155 }
156 
157 inline void commit_bookmarks_config(config& cfg)
158 {
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_editor_map_dir = pretty_path(filesystem::get_legacy_editor_dir() + "/maps");
204 
205  std::vector<path_info> res;
206 
207  if(paths.count(GAME_BIN_DIR) > 0 && !have_path(res, game_bin_dir)) {
208  res.push_back({{ N_("filesystem_path_game^Game executables"), GETTEXT_DOMAIN }, "", game_bin_dir});
209  }
210 
211  if(paths.count(GAME_CORE_DATA_DIR) > 0 && !have_path(res, game_data_dir)) {
212  res.push_back({{ N_("filesystem_path_game^Game data"), GETTEXT_DOMAIN }, "", game_data_dir});
213  }
214 
215  if(paths.count(GAME_USER_DATA_DIR) > 0 && !have_path(res, game_user_data_dir)) {
216  res.push_back({{ N_("filesystem_path_game^User data"), GETTEXT_DOMAIN }, "", game_user_data_dir});
217  }
218 
219  if(paths.count(GAME_EDITOR_MAP_DIR) > 0 && !have_path(res, game_editor_map_dir)) {
220  res.push_back({{ N_("filesystem_path_game^Editor maps"), GETTEXT_DOMAIN }, "", game_editor_map_dir});
221  }
222 
223  return res;
224 }
225 
226 std::vector<path_info> system_paths(std::set<SYSTEM_PATH_TYPES> paths)
227 {
228  static const std::string& home_dir = user_profile_dir();
229 
230  std::vector<path_info> res;
231 
232  if(paths.count(SYSTEM_USER_PROFILE) > 0 && !home_dir.empty()) {
233  res.push_back({{ N_("filesystem_path_system^Home"), GETTEXT_DOMAIN }, "", home_dir});
234  }
235 
236  if(paths.count(SYSTEM_ALL_DRIVES) > 0) {
237  enumerate_storage_devices(res);
238  }
239 
240 #ifndef _WIN32
241  if(paths.count(SYSTEM_ROOTFS) > 0) {
242  res.push_back({{ N_("filesystem_path_system^Root"), GETTEXT_DOMAIN }, "", "/"});
243  }
244 #endif
245 
246  return res;
247 }
248 
249 unsigned add_user_bookmark(const std::string& label, const std::string& path)
250 {
251  config cfg = get_bookmarks_config();
252 
253  config& bookmark_cfg = cfg.add_child("bookmark");
254  bookmark_cfg["label"] = label;
255  bookmark_cfg["path"] = path;
256 
257  commit_bookmarks_config(cfg);
258 
259  return cfg.child_count("bookmark");
260 }
261 
263 {
264  config cfg = get_bookmarks_config();
265  const unsigned prev_size = cfg.child_count("bookmark");
266 
267  if(index < prev_size) {
268  cfg.remove_child("bookmark", index);
269  }
270 
271  commit_bookmarks_config(cfg);
272 }
273 
274 std::vector<bookmark_info> user_bookmarks()
275 {
276  const config& cfg = get_bookmarks_config();
277  std::vector<bookmark_info> res;
278 
279  if(cfg.has_child("bookmark")) {
280  for(const config& bookmark_cfg : cfg.child_range("bookmark")) {
281  res.push_back({ bookmark_cfg["label"], bookmark_cfg["path"] });
282  }
283  }
284 
285  return res;
286 }
287 
288 } // 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
static prefs & get()
optional_const_config dir_bookmarks()
void set_dir_bookmarks(const config &cfg)
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:207
Standard logging facilities (interface).
std::vector< bookmark_info > user_bookmarks()
Definition: paths.cpp:274
unsigned add_user_bookmark(const std::string &label, const std::string &path)
Definition: paths.cpp:249
@ GAME_EDITOR_MAP_DIR
Editor map dir.
Definition: paths.hpp:61
@ GAME_USER_DATA_DIR
User data dir.
Definition: paths.hpp:60
@ GAME_CORE_DATA_DIR
Game data dir.
Definition: paths.hpp:59
@ GAME_BIN_DIR
Game executable dir.
Definition: paths.hpp:58
void remove_user_bookmark(unsigned index)
Definition: paths.cpp:262
std::vector< path_info > system_paths(std::set< SYSTEM_PATH_TYPES > paths)
Returns a list of system-defined paths.
Definition: paths.cpp:226
@ SYSTEM_USER_PROFILE
Path to the user's profile dir (e.g.
Definition: paths.hpp:67
@ SYSTEM_ALL_DRIVES
Paths for each storage media found (Windows), /media and/or /mnt (X11, if non-empty).
Definition: paths.hpp:66
@ SYSTEM_ROOTFS
Path to the root of the filesystem hierarchy (ignored on Windows).
Definition: paths.hpp:68
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_data_dir()
Definition: filesystem.cpp:793
std::string get_exe_dir()
Definition: filesystem.cpp:969
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:89
logger & err()
Definition: log.cpp:304
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