The Battle for Wesnoth  1.15.0-dev
paths.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2016 - 2018 by Iris Morelle <shadowm2006@gmail.com>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "desktop/paths.hpp"
18 
19 #include "game_config.hpp"
20 #include "filesystem.hpp"
21 #include "gettext.hpp"
22 #include "log.hpp"
23 #include "preferences/general.hpp"
25 
26 #if !defined(_WIN32) && !defined(__APPLE__)
27 #include <boost/filesystem.hpp>
28 #endif
29 
30 #ifndef _WIN32
31 
32 // For username stuff on Unix:
33 #include <pwd.h>
34 #include <sys/types.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\n";
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 << ")\n";
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  static const std::vector<std::string> candidates { "/media", "/mnt" };
108 
109  for(const auto& mnt : candidates) {
110  bsys::error_code e;
111  try {
112  if(bfs::is_directory(mnt, e) && !bfs::is_empty(mnt, e) && !e) {
113  DBG_DU << "enumerate_mount_parents(): " << mnt << " appears to be a non-empty dir\n";
114  res.push_back({mnt, "", mnt});
115  }
116  }
117  catch(...) {
118  //bool is_empty(const path& p, system::error_code& ec) might throw.
119  //For example if you have no permission on that directory. Don't list the file in that case.
120  DBG_DU << "caught exception in enumerate_storage_devices\n";
121  }
122  }
123 
124 #endif
125 }
126 
127 bool have_path(const std::vector<path_info>& pathset, const std::string& path)
128 {
129  for(const auto& pinfo : pathset) {
130  if(pinfo.path == path) {
131  return true;
132  }
133  }
134 
135  return false;
136 }
137 
138 inline std::string pretty_path(const std::string& path)
139 {
140  return filesystem::normalize_path(path, true, true);
141 }
142 
143 inline config get_bookmarks_config()
144 {
145  const config& cfg = preferences::get_child("dir_bookmarks");
146  return cfg ? cfg : config{};
147 }
148 
149 inline void commit_bookmarks_config(config& cfg)
150 {
151  preferences::set_child("dir_bookmarks", cfg);
152 }
153 
154 } // unnamed namespace
155 
156 std::string user_profile_dir()
157 {
158 #ifndef _WIN32
159 
160  // TODO: The filesystem API uses $HOME for this purpose, which may be
161  // overridden or missing. Not sure which one really makes more sense
162  // for us here.
163  const passwd* const pwd = getpwuid(geteuid());
164 
165  if(!pwd || !pwd->pw_dir || !*pwd->pw_dir) {
166  return "";
167  }
168 
169  return pwd->pw_dir;
170 
171 #else // _WIN32
172 
173  wchar_t profile_path[MAX_PATH];
174  HRESULT res = SHGetFolderPath(nullptr, CSIDL_PROFILE, nullptr, SHGFP_TYPE_CURRENT, profile_path);
175  return res != S_OK ? "" : unicode_cast<std::string>(std::wstring{profile_path});
176 
177 #endif // _WIN32
178 }
179 
180 std::string path_info::display_name() const
181 {
182  return label.empty() ? name : label + " (" + name + ")";
183 }
184 
185 std::ostream& operator<<(std::ostream& os, const path_info& pinf)
186 {
187  return os << pinf.name << " [" << pinf.label << "] - " << pinf.path;
188 }
189 
190 std::vector<path_info> game_paths(unsigned path_types)
191 {
192  static const std::string& game_bin_dir = pretty_path(filesystem::get_exe_dir());
193  static const std::string& game_data_dir = pretty_path(game_config::path);
194  static const std::string& game_user_data_dir = pretty_path(filesystem::get_user_data_dir());
195  static const std::string& game_user_pref_dir = pretty_path(filesystem::get_user_config_dir());
196 
197  std::vector<path_info> res;
198 
199  if(path_types & GAME_BIN_DIR && !have_path(res, game_bin_dir)) {
200  res.push_back({{ N_("filesystem_path_game^Game executables"), GETTEXT_DOMAIN }, "", game_bin_dir});
201  }
202 
203  if(path_types & GAME_CORE_DATA_DIR && !have_path(res, game_data_dir)) {
204  res.push_back({{ N_("filesystem_path_game^Game data"), GETTEXT_DOMAIN }, "", game_data_dir});
205  }
206 
207  if(path_types & GAME_USER_DATA_DIR && !have_path(res, game_user_data_dir)) {
208  res.push_back({{ N_("filesystem_path_game^User data"), GETTEXT_DOMAIN }, "", game_user_data_dir});
209  }
210 
211  if(path_types & GAME_USER_PREFS_DIR && !have_path(res, game_user_pref_dir)) {
212  res.push_back({{ N_("filesystem_path_game^User preferences"), GETTEXT_DOMAIN }, "", game_user_pref_dir});
213  }
214 
215  return res;
216 }
217 
218 std::vector<path_info> system_paths(unsigned path_types)
219 {
220  static const std::string& home_dir = user_profile_dir();
221 
222  std::vector<path_info> res;
223 
224  if(path_types & SYSTEM_USER_PROFILE && !home_dir.empty()) {
225  res.push_back({{ N_("filesystem_path_system^Home"), GETTEXT_DOMAIN }, "", home_dir});
226  }
227 
228  if(path_types & SYSTEM_ALL_DRIVES) {
229  enumerate_storage_devices(res);
230  }
231 
232 #ifndef _WIN32
233  if(path_types & SYSTEM_ROOTFS) {
234  res.push_back({{ N_("filesystem_path_system^Root"), GETTEXT_DOMAIN }, "", "/"});
235  }
236 #endif
237 
238  return res;
239 }
240 
241 unsigned add_user_bookmark(const std::string& label, const std::string& path)
242 {
243  config cfg = get_bookmarks_config();
244 
245  config& bookmark_cfg = cfg.add_child("bookmark");
246  bookmark_cfg["label"] = label;
247  bookmark_cfg["path"] = path;
248 
249  commit_bookmarks_config(cfg);
250 
251  return cfg.child_count("bookmark");
252 }
253 
255 {
256  config cfg = get_bookmarks_config();
257  const unsigned prev_size = cfg.child_count("bookmark");
258 
259  if(index < prev_size) {
260  cfg.remove_child("bookmark", index);
261  }
262 
263  commit_bookmarks_config(cfg);
264 }
265 
266 std::vector<bookmark_info> user_bookmarks()
267 {
268  const config& cfg = get_bookmarks_config();
269  std::vector<bookmark_info> res;
270 
271  if(cfg.has_child("bookmark")) {
272  for(const config& bookmark_cfg : cfg.child_range("bookmark")) {
273  res.push_back({ bookmark_cfg["label"], bookmark_cfg["path"] });
274  }
275  }
276 
277  return res;
278 }
279 
280 } // namespace desktop
void remove_user_bookmark(unsigned index)
Definition: paths.cpp:254
Paths for each storage media found (Windows), /media and/or /mnt (X11, if non-empty).
Definition: paths.hpp:64
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:416
unsigned child_count(config_key_type key) const
Definition: config.cpp:394
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:366
#define DBG_DU
Definition: paths.cpp:52
std::vector< bookmark_info > user_bookmarks()
Definition: paths.cpp:266
Path to the root of the filesystem hierarchy (ignored on Windows).
Definition: paths.hpp:66
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:190
static lg::log_domain log_desktop("desktop")
std::string get_user_data_dir()
Definition: filesystem.cpp:737
std::vector< path_info > system_paths(unsigned path_types)
Returns a list of system-defined paths.
Definition: paths.cpp:218
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:40
std::string display_name() const
Formats this path for UI display.
Definition: paths.cpp:180
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
Definition: filesystem.cpp:972
std::string path
Definition: game_config.cpp:39
Desktop paths, storage media and bookmark functions.
#define GETTEXT_DOMAIN
Definition: paths.cpp:15
std::string label
System-defined label, if the path is a drive or mount point.
Definition: paths.hpp:42
User preferences dir.
Definition: paths.hpp:58
void set_child(const std::string &key, const config &val)
Definition: general.cpp:210
std::string get_exe_dir()
Definition: filesystem.cpp:783
logger & err()
Definition: log.cpp:78
std::string path
Real path.
Definition: paths.hpp:44
Path to the user&#39;s profile dir (e.g.
Definition: paths.hpp:65
Game executable dir.
Definition: paths.hpp:56
unsigned add_user_bookmark(const std::string &label, const std::string &path)
Definition: paths.cpp:241
Declarations for File-IO.
#define N_(String)
Definition: gettext.hpp:97
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:71
const config & get_child(const std::string &key)
Definition: general.cpp:215
config & add_child(config_key_type key)
Definition: config.cpp:479
std::string user_profile_dir()
Returns the path to the user profile dir (e.g.
Definition: paths.cpp:156
#define LOG_DU
Definition: paths.cpp:51
std::string get_user_config_dir()
Definition: filesystem.cpp:708
Game data dir.
Definition: paths.hpp:57
Standard logging facilities (interface).
std::ostream & operator<<(std::ostream &os, const path_info &pinf)
Definition: paths.cpp:185
#define e
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:92
static map_location::DIRECTION n
void remove_child(config_key_type key, unsigned index)
Definition: config.cpp:696
User data dir.
Definition: paths.hpp:59