addon/manager.cpp

Go to the documentation of this file.
00001 /* $Id: manager.cpp 53411 2012-03-03 19:33:27Z shadowmaster $ */
00002 /*
00003    Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004                  2008 - 2012 by Ignacio R. Morelle <shadowm2006@gmail.com>
00005    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00006 
00007    This program is free software; you can redistribute it and/or modify
00008    it under the terms of the GNU General Public License as published by
00009    the Free Software Foundation; either version 2 of the License, or
00010    (at your option) any later version.
00011    This program is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY.
00013 
00014    See the COPYING file for more details.
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         // Cope with old-style or new-style file organization
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         // Continue if there is a dir (which we already processed).
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         /* Don't upload dot-files/dirs, which are hidden files in
00204            UNIX platforms */
00205         files.push_back(".*");
00206         dirs.push_back(".*");
00207         /* MacOS X metadata-like cruft (http://floatingsun.net/2007/02/07/whats-with-__macosx-in-zip-files/) */
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         /* Remove junk created by certain file manager ;) */
00227         files.push_back("Thumbs.db");
00228         /* Eclipse plugin */
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; // just default 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] == '/') { // directory; we strip the last /
00260             patterns.second.push_back(line.substr(0, l - 1));
00261         } else { // file
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     // External .cfg may not exist; newer campaigns have a _main.cfg
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 } // end unnamed namespace 5
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             // Don't print the warning if the user is clearly the author
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines

Generated by doxygen 1.7.1 on Tue May 22 2012 01:03:34 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs