The Battle for Wesnoth  1.17.14+dev
paths.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2022
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  static const std::vector<std::string> candidates { "/media", "/mnt" };
110 
111  for(const auto& mnt : candidates) {
112  bsys::error_code e;
113  try {
114  if(bfs::is_directory(mnt, e) && !bfs::is_empty(mnt, e) && !e) {
115  DBG_DU << "enumerate_mount_parents(): " << mnt << " appears to be a non-empty dir";
116  res.push_back({mnt, "", mnt});
117  }
118  }
119  catch(...) {
120  //bool is_empty(const path& p, system::error_code& ec) might throw.
121  //For example if you have no permission on that directory. Don't list the file in that case.
122  DBG_DU << "caught exception " << utils::get_unknown_exception_type() << " in enumerate_storage_devices";
123  }
124  }
125 
126 #endif
127 }
128 
129 bool have_path(const std::vector<path_info>& pathset, const std::string& path)
130 {
131  for(const auto& pinfo : pathset) {
132  if(pinfo.path == path) {
133  return true;
134  }
135  }
136 
137  return false;
138 }
139 
140 inline std::string pretty_path(const std::string& path)
141 {
142  return filesystem::normalize_path(path, true, true);
143 }
144 
145 inline config get_bookmarks_config()
146 {
147  const config& cfg = preferences::get_child("dir_bookmarks");
148  return cfg ? cfg : config{};
149 }
150 
151 inline void commit_bookmarks_config(config& cfg)
152 {
153  preferences::set_child("dir_bookmarks", cfg);
154 }
155 
156 } // unnamed namespace
157 
158 std::string user_profile_dir()
159 {
160 #ifndef _WIN32
161 
162  // TODO: The filesystem API uses $HOME for this purpose, which may be
163  // overridden or missing. Not sure which one really makes more sense
164  // for us here.
165  const passwd* const pwd = getpwuid(geteuid());
166 
167  if(!pwd || !pwd->pw_dir || !*pwd->pw_dir) {
168  return "";
169  }
170 
171  return pwd->pw_dir;
172 
173 #else // _WIN32
174 
175  wchar_t profile_path[MAX_PATH];
176  HRESULT res = SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, SHGFP_TYPE_CURRENT, profile_path);
177  return res != S_OK ? "" : unicode_cast<std::string>(std::wstring{profile_path});
178 
179 #endif // _WIN32
180 }
181 
182 std::string path_info::display_name() const
183 {
184  return label.empty() ? name : label + " (" + name + ")";
185 }
186 
187 std::ostream& operator<<(std::ostream& os, const path_info& pinf)
188 {
189  return os << pinf.name << " [" << pinf.label << "] - " << pinf.path;
190 }
191 
192 std::vector<path_info> game_paths(unsigned path_types)
193 {
194  static const std::string& game_bin_dir = pretty_path(filesystem::get_exe_dir());
195  static const std::string& game_data_dir = pretty_path(game_config::path);
196  static const std::string& game_user_data_dir = pretty_path(filesystem::get_user_data_dir());
197  static const std::string& game_user_pref_dir = pretty_path(filesystem::get_user_config_dir());
198 
199  std::vector<path_info> res;
200 
201  if(path_types & GAME_BIN_DIR && !have_path(res, game_bin_dir)) {
202  res.push_back({{ N_("filesystem_path_game^Game executables"), GETTEXT_DOMAIN }, "", game_bin_dir});
203  }
204 
205  if(path_types & GAME_CORE_DATA_DIR && !have_path(res, game_data_dir)) {
206  res.push_back({{ N_("filesystem_path_game^Game data"), GETTEXT_DOMAIN }, "", game_data_dir});
207  }
208 
209  if(path_types & GAME_USER_DATA_DIR && !have_path(res, game_user_data_dir)) {
210  res.push_back({{ N_("filesystem_path_game^User data"), GETTEXT_DOMAIN }, "", game_user_data_dir});
211  }
212 
213  if(path_types & GAME_USER_PREFS_DIR && !have_path(res, game_user_pref_dir)) {
214  res.push_back({{ N_("filesystem_path_game^User preferences"), GETTEXT_DOMAIN }, "", game_user_pref_dir});
215  }
216 
217  return res;
218 }
219 
220 std::vector<path_info> system_paths(unsigned path_types)
221 {
222  static const std::string& home_dir = user_profile_dir();
223 
224  std::vector<path_info> res;
225 
226  if(path_types & SYSTEM_USER_PROFILE && !home_dir.empty()) {
227  res.push_back({{ N_("filesystem_path_system^Home"), GETTEXT_DOMAIN }, "", home_dir});
228  }
229 
230  if(path_types & SYSTEM_ALL_DRIVES) {
231  enumerate_storage_devices(res);
232  }
233 
234 #ifndef _WIN32
235  if(path_types & SYSTEM_ROOTFS) {
236  res.push_back({{ N_("filesystem_path_system^Root"), GETTEXT_DOMAIN }, "", "/"});
237  }
238 #endif
239 
240  return res;
241 }
242 
243 unsigned add_user_bookmark(const std::string& label, const std::string& path)
244 {
245  config cfg = get_bookmarks_config();
246 
247  config& bookmark_cfg = cfg.add_child("bookmark");
248  bookmark_cfg["label"] = label;
249  bookmark_cfg["path"] = path;
250 
251  commit_bookmarks_config(cfg);
252 
253  return cfg.child_count("bookmark");
254 }
255 
257 {
258  config cfg = get_bookmarks_config();
259  const unsigned prev_size = cfg.child_count("bookmark");
260 
261  if(index < prev_size) {
262  cfg.remove_child("bookmark", index);
263  }
264 
265  commit_bookmarks_config(cfg);
266 }
267 
268 std::vector<bookmark_info> user_bookmarks()
269 {
270  const config& cfg = get_bookmarks_config();
271  std::vector<bookmark_info> res;
272 
273  if(cfg.has_child("bookmark")) {
274  for(const config& bookmark_cfg : cfg.child_range("bookmark")) {
275  res.push_back({ bookmark_cfg["label"], bookmark_cfg["path"] });
276  }
277  }
278 
279  return res;
280 }
281 
282 } // namespace desktop
void remove_user_bookmark(unsigned index)
Definition: paths.cpp:256
Paths for each storage media found (Windows), /media and/or /mnt (X11, if non-empty).
Definition: paths.hpp:65
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:394
unsigned child_count(config_key_type key) const
Definition: config.cpp:372
ucs4_convert_impl::enableif< TD, typename TS::value_type >::type unicode_cast(const TS &source)
child_itors child_range(config_key_type key)
Definition: config.cpp:344
#define DBG_DU
Definition: paths.cpp:54
std::vector< bookmark_info > user_bookmarks()
Definition: paths.cpp:268
Path to the root of the filesystem hierarchy (ignored on Windows).
Definition: paths.hpp:67
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
std::string normalize_path(const std::string &fpath, bool normalize_separators, bool resolve_dot_entries)
Returns the absolute path of a file.
std::vector< path_info > game_paths(unsigned path_types)
Returns a list of game-related paths.
Definition: paths.cpp:192
std::string label
What to show in the filter&#39;s drop-down list.
Definition: manager.cpp:217
static lg::log_domain log_desktop("desktop")
std::string get_user_data_dir()
Definition: filesystem.cpp:801
std::vector< path_info > system_paths(unsigned path_types)
Returns a list of system-defined paths.
Definition: paths.cpp:220
t_string name
Path name or drive letter/mount point path; may be a translatable string if it&#39;s a game resources pat...
Definition: paths.hpp:41
std::string display_name() const
Formats this path for UI display.
Definition: paths.cpp:182
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::string path
Definition: game_config.cpp:39
Desktop paths, storage media and bookmark functions.
#define GETTEXT_DOMAIN
Definition: paths.cpp:16
std::string label
System-defined label, if the path is a drive or mount point.
Definition: paths.hpp:43
User preferences dir.
Definition: paths.hpp:59
void set_child(const std::string &key, const config &val)
Definition: general.cpp:195
std::string get_exe_dir()
Definition: filesystem.cpp:921
logger & err()
Definition: log.cpp:216
std::string path
Real path.
Definition: paths.hpp:45
Path to the user&#39;s profile dir (e.g.
Definition: paths.hpp:66
Game executable dir.
Definition: paths.hpp:57
unsigned add_user_bookmark(const std::string &label, const std::string &path)
Definition: paths.cpp:243
Declarations for File-IO.
#define N_(String)
Definition: gettext.hpp:101
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
const config & get_child(const std::string &key)
Definition: general.cpp:200
config & add_child(config_key_type key)
Definition: config.cpp:514
std::string user_profile_dir()
Returns the path to the user profile dir (e.g.
Definition: paths.cpp:158
#define LOG_DU
Definition: paths.cpp:53
std::string get_user_config_dir()
Definition: filesystem.cpp:772
Game data dir.
Definition: paths.hpp:58
Standard logging facilities (interface).
std::ostream & operator<<(std::ostream &os, const path_info &pinf)
Definition: paths.cpp:187
#define e
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
static map_location::DIRECTION n
void remove_child(config_key_type key, unsigned index)
Definition: config.cpp:732
User data dir.
Definition: paths.hpp:60