The Battle for Wesnoth  1.13.10+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
3  2008 - 2015 by Ignacio R. Morelle <shadowm2006@gmail.com>
4  Part of the Battle for Wesnoth Project http://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 #include "addon/manager_ui.hpp"
18 #include "filesystem.hpp"
19 #include "formatter.hpp"
20 #include "preferences/game.hpp"
21 #include "gettext.hpp"
25 #include "gui/dialogs/message.hpp"
28 #include "gui/widgets/settings.hpp"
29 #include "gui/widgets/window.hpp"
30 #include "log.hpp"
31 #include "serialization/parser.hpp"
32 #include "version.hpp"
33 #include "formula/string_utils.hpp"
34 #include "addon/client.hpp"
35 
36 #include <boost/algorithm/string.hpp>
37 
38 static lg::log_domain log_config("config");
39 #define ERR_CFG LOG_STREAM(err , log_config)
40 #define LOG_CFG LOG_STREAM(info, log_config)
41 #define WRN_CFG LOG_STREAM(warn, log_config)
42 
43 static lg::log_domain log_filesystem("filesystem");
44 #define ERR_FS LOG_STREAM(err , log_filesystem)
45 
46 static lg::log_domain log_network("network");
47 #define ERR_NET LOG_STREAM(err , log_network)
48 #define LOG_NET LOG_STREAM(info, log_network)
49 
50 namespace {
51  std::string get_pbl_file_path(const std::string& addon_name)
52  {
53  const std::string& parentd = filesystem::get_addons_dir();
54  // Allow .pbl files directly in the addon dir
55  const std::string exterior = parentd + "/" + addon_name + ".pbl";
56  const std::string interior = parentd + "/" + addon_name + "/_server.pbl";
57  return filesystem::file_exists(exterior) ? exterior : interior;
58  }
59 
60  inline std::string get_info_file_path(const std::string& addon_name)
61  {
62  return filesystem::get_addons_dir() + "/" + addon_name + "/_info.cfg";
63  }
64 }
65 
66 bool have_addon_in_vcs_tree(const std::string& addon_name)
67 {
68  static const std::string parentd = filesystem::get_addons_dir();
69  return
70  filesystem::file_exists(parentd+"/"+addon_name+"/.svn") ||
71  filesystem::file_exists(parentd+"/"+addon_name+"/.git") ||
72  filesystem::file_exists(parentd+"/"+addon_name+"/.hg");
73 }
74 
75 bool have_addon_pbl_info(const std::string& addon_name)
76 {
77  return filesystem::file_exists(get_pbl_file_path(addon_name));
78 }
79 
81 {
82  config cfg;
83  const std::string& pbl_path = get_pbl_file_path(addon_name);
84  try {
86  read(cfg, *stream);
87  } catch(const config::error& e) {
88  throw invalid_pbl_exception(pbl_path, e.message);
89  }
90 
91  return cfg;
92 }
93 
94 void set_addon_pbl_info(const std::string& addon_name, const config& cfg)
95 {
96  filesystem::scoped_ostream stream = filesystem::ostream_file(get_pbl_file_path(addon_name));
97  write(*stream, cfg);
98 }
99 
100 bool have_addon_install_info(const std::string& addon_name)
101 {
102  return filesystem::file_exists(get_info_file_path(addon_name));
103 }
104 
105 void get_addon_install_info(const std::string& addon_name, config& cfg)
106 {
107  const std::string& info_path = get_info_file_path(addon_name);
109  try {
110  read(cfg, *stream);
111  } catch(const config::error& e) {
112  ERR_CFG << "Failed to read add-on installation information for '"
113  << addon_name << "' from " << info_path << ":\n"
114  << e.message << std::endl;
115  }
116 }
117 
118 bool remove_local_addon(const std::string& addon)
119 {
120  const std::string addon_dir = filesystem::get_addons_dir() + "/" + addon;
121 
122  LOG_CFG << "removing local add-on: " << addon << '\n';
123 
124  if(filesystem::file_exists(addon_dir) && !filesystem::delete_directory(addon_dir, true)) {
125  ERR_CFG << "Failed to delete directory/file: " << addon_dir << '\n';
126  ERR_CFG << "removal of add-on " << addon << " failed!" << std::endl;
127  return false;
128  }
129  return true;
130 }
131 
132 std::vector<std::string> available_addons()
133 {
134  std::vector<std::string> res;
135  std::vector<std::string> files, dirs;
136  const std::string parentd = filesystem::get_addons_dir();
137  filesystem::get_files_in_dir(parentd,&files,&dirs);
138 
139  for(std::vector<std::string>::const_iterator i = dirs.begin(); i != dirs.end(); ++i) {
140  if (filesystem::file_exists(parentd + "/" + *i + "/_main.cfg") && have_addon_pbl_info(*i)) {
141  res.push_back(*i);
142  }
143  }
144 
145  return res;
146 }
147 
148 std::vector<std::string> installed_addons()
149 {
150  std::vector<std::string> res;
151  const std::string parentd = filesystem::get_addons_dir();
152  std::vector<std::string> files, dirs;
153  filesystem::get_files_in_dir(parentd,&files,&dirs);
154 
155  for(std::vector<std::string>::const_iterator i = dirs.begin(); i != dirs.end(); ++i) {
156  if(filesystem::file_exists(parentd + "/" + *i + "/_main.cfg")) {
157  res.push_back(*i);
158  }
159  }
160 
161  return res;
162 }
163 
164 bool is_addon_installed(const std::string& addon_name)
165 {
166  const std::string namestem = filesystem::get_addons_dir() + "/" + addon_name;
167  return filesystem::file_exists(namestem + "/_main.cfg");
168 }
169 
170 static inline bool IsCR(const char& c)
171 {
172  return c == '\x0D';
173 }
174 
175 static std::string strip_cr(std::string str, bool strip)
176 {
177  if(!strip)
178  return str;
179  std::string::iterator new_end = std::remove_if(str.begin(), str.end(), IsCR);
180  str.erase(new_end, str.end());
181  return str;
182 }
183 
184 namespace {
185  void append_default_ignore_patterns(std::pair<std::vector<std::string>, std::vector<std::string> >& patterns)
186  {
187  std::vector<std::string>& files = patterns.first;
188  std::vector<std::string>& dirs = patterns.second;
189 
190  static const std::vector<std::string> default_ignored_files {
191  /* Don't upload dot-files/dirs, which are hidden files in UNIX platforms */
192  ".*",
193  "#*#",
194  "*~",
195  "*-bak",
196  "*.swp",
197  "*.pbl",
198  "*.ign",
199  "_info.cfg",
200  "*.exe",
201  "*.bat",
202  "*.cmd",
203  "*.com",
204  "*.scr",
205  "*.sh",
206  "*.js",
207  "*.vbs",
208  "*.o",
209  /* Remove junk created by certain file manager ;) */
210  "Thumbs.db",
211  /* Eclipse plugin */
212  "*.wesnoth",
213  "*.project",
214  };
215 
216  static const std::vector<std::string> default_ignored_dirs {
217  ".*",
218  /* macOS metadata-like cruft (http://floatingsun.net/2007/02/07/whats-with-__macosx-in-zip-files/) */
219  "__MACOSX",
220  };
221 
222  files.insert(files.end(), default_ignored_files.begin(), default_ignored_files.end());
223  dirs.insert(dirs.end(), default_ignored_dirs.begin(), default_ignored_dirs.end());
224  }
225 }
226 
227 static std::pair<std::vector<std::string>, std::vector<std::string> > read_ignore_patterns(const std::string& addon_name)
228 {
229  const std::string parentd = filesystem::get_addons_dir();
230  const std::string ign_file = parentd + "/" + addon_name + "/_server.ign";
231 
232  std::pair<std::vector<std::string>, std::vector<std::string> > patterns;
233  LOG_CFG << "searching for .ign file for '" << addon_name << "'...\n";
234  if (!filesystem::file_exists(ign_file)) {
235  LOG_CFG << "no .ign file found for '" << addon_name << "'\n"
236  << "inserting default ignore patterns...\n";
237  append_default_ignore_patterns(patterns);
238  return patterns; // just default patterns
239  }
240  LOG_CFG << "found .ign file: " << ign_file << '\n';
241  auto stream = filesystem::istream_file(ign_file);
242  std::string line;
243  while (std::getline(*stream, line)) {
244  boost::trim(line);
245  const size_t l = line.size();
246  // .gitignore & WML like comments
247  if (l == 0 || !line.compare(0,2,"# ")) continue;
248  if (line[l - 1] == '/') { // directory; we strip the last /
249  patterns.second.push_back(line.substr(0, l - 1));
250  } else { // file
251  patterns.first.push_back(line);
252  }
253  }
254  return patterns;
255 }
256 
257 static void archive_file(const std::string& path, const std::string& fname, config& cfg)
258 {
259  cfg["name"] = fname;
260  const bool is_cfg = (fname.size() > 4 ? (fname.substr(fname.size() - 4) == ".cfg") : false);
261  cfg["contents"] = encode_binary(strip_cr(filesystem::read_file(path + '/' + fname),is_cfg));
262 }
263 
264 static void archive_dir(const std::string& path, const std::string& dirname, config& cfg, std::pair<std::vector<std::string>, std::vector<std::string> >& ignore_patterns)
265 {
266  cfg["name"] = dirname;
267  const std::string dir = path + '/' + dirname;
268 
269  std::vector<std::string> files, dirs;
270  filesystem::get_files_in_dir(dir,&files,&dirs);
271  for(std::vector<std::string>::const_iterator i = files.begin(); i != files.end(); ++i) {
272  bool valid = !filesystem::looks_like_pbl(*i);
273  for(std::vector<std::string>::const_iterator p = ignore_patterns.first.begin(); p != ignore_patterns.first.end(); ++p) {
274  if (utils::wildcard_string_match(*i, *p)) {
275  valid = false;
276  break;
277  }
278  }
279  if (valid) {
280  archive_file(dir,*i,cfg.add_child("file"));
281  }
282  }
283 
284  for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
285  bool valid = true;
286  for(std::vector<std::string>::const_iterator p = ignore_patterns.second.begin(); p != ignore_patterns.second.end(); ++p) {
287  if (utils::wildcard_string_match(*j, *p)) {
288  valid = false;
289  break;
290  }
291  }
292  if (valid) {
293  archive_dir(dir,*j,cfg.add_child("dir"),ignore_patterns);
294  }
295  }
296 }
297 
298 void archive_addon(const std::string& addon_name, config& cfg)
299 {
300  const std::string parentd = filesystem::get_addons_dir();
301 
302  std::pair<std::vector<std::string>, std::vector<std::string> > ignore_patterns;
303  ignore_patterns = read_ignore_patterns(addon_name);
304  archive_dir(parentd, addon_name, cfg.add_child("dir"), ignore_patterns);
305 }
306 
307 static void unarchive_file(const std::string& path, const config& cfg)
308 {
309  filesystem::write_file(path + '/' + cfg["name"].str(), unencode_binary(cfg["contents"]));
310 }
311 
312 static void unarchive_dir(const std::string& path, const config& cfg)
313 {
314  std::string dir;
315  if (cfg["name"].empty())
316  dir = path;
317  else
318  dir = path + '/' + cfg["name"].str();
319 
321 
322  for(const config &d : cfg.child_range("dir")) {
323  unarchive_dir(dir, d);
324  }
325 
326  for(const config &f : cfg.child_range("file")) {
327  unarchive_file(dir, f);
328  }
329 }
330 
331 void unarchive_addon(const config& cfg)
332 {
333  const std::string parentd = filesystem::get_addons_dir();
334  unarchive_dir(parentd, cfg);
335 }
336 
337 namespace {
338  std::map< std::string, version_info > version_info_cache;
339 } // end unnamed namespace 5
340 
342 {
343  version_info_cache.clear();
344 
345  LOG_CFG << "refreshing add-on versions cache\n";
346 
347  const std::vector<std::string>& addons = installed_addons();
348  if(addons.empty()) {
349  return;
350  }
351 
352  std::vector<std::string> addon_info_files(addons.size());
353 
354  std::transform(addons.begin(), addons.end(),
355  addon_info_files.begin(), get_info_file_path);
356 
357  for(size_t i = 0; i < addon_info_files.size(); ++i) {
358  assert(i < addons.size());
359 
360  const std::string& addon = addons[i];
361  const std::string& info_file = addon_info_files[i];
362 
363  if(filesystem::file_exists(info_file)) {
364  config cfg;
365  get_addon_install_info(addon, cfg);
366 
367  const config& info_cfg = cfg.child("info");
368  if(!info_cfg) {
369  continue;
370  }
371 
372  const std::string& version = info_cfg["version"].str();
373  LOG_CFG << "cached add-on version: " << addon << " [" << version << "]\n";
374 
375  version_info_cache[addon] = version;
376  } else if (!have_addon_pbl_info(addon) && !have_addon_in_vcs_tree(addon)) {
377  // Don't print the warning if the user is clearly the author
378  WRN_CFG << "add-on '" << addon << "' has no _info.cfg; cannot read version info" << std::endl;
379  }
380  }
381 }
382 
384 {
385  static const version_info nil;
386  std::map< std::string, version_info >::iterator entry = version_info_cache.find(addon);
387  return entry != version_info_cache.end() ? entry->second : nil;
388 }
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:352
std::vector< char_t > string
bool looks_like_pbl(const std::string &file)
Exception thrown when the WML parser fails to read a .pbl file.
Definition: manager.hpp:30
std::string encode_binary(const std::string &str)
Definition: validation.cpp:249
std::string unencode_binary(const std::string &str)
Definition: validation.cpp:267
This file contains the window object, this object is a top level container which has the event manage...
bool is_addon_installed(const std::string &addon_name)
Check whether the specified add-on is currently installed.
Definition: manager.cpp:164
child_itors child_range(config_key_type key)
Definition: config.cpp:295
#define ERR_CFG
Definition: manager.cpp:39
static bool IsCR(const char &c)
Definition: manager.cpp:170
bool wildcard_string_match(const std::string &str, const std::string &match)
Match using '*' as any number of characters (including none), and '?' as any one character.
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:105
static std::string strip_cr(std::string str, bool strip)
Definition: manager.cpp:175
#define d
static lg::log_domain log_network("network")
bool have_addon_in_vcs_tree(const std::string &addon_name)
Returns true if the specified add-ons appear to be managed by a 'supported' VCS.
Definition: manager.cpp:66
version_info get_addon_version_info(const std::string &addon)
Returns a particular installed add-on's version information.
Definition: manager.cpp:383
std::vector< std::string > available_addons()
Returns a list of local add-ons that can be published.
Definition: manager.cpp:132
#define LOG_CFG
Definition: manager.cpp:40
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:100
static void archive_file(const std::string &path, const std::string &fname, config &cfg)
Definition: manager.cpp:257
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error=true)
static void unarchive_file(const std::string &path, const config &cfg)
Definition: manager.cpp:307
This file contains the settings handling of the widget library.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:612
void write_file(const std::string &fname, const std::string &data)
Throws io_exception if an error occurs.
void set_addon_pbl_info(const std::string &addon_name, const config &cfg)
Definition: manager.cpp:94
std::unique_ptr< std::istream > scoped_istream
Definition: filesystem.hpp:37
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:148
std::string path
Definition: game_config.cpp:56
void refresh_addon_version_info_cache()
Refreshes the per-session cache of add-on's version information structs.
Definition: manager.cpp:341
bool remove_local_addon(const std::string &addon)
Definition: manager.cpp:118
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
config get_addon_pbl_info(const std::string &addon_name)
Gets the publish information for an add-on.
Definition: manager.cpp:80
static lg::log_domain log_filesystem("filesystem")
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:38
filesystem::scoped_ostream ostream_file(const std::string &fname, bool create_directory=true)
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, file_name_option mode=FILE_NAME_ONLY, file_filter_option filter=NO_FILTER, file_reorder_option reorder=DONT_REORDER, file_tree_checksum *checksum=nullptr)
Populates 'files' with all the files and 'dirs' with all the directories in dir.
bool have_addon_pbl_info(const std::string &addon_name)
Returns true if there's a local .pbl file stored for the specified add-on.
Definition: manager.cpp:75
mock_party p
void archive_addon(const std::string &addon_name, config &cfg)
Archives an add-on into a config object for campaignd transactions.
Definition: manager.cpp:298
static lg::log_domain log_config("config")
bool make_directory(const std::string &dirname)
#define i
Declarations for File-IO.
Represents version numbers.
Definition: version.hpp:43
bool delete_directory(const std::string &dirname, const bool keep_pbl=false)
config & add_child(config_key_type key)
Definition: config.cpp:408
#define f
#define WRN_CFG
Definition: manager.cpp:41
void write(std::ostream &out, configr_of const &cfg, unsigned int level)
Definition: parser.cpp:749
void unarchive_addon(const config &cfg)
Definition: manager.cpp:331
std::string get_addons_dir()
Standard logging facilities (interface).
std::string message
Definition: exceptions.hpp:31
#define e
static std::pair< std::vector< std::string >, std::vector< std::string > > read_ignore_patterns(const std::string &addon_name)
Definition: manager.cpp:227
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:93
mock_char c
Interfaces for manipulating version numbers of engine, add-ons, etc.
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
const std::string version
Definition: game_config.cpp:39
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
static void unarchive_dir(const std::string &path, const config &cfg)
Definition: manager.cpp:312
static void archive_dir(const std::string &path, const std::string &dirname, config &cfg, std::pair< std::vector< std::string >, std::vector< std::string > > &ignore_patterns)
Definition: manager.cpp:264