The Battle for Wesnoth  1.19.5+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  const auto& cfg = prefs::get().dir_bookmarks();
154  return cfg.has_value() ? cfg.value() : config();
155 
156 // return cfg ? *cfg : config{};
157 }
158 
159 inline void commit_bookmarks_config(config& cfg)
160 {
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_editor_map_dir = pretty_path(filesystem::get_legacy_editor_dir() + "/maps");
206 
207  std::vector<path_info> res;
208 
209  if(paths.count(GAME_BIN_DIR) > 0 && !have_path(res, game_bin_dir)) {
210  res.push_back({{ N_("filesystem_path_game^Game executables"), GETTEXT_DOMAIN }, "", game_bin_dir});
211  }
212 
213  if(paths.count(GAME_CORE_DATA_DIR) > 0 && !have_path(res, game_data_dir)) {
214  res.push_back({{ N_("filesystem_path_game^Game data"), GETTEXT_DOMAIN }, "", game_data_dir});
215  }
216 
217  if(paths.count(GAME_USER_DATA_DIR) > 0 && !have_path(res, game_user_data_dir)) {
218  res.push_back({{ N_("filesystem_path_game^User data"), GETTEXT_DOMAIN }, "", game_user_data_dir});
219  }
220 
221  if(paths.count(GAME_EDITOR_MAP_DIR) > 0 && !have_path(res, game_editor_map_dir)) {
222  res.push_back({{ N_("filesystem_path_game^Editor maps"), GETTEXT_DOMAIN }, "", game_editor_map_dir});
223  }
224 
225  return res;
226 }
227 
228 std::vector<path_info> system_paths(std::set<SYSTEM_PATH_TYPES> paths)
229 {
230  static const std::string& home_dir = user_profile_dir();
231 
232  std::vector<path_info> res;
233 
234  if(paths.count(SYSTEM_USER_PROFILE) > 0 && !home_dir.empty()) {
235  res.push_back({{ N_("filesystem_path_system^Home"), GETTEXT_DOMAIN }, "", home_dir});
236  }
237 
238  if(paths.count(SYSTEM_ALL_DRIVES) > 0) {
239  enumerate_storage_devices(res);
240  }
241 
242 #ifndef _WIN32
243  if(paths.count(SYSTEM_ROOTFS) > 0) {
244  res.push_back({{ N_("filesystem_path_system^Root"), GETTEXT_DOMAIN }, "", "/"});
245  }
246 #endif
247 
248  return res;
249 }
250 
251 unsigned add_user_bookmark(const std::string& label, const std::string& path)
252 {
253  config cfg = get_bookmarks_config();
254 
255  config& bookmark_cfg = cfg.add_child("bookmark");
256  bookmark_cfg["label"] = label;
257  bookmark_cfg["path"] = path;
258 
259  commit_bookmarks_config(cfg);
260 
261  return cfg.child_count("bookmark");
262 }
263 
265 {
266  config cfg = get_bookmarks_config();
267  const unsigned prev_size = cfg.child_count("bookmark");
268 
269  if(index < prev_size) {
270  cfg.remove_child("bookmark", index);
271  }
272 
273  commit_bookmarks_config(cfg);
274 }
275 
276 std::vector<bookmark_info> user_bookmarks()
277 {
278  const config& cfg = get_bookmarks_config();
279  std::vector<bookmark_info> res;
280 
281  if(cfg.has_child("bookmark")) {
282  for(const config& bookmark_cfg : cfg.child_range("bookmark")) {
283  res.push_back({ bookmark_cfg["label"], bookmark_cfg["path"] });
284  }
285  }
286 
287  return res;
288 }
289 
290 } // namespace desktop
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
void remove_child(config_key_type key, std::size_t index)
Definition: config.cpp:643
std::size_t child_count(config_key_type key) const
Definition: config.cpp:296
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:316
child_itors child_range(config_key_type key)
Definition: config.cpp:272
config & add_child(config_key_type key)
Definition: config.cpp:440
bool has_value() const
Definition: config.hpp:100
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:200
Standard logging facilities (interface).
std::vector< bookmark_info > user_bookmarks()
Definition: paths.cpp:276
unsigned add_user_bookmark(const std::string &label, const std::string &path)
Definition: paths.cpp:251
@ 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:264
std::vector< path_info > system_paths(std::set< SYSTEM_PATH_TYPES > paths)
Returns a list of system-defined paths.
Definition: paths.cpp:228
@ 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: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_data_dir()
Definition: filesystem.cpp:827
std::string get_exe_dir()
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:90
logger & err()
Definition: log.cpp:307
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: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