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