The Battle for Wesnoth  1.17.0-dev
manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
3  Copyright (C) 2008 - 2020 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 #include "addon/manager.hpp"
17 
18 #include "filesystem.hpp"
19 #include "log.hpp"
20 #include "serialization/parser.hpp"
21 #include "game_version.hpp"
22 
23 #include <boost/algorithm/string.hpp>
24 
25 static lg::log_domain log_config("config");
26 #define ERR_CFG LOG_STREAM(err , log_config)
27 #define LOG_CFG LOG_STREAM(info, log_config)
28 #define WRN_CFG LOG_STREAM(warn, log_config)
29 
30 static lg::log_domain log_filesystem("filesystem");
31 #define ERR_FS LOG_STREAM(err , log_filesystem)
32 
33 static lg::log_domain log_network("network");
34 #define ERR_NET LOG_STREAM(err , log_network)
35 #define LOG_NET LOG_STREAM(info, log_network)
36 
37 namespace {
38  std::string get_pbl_file_path(const std::string& addon_name)
39  {
40  const std::string& parentd = filesystem::get_addons_dir();
41  // Allow .pbl files directly in the addon dir
42  const std::string exterior = parentd + "/" + addon_name + ".pbl";
43  const std::string interior = parentd + "/" + addon_name + "/_server.pbl";
44  return filesystem::file_exists(exterior) ? exterior : interior;
45  }
46 
47  inline std::string get_info_file_path(const std::string& addon_name)
48  {
49  return filesystem::get_addons_dir() + "/" + addon_name + "/_info.cfg";
50  }
51 }
52 
53 bool have_addon_in_vcs_tree(const std::string& addon_name)
54 {
55  static const std::string parentd = filesystem::get_addons_dir();
56  return
57  filesystem::file_exists(parentd+"/"+addon_name+"/.svn") ||
58  filesystem::file_exists(parentd+"/"+addon_name+"/.git") ||
59  filesystem::file_exists(parentd+"/"+addon_name+"/.hg");
60 }
61 
62 bool have_addon_pbl_info(const std::string& addon_name)
63 {
64  return filesystem::file_exists(get_pbl_file_path(addon_name));
65 }
66 
67 config get_addon_pbl_info(const std::string& addon_name)
68 {
69  config cfg;
70  const std::string& pbl_path = get_pbl_file_path(addon_name);
71  try {
73  read(cfg, *stream);
74  } catch(const config::error& e) {
75  throw invalid_pbl_exception(pbl_path, e.message);
76  }
77 
78  return cfg;
79 }
80 
81 void set_addon_pbl_info(const std::string& addon_name, const config& cfg)
82 {
83  filesystem::scoped_ostream stream = filesystem::ostream_file(get_pbl_file_path(addon_name));
84  write(*stream, cfg);
85 }
86 
87 bool have_addon_install_info(const std::string& addon_name)
88 {
89  return filesystem::file_exists(get_info_file_path(addon_name));
90 }
91 
92 void get_addon_install_info(const std::string& addon_name, config& cfg)
93 {
94  const std::string& info_path = get_info_file_path(addon_name);
96  try {
97  // The parser's read() API would normally do this at the start. This
98  // is a safeguard in case read() throws later
99  cfg.clear();
100  config envelope;
101  read(envelope, *stream);
102  if(config& info = envelope.child("info")) {
103  cfg = std::move(info);
104  }
105  } catch(const config::error& e) {
106  ERR_CFG << "Failed to read add-on installation information for '"
107  << addon_name << "' from " << info_path << ":\n"
108  << e.message << std::endl;
109  }
110 }
111 
112 void write_addon_install_info(const std::string& addon_name, const config& cfg)
113 {
114  LOG_CFG << "Writing version info for add-on '" << addon_name << "'\n";
115 
116  const auto& info_path = get_info_file_path(addon_name);
117  auto out = filesystem::ostream_file(info_path);
118 
119  *out << "#\n"
120  << "# File automatically generated by Wesnoth to keep track\n"
121  << "# of version information on installed add-ons. DO NOT EDIT!\n"
122  << "#\n";
123 
124  config envelope;
125  envelope.add_child("info", cfg);
126  write(*out, envelope);
127 }
128 
129 bool remove_local_addon(const std::string& addon)
130 {
131  const std::string addon_dir = filesystem::get_addons_dir() + "/" + addon;
132 
133  LOG_CFG << "removing local add-on: " << addon << '\n';
134 
135  if(filesystem::file_exists(addon_dir) && !filesystem::delete_directory(addon_dir, true)) {
136  ERR_CFG << "Failed to delete directory/file: " << addon_dir << '\n';
137  ERR_CFG << "removal of add-on " << addon << " failed!" << std::endl;
138  return false;
139  }
140  return true;
141 }
142 
143 namespace {
144 
146 {
147  ADDON_ANY,
148  ADDON_HAS_PBL,
149 };
150 
151 std::vector<std::string> enumerate_addons_internal(ADDON_ENUM_CRITERIA filter)
152 {
153  std::vector<std::string> res;
154  std::vector<std::string> addon_dirnames;
155 
156  const auto& addons_root = filesystem::get_addons_dir();
157  filesystem::get_files_in_dir(addons_root, nullptr, &addon_dirnames);
158 
159  for(const auto& addon_name : addon_dirnames) {
160  if(filesystem::file_exists(addons_root + "/" + addon_name + "/_main.cfg") &&
161  (filter != ADDON_HAS_PBL || have_addon_pbl_info(addon_name)))
162  {
163  res.emplace_back(addon_name);
164  }
165  }
166 
167  return res;
168 }
169 
170 }
171 
172 std::vector<std::string> available_addons()
173 {
174  return enumerate_addons_internal(ADDON_HAS_PBL);
175 }
176 
177 std::vector<std::string> installed_addons()
178 {
179  return enumerate_addons_internal(ADDON_ANY);
180 }
181 
182 std::map<std::string, std::string> installed_addons_and_versions()
183 {
184  std::map<std::string, std::string> addons;
185 
186  for(const std::string& addon_id : installed_addons()) {
187  if(have_addon_pbl_info(addon_id)) {
188  try {
189  addons[addon_id] = get_addon_pbl_info(addon_id)["version"].str();
190  } catch(const invalid_pbl_exception&) {
191  addons[addon_id] = "Invalid pbl file, version unknown";
192  }
193  } else if(filesystem::file_exists(get_info_file_path(addon_id))) {
194  config info_cfg;
195  get_addon_install_info(addon_id, info_cfg);
196  addons[addon_id] = !info_cfg.empty() ? info_cfg["version"].str() : "Unknown";
197  } else {
198  addons[addon_id] = "Unknown";
199  }
200  }
201  return addons;
202 }
203 
204 bool is_addon_installed(const std::string& addon_name)
205 {
206  const std::string namestem = filesystem::get_addons_dir() + "/" + addon_name;
207  return filesystem::file_exists(namestem + "/_main.cfg");
208 }
209 
210 static inline bool IsCR(const char& c)
211 {
212  return c == '\x0D';
213 }
214 
215 static std::string strip_cr(std::string str, bool strip)
216 {
217  if(!strip)
218  return str;
219  std::string::iterator new_end = std::remove_if(str.begin(), str.end(), IsCR);
220  str.erase(new_end, str.end());
221  return str;
222 }
223 
224 static filesystem::blacklist_pattern_list read_ignore_patterns(const std::string& addon_name)
225 {
226  const std::string parentd = filesystem::get_addons_dir();
227  const std::string ign_file = parentd + "/" + addon_name + "/_server.ign";
228 
230  LOG_CFG << "searching for .ign file for '" << addon_name << "'...\n";
231  if (!filesystem::file_exists(ign_file)) {
232  LOG_CFG << "no .ign file found for '" << addon_name << "'\n"
233  << "using default ignore patterns...\n";
235  }
236  LOG_CFG << "found .ign file: " << ign_file << '\n';
237  auto stream = filesystem::istream_file(ign_file);
238  std::string line;
239  while (std::getline(*stream, line)) {
240  boost::trim(line);
241  const std::size_t l = line.size();
242  // .gitignore & WML like comments
243  if (l == 0 || !line.compare(0,2,"# ")) continue;
244  if (line[l - 1] == '/') { // directory; we strip the last /
245  patterns.add_directory_pattern(line.substr(0, l - 1));
246  } else { // file
247  patterns.add_file_pattern(line);
248  }
249  }
250  return patterns;
251 }
252 
253 static void archive_file(const std::string& path, const std::string& fname, config& cfg)
254 {
255  cfg["name"] = fname;
256  const bool is_cfg = (fname.size() > 4 ? (fname.substr(fname.size() - 4) == ".cfg") : false);
257  cfg["contents"] = encode_binary(strip_cr(filesystem::read_file(path + '/' + fname),is_cfg));
258 }
259 
260 static void archive_dir(const std::string& path, const std::string& dirname, config& cfg, const filesystem::blacklist_pattern_list& ignore_patterns)
261 {
262  cfg["name"] = dirname;
263  const std::string dir = path + '/' + dirname;
264 
265  std::vector<std::string> files, dirs;
266  filesystem::get_files_in_dir(dir,&files,&dirs);
267  for(const std::string& name : files) {
268  bool valid = !filesystem::looks_like_pbl(name) && !ignore_patterns.match_file(name);
269  if (valid) {
270  archive_file(dir,name,cfg.add_child("file"));
271  }
272  }
273 
274  for(const std::string& name : dirs) {
275  bool valid = !ignore_patterns.match_dir(name);
276  if (valid) {
277  archive_dir(dir,name,cfg.add_child("dir"),ignore_patterns);
278  }
279  }
280 }
281 
282 void archive_addon(const std::string& addon_name, config& cfg)
283 {
284  const std::string parentd = filesystem::get_addons_dir();
285 
286  filesystem::blacklist_pattern_list ignore_patterns(read_ignore_patterns(addon_name));
287  archive_dir(parentd, addon_name, cfg.add_child("dir"), ignore_patterns);
288 }
289 
290 static void unarchive_file(const std::string& path, const config& cfg)
291 {
292  filesystem::write_file(path + '/' + cfg["name"].str(), unencode_binary(cfg["contents"]));
293 }
294 
295 static void unarchive_dir(const std::string& path, const config& cfg)
296 {
297  std::string dir;
298  if (cfg["name"].empty())
299  dir = path;
300  else
301  dir = path + '/' + cfg["name"].str();
302 
304 
305  for(const config &d : cfg.child_range("dir")) {
306  unarchive_dir(dir, d);
307  }
308 
309  for(const config &f : cfg.child_range("file")) {
310  unarchive_file(dir, f);
311  }
312 }
313 
314 void unarchive_addon(const config& cfg)
315 {
316  const std::string parentd = filesystem::get_addons_dir();
317  unarchive_dir(parentd, cfg);
318 }
319 
320 static void purge_dir(const std::string& path, const config& removelist)
321 {
322  std::string dir;
323  if(removelist["name"].empty())
324  dir = path;
325  else
326  dir = path + '/' + removelist["name"].str();
327 
328  if(!filesystem::is_directory(dir)) {
329  return;
330  }
331 
332  for(const config& d : removelist.child_range("dir")) {
333  purge_dir(dir, d);
334  }
335 
336  for(const config& f : removelist.child_range("file")) {
337  filesystem::delete_file(dir + '/' + f["name"].str());
338  }
339 
340  if(filesystem::dir_size(dir) < 1) {
342  }
343 }
344 
345 void purge_addon(const config& removelist)
346 {
347  const std::string parentd = filesystem::get_addons_dir();
348  purge_dir(parentd, removelist);
349 }
350 
351 namespace {
352  std::map< std::string, version_info > version_info_cache;
353 } // end unnamed namespace 5
354 
356 {
357  version_info_cache.clear();
358 
359  LOG_CFG << "refreshing add-on versions cache\n";
360 
361  const std::vector<std::string>& addons = installed_addons();
362  if(addons.empty()) {
363  return;
364  }
365 
366  std::vector<std::string> addon_info_files(addons.size());
367 
368  std::transform(addons.begin(), addons.end(),
369  addon_info_files.begin(), get_info_file_path);
370 
371  for(std::size_t i = 0; i < addon_info_files.size(); ++i) {
372  assert(i < addons.size());
373 
374  const std::string& addon = addons[i];
375  const std::string& info_file = addon_info_files[i];
376 
377  if(filesystem::file_exists(info_file)) {
378  config info_cfg;
379  get_addon_install_info(addon, info_cfg);
380 
381  if(info_cfg.empty()) {
382  continue;
383  }
384 
385  const std::string& version = info_cfg["version"].str();
386  LOG_CFG << "cached add-on version: " << addon << " [" << version << "]\n";
387 
388  version_info_cache[addon] = version;
389  } else if (!have_addon_pbl_info(addon) && !have_addon_in_vcs_tree(addon)) {
390  // Don't print the warning if the user is clearly the author
391  WRN_CFG << "add-on '" << addon << "' has no _info.cfg; cannot read version info" << std::endl;
392  }
393  }
394 }
395 
396 version_info get_addon_version_info(const std::string& addon)
397 {
398  static const version_info nil;
399  std::map< std::string, version_info >::iterator entry = version_info_cache.find(addon);
400  return entry != version_info_cache.end() ? entry->second : nil;
401 }
bool delete_directory(const std::string &dirname, const bool keep_pbl)
Definition: filesystem.cpp:947
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:414
Interfaces for manipulating version numbers of engine, add-ons, etc.
void purge_addon(const config &removelist)
Removes the listed files from the addon.
Definition: manager.cpp:345
bool delete_file(const std::string &filename)
Definition: filesystem.cpp:986
bool looks_like_pbl(const std::string &file)
void add_directory_pattern(const std::string &pattern)
Definition: filesystem.hpp:78
void write_addon_install_info(const std::string &addon_name, const config &cfg)
Definition: manager.cpp:112
ADDON_ENUM_CRITERIA
Definition: manager.cpp:145
logger & info()
Definition: log.cpp:88
Exception thrown when the WML parser fails to read a .pbl file.
Definition: manager.hpp:43
std::string encode_binary(const std::string &str)
Definition: validation.cpp:217
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:262
std::string unencode_binary(const std::string &str)
Definition: validation.cpp:235
bool is_addon_installed(const std::string &addon_name)
Check whether the specified add-on is currently installed.
Definition: manager.cpp:204
child_itors child_range(config_key_type key)
Definition: config.cpp:356
#define ERR_CFG
Definition: manager.cpp:26
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
static bool IsCR(const char &c)
Definition: manager.cpp:210
void get_addon_install_info(const std::string &addon_name, config &cfg)
Gets the installation info (_info.cfg) for an add-on.
Definition: manager.cpp:92
static std::string strip_cr(std::string str, bool strip)
Definition: manager.cpp:215
void clear()
Definition: config.cpp:895
#define d
static lg::log_domain log_network("network")
bool have_addon_in_vcs_tree(const std::string &addon_name)
Returns whether the specified add-on appears to be managed by a VCS or not.
Definition: manager.cpp:53
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
static void purge_dir(const std::string &path, const config &removelist)
Definition: manager.cpp:320
version_info get_addon_version_info(const std::string &addon)
Returns a particular installed add-on&#39;s version information.
Definition: manager.cpp:396
std::vector< std::string > available_addons()
Returns a list of local add-ons that can be published.
Definition: manager.cpp:172
#define LOG_CFG
Definition: manager.cpp:27
bool have_addon_install_info(const std::string &addon_name)
Returns true if there is a local installation info (_info.cfg) file for the add-on.
Definition: manager.cpp:87
static void archive_file(const std::string &path, const std::string &fname, config &cfg)
Definition: manager.cpp:253
void write(std::ostream &out, const configr_of &cfg, unsigned int level)
Definition: parser.cpp:763
bool match_file(const std::string &name) const
static void unarchive_file(const std::string &path, const config &cfg)
Definition: manager.cpp:290
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:626
void write_file(const std::string &fname, const std::string &data)
Throws io_exception if an error occurs.
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:348
void set_addon_pbl_info(const std::string &addon_name, const config &cfg)
Definition: manager.cpp:81
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:38
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:177
std::string path
Definition: game_config.cpp:38
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on&#39;s version information structs.
Definition: manager.cpp:355
bool remove_local_addon(const std::string &addon)
Removes the specified add-on, deleting its full directory structure.
Definition: manager.cpp:129
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
Definition: filesystem.cpp:997
config get_addon_pbl_info(const std::string &addon_name)
Gets the publish information for an add-on.
Definition: manager.cpp:67
void add_file_pattern(const std::string &pattern)
Definition: filesystem.hpp:73
static lg::log_domain log_filesystem("filesystem")
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:39
std::size_t i
Definition: function.cpp:940
int dir_size(const std::string &pname)
Returns the sum of the sizes of the files contained in a directory.
bool have_addon_pbl_info(const std::string &addon_name)
Returns whether a .pbl file is present for the specified add-on or not.
Definition: manager.cpp:62
void archive_addon(const std::string &addon_name, config &cfg)
Archives an add-on into a config object for campaignd transactions.
Definition: manager.cpp:282
static lg::log_domain log_config("config")
bool make_directory(const std::string &dirname)
Definition: filesystem.cpp:936
static filesystem::blacklist_pattern_list read_ignore_patterns(const std::string &addon_name)
Definition: manager.cpp:224
Declarations for File-IO.
static void archive_dir(const std::string &path, const std::string &dirname, config &cfg, const filesystem::blacklist_pattern_list &ignore_patterns)
Definition: manager.cpp:260
Represents version numbers.
config & add_child(config_key_type key)
Definition: config.cpp:500
#define f
#define WRN_CFG
Definition: manager.cpp:28
void unarchive_addon(const config &cfg)
Definition: manager.cpp:314
std::string get_addons_dir()
Standard logging facilities (interface).
std::string message
Definition: exceptions.hpp:29
void trim(std::string_view &s)
#define e
bool match_dir(const std::string &name) const
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:59
mock_char c
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
bool empty() const
Definition: config.cpp:916
static void unarchive_dir(const std::string &path, const config &cfg)
Definition: manager.cpp:295
std::map< std::string, std::string > installed_addons_and_versions()
Retrieves the ids and versions of all installed add-ons.
Definition: manager.cpp:182
static const blacklist_pattern_list default_blacklist
Definition: filesystem.hpp:90