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