upload_log.cpp

Go to the documentation of this file.
00001 /* $Id: upload_log.cpp 26758 2008-05-21 18:34:21Z mordante $ */
00002 /*
00003    Copyright (C) 2005 - 2008 by Rusty Russell <rusty@rustcorp.com.au>
00004    Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006    This program is free software; you can redistribute it and/or modify
00007    it under the terms of the GNU General Public License version 2
00008    or at your option any later version.
00009    This program is distributed in the hope that it will be useful,
00010    but WITHOUT ANY WARRANTY.
00011 
00012    See the COPYING file for more details.
00013 */
00014 
00015 /**
00016  *  @file upload_log.cpp
00017  *  Manage logfiles for uploading as feedback, e.g.\ for champaign-balancing.
00018  */
00019 
00020 #include "global.hpp"
00021 
00022 #define GETTEXT_DOMAIN "wesnoth"
00023 
00024 #include "config.hpp"
00025 #include "construct_dialog.hpp"
00026 #include "display.hpp"
00027 #include "game_config.hpp"
00028 #include "game_preferences.hpp"
00029 #include "gamestatus.hpp"
00030 #include "gettext.hpp"
00031 #include "filesystem.hpp"
00032 #include "serialization/parser.hpp"
00033 #include "team.hpp"
00034 #include "tstring.hpp"
00035 #include "upload_log.hpp"
00036 #include "wesconfig.h"
00037 #include "wml_separators.hpp"
00038 
00039 #include "SDL_net.h"
00040 
00041 #include <vector>
00042 #include <string>
00043 
00044 #define TARGET_HOST "stats.wesnoth.org"
00045 #define TARGET_URL "/upload.cgi"
00046 #define TARGET_PORT 80
00047 
00048 struct upload_log::thread_info upload_log::thread_;
00049 upload_log::manager* upload_log::manager_ = 0;
00050 
00051 // On exit, kill the upload thread if it's still going.
00052 upload_log::manager::~manager()
00053 {
00054     upload_log::manager_ = 0;
00055     threading::thread *t = thread_.t;
00056     if (t)
00057         t->join();
00058 }
00059 
00060 void upload_log::manager::manage()
00061 {
00062 
00063     // We have to wait for thread so it won't leak
00064     if (thread_.shutdown)
00065     {
00066         thread_.t->join();
00067         thread_.shutdown = false;
00068         thread_.t = 0;
00069     }
00070 }
00071 
00072 static void send_string(TCPsocket sock, const std::string &str)
00073 {
00074     if (SDLNet_TCP_Send(sock, const_cast<void*>(static_cast<const void*>(
00075             str.c_str())), str.length()) != static_cast<int>(str.length())) {
00076 
00077         throw network::error("");
00078     }
00079 }
00080 
00081 // Function which runs in a background thread to upload logs to server.
00082 // Uses http POST to port 80 for maximum firewall penetration & other-end
00083 // compatibility.
00084 static int upload_logs(void *_ti)
00085 {
00086     TCPsocket sock = NULL;
00087     upload_log::thread_info *ti = static_cast<upload_log::thread_info*>(_ti);
00088 
00089     const char *header =
00090         "POST " TARGET_URL " HTTP/1.1\n"
00091         "Host: " TARGET_HOST "\n"
00092         "User-Agent: Wesnoth " VERSION "\n"
00093         "Content-Type: text/plain\n";
00094 
00095     try {
00096         std::vector<std::string> files;
00097 
00098         // These are sorted: send them one at a time until we get to lastfile.
00099         get_files_in_dir(get_upload_dir(), &files, NULL, ENTIRE_FILE_PATH);
00100 
00101         IPaddress ip;
00102         if (SDLNet_ResolveHost(&ip, TARGET_HOST, TARGET_PORT) == 0) {
00103             std::vector<std::string>::iterator i;
00104             for (i = files.begin(); i!=files.end() && *i!=ti->lastfile; i++) {
00105                 std::string contents;
00106                 char response[10]; //This needs to be strlen("HTTP/1.1 2");
00107 
00108                 contents = read_file(*i);
00109 
00110                 sock = SDLNet_TCP_Open(&ip);
00111                 if (!sock)
00112                     break;
00113                 send_string(sock, header);
00114                 send_string(sock, "Content-length: ");
00115                 send_string(sock, lexical_cast<std::string>(contents.length()));
00116                 send_string(sock, "\n\n");
00117                 send_string(sock, contents.c_str());
00118 
00119                 if (SDLNet_TCP_Recv(sock, response, sizeof(response))
00120                     != sizeof(response))
00121                     break;
00122                 // Must be version 1.x, must start with 2 (eg. 200) for success
00123                 if (memcmp(response, "HTTP/1.", strlen("HTTP/1.")) != 0)
00124                     break;
00125                 if (memcmp(response+8, " 2", strlen(" 2")) != 0)
00126                     break;
00127 
00128                 delete_directory(*i);
00129                 SDLNet_TCP_Close(sock);
00130                 sock = NULL;
00131             }
00132         }
00133     } catch(...) { }
00134 
00135     if (sock)
00136         SDLNet_TCP_Close(sock);
00137     ti->shutdown = true;
00138     return 0;
00139 }
00140 
00141 
00142 // Currently only enabled when playing campaigns.
00143 upload_log::upload_log(bool enable) : game_(NULL), enabled_(enable)
00144 {
00145     filename_ = next_filename(get_upload_dir(), 100);
00146     if (upload_log::manager_)
00147         upload_log::manager_->manage();
00148     if (preferences::upload_log() && !thread_.t) {
00149         // Thread can outlive us; it uploads everything up to the
00150         // next filename, and unsets thread_.t when it's finished.
00151         thread_.lastfile = filename_;
00152         thread_.t = new threading::thread(upload_logs, &thread_);
00153     }
00154 }
00155 
00156 upload_log::~upload_log()
00157 {
00158     // If last game has a conclusion, add it.
00159     if (game_finished(game_))
00160         config_.add_child("game", *game_);
00161 
00162     if (game_)
00163         delete game_;
00164 
00165     if (enabled_ && !config_.empty() && !game_config::debug) {
00166         config_["version"] = VERSION;
00167         config_["format_version"] = "1";
00168         config_["id"] = preferences::upload_id();
00169         config_["serial"] = lexical_cast<std::string>(time(NULL)) + file_name(filename_);
00170         std::ostream *out = ostream_file(filename_);
00171         write(*out, config_);
00172         delete out;
00173 
00174         if (upload_log::manager_)
00175             upload_log::manager_->manage();
00176 
00177         // Try to upload latest log before exit.
00178         if (preferences::upload_log() && !thread_.t) {
00179             thread_.lastfile = next_filename(get_upload_dir(), 1000);
00180             thread_.t = new threading::thread(upload_logs, &thread_);
00181         }
00182     }
00183 }
00184 
00185 bool upload_log::game_finished(config *game)
00186 {
00187     if (!game)
00188         return false;
00189 
00190     return game->child("victory") || game->child("defeat") || game->child("quit");
00191 }
00192 
00193 config &upload_log::add_game_result(const std::string &str, int turn)
00194 {
00195     config &child = game_->add_child(str);
00196     child["time"] = lexical_cast<std::string>(SDL_GetTicks() / 1000);
00197     child["end_turn"] = lexical_cast<std::string>(turn);
00198     return child;
00199 }
00200 
00201 // User starts a game (may be new campaign or saved).
00202 void upload_log::start(game_state &state, const team &team,
00203                        unsigned team_number,
00204                        const unit_map &units,
00205                        const t_string &turn,
00206                        int num_turns)
00207 {
00208     std::vector<const unit*> all_units;
00209 
00210     // If we have a previous game which is finished, add it.
00211     if (game_finished(game_))
00212         config_.add_child("game", *game_);
00213 
00214     // Start could be called more than once,
00215     // so delete game_ to prevent memory leak
00216     delete game_;
00217     game_ = new config();
00218     (*game_)["time"] = lexical_cast<std::string>(SDL_GetTicks() / 1000);
00219     (*game_)["campaign"] = state.campaign_define;
00220     (*game_)["difficulty"] = state.difficulty;
00221     (*game_)["scenario"] = state.scenario;
00222     if (!state.version.empty())
00223         (*game_)["version"] = state.version;
00224     if (!turn.empty())
00225         (*game_)["start_turn"] = turn;
00226     (*game_)["gold"] = lexical_cast<std::string>(team.gold());
00227     (*game_)["num_turns"] = lexical_cast<std::string>(num_turns);
00228 
00229     // We seem to have to walk the map to find some units,
00230     // and the player's available_units for the rest.
00231     for (unit_map::const_iterator un = units.begin(); un != units.end(); ++un){
00232         if (un->second.side() == team_number) {
00233             all_units.push_back(&un->second);
00234         }
00235     }
00236 
00237     /** @todo FIXME: Assumes first player is "us"; is that valid? */
00238     player_info &player = state.players.begin()->second;
00239     for (std::vector<unit>::iterator it = player.available_units.begin();
00240          it != player.available_units.end();
00241          ++it) {
00242         all_units.push_back(&*it);
00243     }
00244 
00245     // Record details of any special units.
00246     std::vector<const unit*>::const_iterator i;
00247     for (i = all_units.begin(); i != all_units.end(); ++i) {
00248         if ((*i)->can_recruit()) {
00249             config &sp = game_->add_child("special-unit");
00250             sp["name"] = (*i)->id();
00251             sp["level"] = lexical_cast<std::string>((*i)->level());
00252             sp["experience"] = lexical_cast<std::string>((*i)->experience());
00253         }
00254     }
00255 
00256     // Record summary of all units.
00257     config &summ = game_->add_child("units-by-level");
00258     bool higher_units = true;
00259     for (int level = 0; higher_units; level++) {
00260         std::map<std::string, int> tally;
00261 
00262         higher_units = false;
00263         for (i = all_units.begin(); i != all_units.end(); ++i) {
00264             if ((*i)->level() > level)
00265                 higher_units = true;
00266             else if ((*i)->level() == level) {
00267                 if (tally.find((*i)->type_id()) == tally.end())
00268                     tally[(*i)->type_id()] = 1;
00269                 else
00270                     tally[(*i)->type_id()]++;
00271             }
00272         }
00273         if (!tally.empty()) {
00274             config &tc = summ.add_child(lexical_cast<std::string>(level));
00275             for (std::map<std::string, int>::iterator t = tally.begin();
00276                  t != tally.end();
00277                  t++) {
00278                 config &uc = tc.add_child(t->first);
00279                 uc["count"] = lexical_cast<std::string>(t->second);
00280             }
00281         }
00282     }
00283 }
00284 
00285 // User finishes a scenario.
00286 void upload_log::defeat(int turn)
00287 {
00288     // game_ can be NULL if user takes over partway through MP game.
00289     if (game_) {
00290         add_game_result("defeat", turn);
00291     }
00292 }
00293 
00294 void upload_log::victory(int turn, int gold)
00295 {
00296     // game_ can be NULL if user takes over partway through MP game.
00297     if (game_) {
00298         config &e = add_game_result("victory", turn);
00299         e["gold"] = lexical_cast<std::string>(gold);
00300     }
00301 }
00302 
00303 void upload_log::quit(int turn)
00304 {
00305     std::string turnstr = lexical_cast<std::string>(turn);
00306 
00307     // We only record the quit if they've actually played a turn.
00308     if (!game_ || game_->get_attribute("start_turn") == turnstr || turn == 1)
00309         return;
00310 
00311     add_game_result("quit", turn);
00312 }
00313 
00314 /** Ask user for permission to upload his game-stats. */
00315 void upload_log_dialog::show_beg_dialog(display& disp)
00316 {
00317     std::string msg = std::string(_("Wesnoth relies on volunteers like yourself for feedback, especially beginners and new players. Wesnoth keeps summaries of your games: you can help us improve game play by giving permission to send these summaries (anonymously) to wesnoth.org.\n"))
00318         + " \n`" + _("Summaries are stored here:")
00319         + " \n`~" + get_upload_dir() + "\n \n`"
00320         + _("You can view the results at:") + "\n`~"
00321         + "http://stats.wesnoth.org/?" + preferences::upload_id() + "\n \n";
00322     gui::dialog d(disp, _("Help us make Wesnoth better for you!"), msg, gui::OK_ONLY);
00323 
00324     d.add_option(_("Enable summary uploads"),
00325         preferences::upload_log(), gui::dialog::BUTTON_CHECKBOX_LEFT);
00326     d.show();
00327     preferences::set_upload_log(d.option_checked());
00328 }

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs