00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017 #include "global.hpp"
00018
00019 #include "addon/manager.hpp"
00020 #include "addon/manager_ui.hpp"
00021 #include "dialogs.hpp"
00022 #include "filesystem.hpp"
00023 #include "foreach.hpp"
00024 #include "formatter.hpp"
00025 #include "game_display.hpp"
00026 #include "game_preferences.hpp"
00027 #include "gettext.hpp"
00028 #include "gui/dialogs/addon_connect.hpp"
00029 #include "gui/dialogs/addon_list.hpp"
00030 #include "gui/dialogs/addon/description.hpp"
00031 #include "gui/dialogs/addon/uninstall_list.hpp"
00032 #include "gui/dialogs/message.hpp"
00033 #include "gui/dialogs/network_transmission.hpp"
00034 #include "gui/dialogs/simple_item_selector.hpp"
00035 #include "gui/dialogs/transient_message.hpp"
00036 #include "gui/widgets/settings.hpp"
00037 #include "gui/widgets/window.hpp"
00038 #include "log.hpp"
00039 #include "marked-up_text.hpp"
00040 #include "serialization/parser.hpp"
00041 #include "version.hpp"
00042 #include "wml_separators.hpp"
00043 #include "formula_string_utils.hpp"
00044 #include "addon/client.hpp"
00045
00046 static lg::log_domain log_config("config");
00047 #define ERR_CFG LOG_STREAM(err , log_config)
00048 #define LOG_CFG LOG_STREAM(info, log_config)
00049 #define WRN_CFG LOG_STREAM(warn, log_config)
00050
00051 static lg::log_domain log_filesystem("filesystem");
00052 #define ERR_FS LOG_STREAM(err , log_filesystem)
00053
00054 static lg::log_domain log_network("network");
00055 #define ERR_NET LOG_STREAM(err , log_network)
00056 #define LOG_NET LOG_STREAM(info, log_network)
00057
00058 namespace {
00059 std::string get_pbl_file_path(const std::string& addon_name)
00060 {
00061 const std::string& parentd = get_addon_campaigns_dir();
00062
00063 const std::string exterior = parentd + "/" + addon_name + ".pbl";
00064 const std::string interior = parentd + "/" + addon_name + "/_server.pbl";
00065 return file_exists(exterior) ? exterior : interior;
00066 }
00067
00068 inline std::string get_info_file_path(const std::string& addon_name)
00069 {
00070 return get_addon_campaigns_dir() + "/" + addon_name + "/_info.cfg";
00071 }
00072 }
00073
00074 bool have_addon_in_vcs_tree(const std::string& addon_name)
00075 {
00076 static const std::string parentd = get_addon_campaigns_dir();
00077 return
00078 file_exists(parentd+"/"+addon_name+"/.svn") ||
00079 file_exists(parentd+"/"+addon_name+"/.git") ||
00080 file_exists(parentd+"/"+addon_name+"/.hg");
00081 }
00082
00083 bool have_addon_pbl_info(const std::string& addon_name)
00084 {
00085 static const std::string parentd = get_addon_campaigns_dir();
00086 return
00087 file_exists(parentd+"/"+addon_name+".pbl") ||
00088 file_exists(parentd+"/"+addon_name+"/_server.pbl");
00089 }
00090
00091 void get_addon_pbl_info(const std::string& addon_name, config& cfg)
00092 {
00093 scoped_istream stream = istream_file(get_pbl_file_path(addon_name));
00094 read(cfg, *stream);
00095 }
00096
00097 void set_addon_pbl_info(const std::string& addon_name, const config& cfg)
00098 {
00099 scoped_ostream stream = ostream_file(get_pbl_file_path(addon_name));
00100 write(*stream, cfg);
00101 }
00102
00103 bool remove_local_addon(const std::string& addon)
00104 {
00105 bool ret = true;
00106 const std::string addon_dir = get_addon_campaigns_dir() + "/" + addon;
00107
00108 LOG_CFG << "removing local add-on: " << addon << '\n';
00109
00110 if(file_exists(addon_dir) && !delete_directory(addon_dir, true)) {
00111 ERR_CFG << "Failed to delete directory/file: " << addon_dir << '\n';
00112 ret = false;
00113 }
00114
00115 if(file_exists(addon_dir + ".cfg") && !delete_directory(addon_dir + ".cfg", true)) {
00116 ERR_CFG << "Failed to delete directory/file: " << addon_dir << ".cfg\n";
00117 ret = false;
00118 }
00119
00120 if(!ret) {
00121 ERR_CFG << "removal of add-on " << addon << " failed!\n";
00122 }
00123
00124 return ret;
00125 }
00126
00127 std::vector<std::string> available_addons()
00128 {
00129 std::vector<std::string> res;
00130 std::vector<std::string> files, dirs;
00131 const std::string parentd = get_addon_campaigns_dir();
00132 get_files_in_dir(parentd,&files,&dirs);
00133
00134 for(std::vector<std::string>::const_iterator i = dirs.begin(); i != dirs.end(); ++i) {
00135 const std::string external_cfg_file = *i + ".cfg";
00136 const std::string internal_cfg_file = *i + "/_main.cfg";
00137 const std::string external_pbl_file = *i + ".pbl";
00138 const std::string internal_pbl_file = *i + "/_server.pbl";
00139 if((std::find(files.begin(),files.end(),external_cfg_file) != files.end() || file_exists(parentd + "/" + internal_cfg_file)) &&
00140 (std::find(files.begin(),files.end(),external_pbl_file) != files.end() || (file_exists(parentd + "/" + internal_pbl_file)))) {
00141 res.push_back(*i);
00142 }
00143 }
00144 for(std::vector<std::string>::const_iterator i = files.begin(); i != files.end(); ++i) {
00145 const size_t length = i->size() - 4;
00146 if (i->rfind(".cfg", length) != length) continue;
00147 const std::string name = i->substr(0, length);
00148
00149 if (std::find(dirs.begin(), dirs.end(), name) != dirs.end()) continue;
00150 if (std::find(files.begin(), files.end(), name + ".pbl") != files.end()) {
00151 res.push_back(name);
00152 }
00153 }
00154
00155 return res;
00156 }
00157
00158 std::vector<std::string> installed_addons()
00159 {
00160 std::vector<std::string> res;
00161 const std::string parentd = get_addon_campaigns_dir();
00162 std::vector<std::string> files, dirs;
00163 get_files_in_dir(parentd,&files,&dirs);
00164
00165 for(std::vector<std::string>::const_iterator i = dirs.begin(); i != dirs.end(); ++i) {
00166 const std::string external_cfg_file = *i + ".cfg";
00167 const std::string internal_cfg_file = *i + "/_main.cfg";
00168 if(std::find(files.begin(),files.end(),external_cfg_file) != files.end() || file_exists(parentd + "/" + internal_cfg_file)) {
00169 res.push_back(*i);
00170 }
00171 }
00172
00173 return res;
00174 }
00175
00176 bool is_addon_installed(const std::string& addon_name)
00177 {
00178 const std::string namestem = get_addon_campaigns_dir() + "/" + addon_name;
00179
00180 return file_exists(namestem + ".cfg") || file_exists(namestem + "/_main.cfg");
00181 }
00182
00183 static inline bool IsCR(const char& c)
00184 {
00185 return c == '\x0D';
00186 }
00187
00188 static std::string strip_cr(std::string str, bool strip)
00189 {
00190 if(!strip)
00191 return str;
00192 std::string::iterator new_end = std::remove_if(str.begin(), str.end(), IsCR);
00193 str.erase(new_end, str.end());
00194 return str;
00195 }
00196
00197 namespace {
00198 void append_default_ignore_patterns(std::pair<std::vector<std::string>, std::vector<std::string> >& patterns)
00199 {
00200 std::vector<std::string>& files = patterns.first;
00201 std::vector<std::string>& dirs = patterns.second;
00202
00203
00204
00205 files.push_back(".*");
00206 dirs.push_back(".*");
00207
00208 dirs.push_back("__MACOSX");
00209
00210 files.push_back("#*#");
00211 files.push_back("*~");
00212 files.push_back("*-bak");
00213 files.push_back("*.swp");
00214 files.push_back("*.pbl");
00215 files.push_back("*.ign");
00216 files.push_back("_info.cfg");
00217 files.push_back("*.exe");
00218 files.push_back("*.bat");
00219 files.push_back("*.cmd");
00220 files.push_back("*.com");
00221 files.push_back("*.scr");
00222 files.push_back("*.sh");
00223 files.push_back("*.js");
00224 files.push_back("*.vbs");
00225 files.push_back("*.o");
00226
00227 files.push_back("Thumbs.db");
00228
00229 files.push_back("*.wesnoth");
00230 files.push_back("*.project");
00231 }
00232 }
00233
00234 static std::pair<std::vector<std::string>, std::vector<std::string> > read_ignore_patterns(const std::string& addon_name)
00235 {
00236 const std::string parentd = get_addon_campaigns_dir();
00237 const std::string exterior = parentd + "/" + addon_name + ".ign";
00238 const std::string interior = parentd + "/" + addon_name + "/_server.ign";
00239
00240 std::pair<std::vector<std::string>, std::vector<std::string> > patterns;
00241 std::string ign_file;
00242 LOG_CFG << "searching for .ign file for '" << addon_name << "'...\n";
00243 if (file_exists(interior)) {
00244 ign_file = interior;
00245 } else if (file_exists(exterior)) {
00246 ign_file = exterior;
00247 } else {
00248 LOG_CFG << "no .ign file found for '" << addon_name << "'\n"
00249 << "inserting default ignore patterns...\n";
00250 append_default_ignore_patterns(patterns);
00251 return patterns;
00252 }
00253 LOG_CFG << "found .ign file: " << ign_file << '\n';
00254 std::istream *stream = istream_file(ign_file);
00255 std::string line;
00256 while (std::getline(*stream, line)) {
00257 utils::strip(line);
00258 const size_t l = line.size();
00259 if (line[l - 1] == '/') {
00260 patterns.second.push_back(line.substr(0, l - 1));
00261 } else {
00262 patterns.first.push_back(line);
00263 }
00264 }
00265 return patterns;
00266 }
00267
00268 static void archive_file(const std::string& path, const std::string& fname, config& cfg)
00269 {
00270 cfg["name"] = fname;
00271 const bool is_cfg = (fname.size() > 4 ? (fname.substr(fname.size() - 4) == ".cfg") : false);
00272 cfg["contents"] = encode_binary(strip_cr(read_file(path + '/' + fname),is_cfg));
00273 }
00274
00275 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)
00276 {
00277 cfg["name"] = dirname;
00278 const std::string dir = path + '/' + dirname;
00279
00280 std::vector<std::string> files, dirs;
00281 get_files_in_dir(dir,&files,&dirs);
00282 for(std::vector<std::string>::const_iterator i = files.begin(); i != files.end(); ++i) {
00283 bool valid = !looks_like_pbl(*i);
00284 for(std::vector<std::string>::const_iterator p = ignore_patterns.first.begin(); p != ignore_patterns.first.end(); ++p) {
00285 if (utils::wildcard_string_match(*i, *p)) {
00286 valid = false;
00287 break;
00288 }
00289 }
00290 if (valid) {
00291 archive_file(dir,*i,cfg.add_child("file"));
00292 }
00293 }
00294
00295 for(std::vector<std::string>::const_iterator j = dirs.begin(); j != dirs.end(); ++j) {
00296 bool valid = true;
00297 for(std::vector<std::string>::const_iterator p = ignore_patterns.second.begin(); p != ignore_patterns.second.end(); ++p) {
00298 if (utils::wildcard_string_match(*j, *p)) {
00299 valid = false;
00300 break;
00301 }
00302 }
00303 if (valid) {
00304 archive_dir(dir,*j,cfg.add_child("dir"),ignore_patterns);
00305 }
00306 }
00307 }
00308
00309 void archive_addon(const std::string& addon_name, config& cfg)
00310 {
00311 const std::string parentd = get_addon_campaigns_dir();
00312
00313 std::pair<std::vector<std::string>, std::vector<std::string> > ignore_patterns;
00314
00315 const std::string external_cfg = addon_name + ".cfg";
00316 if (file_exists(parentd + "/" + external_cfg)) {
00317 archive_file(parentd, external_cfg, cfg.add_child("file"));
00318 }
00319 ignore_patterns = read_ignore_patterns(addon_name);
00320 archive_dir(parentd, addon_name, cfg.add_child("dir"), ignore_patterns);
00321 }
00322
00323 static void unarchive_file(const std::string& path, const config& cfg)
00324 {
00325 write_file(path + '/' + cfg["name"].str(), unencode_binary(cfg["contents"]));
00326 }
00327
00328 static void unarchive_dir(const std::string& path, const config& cfg)
00329 {
00330 std::string dir;
00331 if (cfg["name"].empty())
00332 dir = path;
00333 else
00334 dir = path + '/' + cfg["name"].str();
00335
00336 make_directory(dir);
00337
00338 foreach (const config &d, cfg.child_range("dir")) {
00339 unarchive_dir(dir, d);
00340 }
00341
00342 foreach (const config &f, cfg.child_range("file")) {
00343 unarchive_file(dir, f);
00344 }
00345 }
00346
00347 void unarchive_addon(const config& cfg)
00348 {
00349 const std::string parentd = get_addon_campaigns_dir();
00350 unarchive_dir(parentd, cfg);
00351 }
00352
00353 namespace {
00354 std::map< std::string, version_info > version_info_cache;
00355 }
00356
00357 void refresh_addon_version_info_cache()
00358 {
00359 version_info_cache.clear();
00360
00361 LOG_CFG << "refreshing add-on versions cache\n";
00362
00363 const std::vector<std::string>& addons = installed_addons();
00364 if(addons.empty()) {
00365 return;
00366 }
00367
00368 std::vector<std::string> addon_info_files(addons.size());
00369
00370 std::transform(addons.begin(), addons.end(),
00371 addon_info_files.begin(), get_info_file_path);
00372
00373 for(size_t i = 0; i < addon_info_files.size(); ++i) {
00374 assert(i < addons.size());
00375
00376 const std::string& addon = addons[i];
00377 const std::string& info_file = addon_info_files[i];
00378
00379 if(file_exists(info_file)) {
00380 scoped_istream stream = istream_file(info_file);
00381
00382 config cfg;
00383 read(cfg, *stream);
00384
00385 const config& info_cfg = cfg.child("info");
00386 if(!info_cfg) {
00387 continue;
00388 }
00389
00390 const std::string& version = info_cfg["version"].str();
00391 LOG_CFG << "cached add-on version: " << addon << " [" << version << "]\n";
00392
00393 version_info_cache[addon] = version;
00394 } else if (!have_addon_pbl_info(addon) && !have_addon_in_vcs_tree(addon)) {
00395
00396 WRN_CFG << "add-on '" << addon << "' has no _info.cfg; cannot read version info\n";
00397 }
00398 }
00399 }
00400
00401 version_info get_addon_version_info(const std::string& addon)
00402 {
00403 static const version_info nil(0,0,0,false);
00404 std::map< std::string, version_info >::iterator entry = version_info_cache.find(addon);
00405 return entry != version_info_cache.end() ? entry->second : nil;
00406 }