add anonymous data logging, thanks, rusty, for the hard work

This commit is contained in:
Jérémy Rosen 2006-01-03 22:59:23 +00:00
parent 8674b86082
commit eab9c1533b
18 changed files with 490 additions and 12 deletions

View file

@ -20,6 +20,9 @@ SVN trunk (1.1+svn):
use any attack of the same range. Official name for current ranges are
"melee" and "ranged"
* starting_villages macro updated for added village types
* Miscelleneous
* Added anonymous data logging for single player campaigns, see
stats.wesnoth.org
Version 1.1:
* campaign server

View file

@ -35,3 +35,4 @@ tip_of_day_33= _ "Units can gain After Maximum Level Advancements
Gaining an AMLA becomes progressively harder for each AMLA the unit
receives, however. Thus, it is often more useful to try to advance
your lower level units."
tip_of_day_34= _ "You can send the Wesnoth Project an anonymous summary of your progress using the Help Wesnoth button. This information is vital so we can adjust campaign difficulty."

View file

@ -103,6 +103,7 @@ wesnoth_SOURCES = \
unit.cpp \
unit_display.cpp \
unit_types.cpp \
upload_log.cpp \
variable.cpp \
video.cpp \
serialization/binary_or_text.cpp \

View file

@ -353,6 +353,7 @@ std::vector<std::string> get_text() {
"- Frédéric Wagner",
"- Jeff Breidenbach (jab)",
"- Dominique Bolin (Xan)",
"- Rusty Russell (rusty)",
"_" N_("+Bots"),
"- wesbot",

View file

@ -54,6 +54,8 @@ BPath be_path;
#include <algorithm>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <set>
#include "wesconfig.h"
@ -68,7 +70,6 @@ BPath be_path;
#define ERR_FS LOG_STREAM(err, filesystem)
#ifdef USE_ZIPIOS
#include <sstream>
#include <zipios++/collcoll.h>
#include <zipios++/dircoll.h>
#include <zipios++/zipfile.h>
@ -303,6 +304,12 @@ std::string get_screenshot_dir()
return get_dir(dir_path);
}
std::string get_upload_dir()
{
const std::string dir_path = get_user_data_dir() + "/upload";
return get_dir(dir_path);
}
std::string get_dir(const std::string& dir_path)
{
#ifdef _WIN32
@ -617,6 +624,32 @@ time_t file_create_time(const std::string& fname)
return buf.st_mtime;
}
//return the next ordered full filename within this directory
std::string next_filename(const std::string &dirname)
{
std::vector<std::string> files;
std::stringstream fname;
unsigned int num = 1;
// These are sorted, so we can simply add one to last one.
get_files_in_dir(dirname, &files);
// Make sure we skip over any files we didn't create ourselves.
std::vector<std::string>::reverse_iterator i;
for (i = files.rbegin(); i != files.rend(); ++i) {
if (i->length() == 8) {
try {
num = lexical_cast<int>(*i)+1;
break;
} catch (bad_lexical_cast &c) {
}
}
}
fname << std::setw(8) << std::setfill('0') << num;
return dirname + "/" + fname.str();
}
file_tree_checksum::file_tree_checksum()
: nfiles(0), sum_size(0), modified(0)
{}

View file

@ -51,6 +51,7 @@ std::string get_saves_dir();
std::string get_cache_dir();
std::string get_intl_dir();
std::string get_screenshot_dir();
std::string get_upload_dir();
std::string get_user_data_dir();
std::string get_cwd();
@ -78,6 +79,9 @@ bool file_exists(const std::string& name);
//function to get the creation time of a file
time_t file_create_time(const std::string& fname);
//return the next ordered full filename within this directory
std::string next_filename(const std::string &dirname);
struct file_tree_checksum
{
file_tree_checksum();

View file

@ -40,6 +40,7 @@
#include "thread.hpp"
#include "titlescreen.hpp"
#include "util.hpp"
#include "upload_log.hpp"
#include "wassert.hpp"
#include "wml_separators.hpp"
#include "serialization/binary_or_text.hpp"
@ -90,6 +91,7 @@ public:
bool change_language();
void show_preferences();
void show_upload_begging();
enum RELOAD_GAME_DATA { RELOAD_DATA, NO_RELOAD_DATA };
void play_game(RELOAD_GAME_DATA reload=RELOAD_DATA);
@ -123,6 +125,7 @@ private:
const image::manager image_manager_;
const events::event_context main_event_context_;
const hotkey::manager hotkey_manager_;
const upload_log::manager upload_log_manager_;
binary_paths_manager paths_manager_;
bool test_mode_, multiplayer_mode_, no_gui_;
@ -401,7 +404,8 @@ bool game_controller::play_test()
state_.scenario = "test";
try {
::play_game(disp(),state_,game_config_,units_data_,video_);
upload_log nolog(false);
::play_game(disp(),state_,game_config_,units_data_,video_,nolog);
} catch(game::load_game_exception& e) {
loaded_game_ = e.game;
loaded_game_show_replay_ = e.show_replay;
@ -560,8 +564,9 @@ bool game_controller::play_multiplayer_mode()
}
try {
upload_log nolog(false);
state_.snapshot = level;
::play_game(disp(),state_,game_config_,units_data_,video_);
::play_game(disp(),state_,game_config_,units_data_,video_,nolog);
//play_level(units_data_,game_config_,&level,video_,state_,story);
} catch(game::error& e) {
std::cerr << "caught error: '" << e.message << "'\n";
@ -1226,6 +1231,13 @@ void game_controller::show_preferences()
disp().redraw_everything();
}
void game_controller::show_upload_begging()
{
upload_log_dialog::show_beg_dialog(disp());
disp().redraw_everything();
}
//this function reads the game configuration, searching for valid cached copies first
void game_controller::read_game_cfg(const preproc_map& defines, config& cfg, bool use_cache)
{
@ -1438,7 +1450,12 @@ void game_controller::play_game(RELOAD_GAME_DATA reload)
const binary_paths_manager bin_paths_manager(game_config_);
try {
const LEVEL_RESULT result = ::play_game(disp(),state_,game_config_,units_data_,video_);
// Only record log for single-player games & tutorial.
upload_log log(state_.campaign_type.empty()
|| state_.campaign_type == "scenario"
|| state_.campaign_type == "tutorial");
const LEVEL_RESULT result = ::play_game(disp(),state_,game_config_,units_data_,video_, log);
// don't show The End for multiplayer scenario
// change this if MP campaigns are implemented
if(result == VICTORY && (state_.campaign_type.empty() || state_.campaign_type != "multiplayer")) {
@ -1715,6 +1732,9 @@ int play_game(int argc, char** argv)
} else if(res == gui::SHOW_ABOUT) {
about::show_about(game.disp());
continue;
} else if(res == gui::BEG_FOR_UPLOAD) {
game.show_upload_begging();
continue;
}
if (recorder.at_end()){

View file

@ -30,6 +30,7 @@
#include "video.hpp"
#include "statistics.hpp"
#include "serialization/string_utils.hpp"
#include "upload_log.hpp"
#define LOG_NW LOG_STREAM(info, network)
@ -236,6 +237,7 @@ void enter_wait_mode(display& disp, const config& game_config, game_data& data,
mp::ui::result res;
game_state state;
network_game_manager m;
upload_log nolog(false);
gamelist.clear();
statistics::fresh_stats();
@ -257,7 +259,7 @@ void enter_wait_mode(display& disp, const config& game_config, game_data& data,
switch (res) {
case mp::ui::PLAY:
play_game(disp, state, game_config, data, disp.video(), IO_CLIENT);
play_game(disp, state, game_config, data, disp.video(), nolog, IO_CLIENT);
recorder.clear();
break;
@ -278,6 +280,7 @@ void enter_connect_mode(display& disp, const config& game_config, game_data& dat
network::server_manager::TRY_CREATE_SERVER :
network::server_manager::NO_SERVER);
network_game_manager m;
upload_log nolog(false);
gamelist.clear();
statistics::fresh_stats();
@ -298,7 +301,7 @@ void enter_connect_mode(display& disp, const config& game_config, game_data& dat
switch (res) {
case mp::ui::PLAY:
play_game(disp, state, game_config, data, disp.video(), IO_SERVER);
play_game(disp, state, game_config, data, disp.video(), nolog, IO_SERVER);
recorder.clear();
break;

View file

@ -120,6 +120,7 @@ void play_replay(display& disp, game_state& state, const config& game_config,
LEVEL_RESULT play_game(display& disp, game_state& state, const config& game_config,
const game_data& units_data, CVideo& video,
upload_log &log,
io_type_t io_type)
{
std::string type = state.campaign_type;
@ -200,7 +201,7 @@ LEVEL_RESULT play_game(display& disp, game_state& state, const config& game_conf
if (state.label.empty())
state.label = (*scenario)["name"];
LEVEL_RESULT res = play_level(units_data,game_config,scenario,video,state,story);
LEVEL_RESULT res = play_level(units_data,game_config,scenario,video,state,story,log);
//LEVEL_RESULT res = play_scenario(units_data,game_config,scenario,video,state,story);
state.snapshot = config();

View file

@ -22,6 +22,7 @@ struct game_state;
class config;
struct game_data;
class CVideo;
class upload_log;
enum io_type_t {
IO_NONE,
@ -31,6 +32,7 @@ enum io_type_t {
LEVEL_RESULT play_game(display& disp, game_state& state, const config& game_config,
const game_data& units_data, CVideo& video,
upload_log &log,
io_type_t io_type=IO_NONE);

View file

@ -33,6 +33,8 @@
#include "sound.hpp"
#include "statistics.hpp"
#include "tooltips.hpp"
#include "upload_log.hpp"
#include "game_errors.hpp"
#include <iostream>
#include <iterator>
@ -107,7 +109,8 @@ namespace play{
LEVEL_RESULT play_level(const game_data& gameinfo, const config& game_config,
config const* level, CVideo& video,
game_state& state_of_game,
const std::vector<config*>& story)
const std::vector<config*>& story,
upload_log &log)
{
//if the recorder has no event, adds an "game start" event to the
//recorder, whose only goal is to initialize the RNG
@ -293,6 +296,14 @@ LEVEL_RESULT play_level(const game_data& gameinfo, const config& game_config,
//instead of starting a fresh one
const bool loading_game = lvl["playing_team"].empty() == false;
// log before prestart events: they do weird things.
if (first_human_team != -1) {
log.start(state_of_game, teams[first_human_team],
first_human_team+1, units,
loading_game ? state_of_game.get_variable("turn_number") : "",
num_turns);
}
//pre-start events must be executed before any GUI operation,
//as those may cause the display to be refreshed.
if(!loading_game) {
@ -573,6 +584,10 @@ redo_turn:
}
} //end for loop
} catch(game::load_game_exception& e) {
// Loading a new game is effectively a quit.
log.quit(status.turn());
throw;
} catch(end_level_exception& end_level) {
bool obs = team_manager.is_observer();
if (end_level.result == DEFEAT || end_level.result == VICTORY) {
@ -592,8 +607,10 @@ redo_turn:
}
if(end_level.result == QUIT) {
log.quit(status.turn());
return end_level.result;
} else if(end_level.result == DEFEAT) {
log.defeat(status.turn());
try {
game_events::fire("defeat");
} catch(end_level_exception&) {
@ -612,6 +629,10 @@ redo_turn:
} catch(end_level_exception&) {
}
if (end_level.result == VICTORY && first_human_team != -1) {
log.victory(status.turn(), teams[first_human_team].gold());
}
if(state_of_game.scenario == (*level)["id"]) {
state_of_game.scenario = (*level)["next_scenario"];
}

View file

@ -16,6 +16,7 @@
class config;
class CVideo;
struct game_state;
class upload_log;
#include "game_config.hpp"
#include "unit_types.hpp"
@ -40,7 +41,8 @@ struct end_turn_exception {
LEVEL_RESULT play_level(const game_data& gameinfo, const config& terrain_config,
config const* level, CVideo& video,
game_state& state_of_game,
const std::vector<config*>& story);
const std::vector<config*>& story,
upload_log &log);
namespace play{
void place_sides_in_preferred_locations(gamemap& map, const config::child_list& sides);

View file

@ -769,6 +769,29 @@ void set_show_fps(bool value)
fps = value;
}
bool upload_log()
{
return prefs["upload_log"] == "yes";
}
void set_upload_log(bool value)
{
prefs["upload_log"] = value ? "yes" : "no";
}
const std::string &upload_id()
{
// We create a unique id for each person, *when asked for* to increase
// randomness.
if (prefs["upload_id"] == "") {
srand(time(NULL));
prefs["upload_id"]
= lexical_cast<std::string>(rand())
+ lexical_cast<std::string>(SDL_GetTicks());
}
return prefs["upload_id"];
}
bool compress_saves()
{
return prefs["compress_saves"] != "no";

View file

@ -178,6 +178,10 @@ namespace preferences {
bool flip_time();
void set_flip_time(bool value);
bool upload_log();
void set_upload_log(bool value);
const std::string &upload_id();
// Multiplayer functions
bool chat_timestamp();
void set_chat_timestamp(bool value);

View file

@ -203,14 +203,16 @@ TITLE_RESULT show_title(display& screen, config& tips_of_day, int* ntip)
N_("TitleScreen button^Load"),
N_("TitleScreen button^Language"),
N_("TitleScreen button^Preferences"),
N_("TitleScreen button^Help Wesnoth"),
N_("About"),
N_("TitleScreen button^Quit") };
N_("TitleScreen button^Quit") };
static const char* help_button_labels[] = { N_("Start a tutorial to familiarize yourself with the game"),
N_("Start a new single player campaign"),
N_("Play multiplayer (hotseat, LAN, or Internet), or a single scenario against the AI"),
N_("Load a single player saved game"),
N_("Change the language"),
N_("Configure the game's settings"),
N_("Help Wesnoth by sending us information"),
N_("View the credits"),
N_("Quit the game") };
@ -222,7 +224,7 @@ TITLE_RESULT show_title(display& screen, config& tips_of_day, int* ntip)
#ifdef USE_TINY_GUI
const int menu_yincr = 15;
#else
const int menu_yincr = 40;
const int menu_yincr = 35;
#endif
const int padding = game_config::title_buttons_padding;

View file

@ -20,7 +20,7 @@ class display;
namespace gui {
enum TITLE_RESULT { TUTORIAL = 0, NEW_CAMPAIGN, MULTIPLAYER, LOAD_GAME,
CHANGE_LANGUAGE, EDIT_PREFERENCES, SHOW_ABOUT, QUIT_GAME, TITLE_CONTINUE };
CHANGE_LANGUAGE, EDIT_PREFERENCES, BEG_FOR_UPLOAD, SHOW_ABOUT, QUIT_GAME, TITLE_CONTINUE };
TITLE_RESULT show_title(display& screen, config& tips_of_day, int* ntip);

288
src/upload_log.cpp Normal file
View file

@ -0,0 +1,288 @@
/* $Id$ */
/*
Copyright (C) 2005 by Rusty Russell <rusty@rustcorp.com.au>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#include "global.hpp"
#define GETTEXT_DOMAIN "wesnoth"
#include "gettext.hpp"
#include "filesystem.hpp"
#include "preferences.hpp"
#include "serialization/parser.hpp"
#include "show_dialog.hpp"
#include "upload_log.hpp"
#include "wesconfig.h"
#include "wml_separators.hpp"
#include "SDL_net.h"
#include <vector>
#include <string>
#define TARGET_HOST "stats.wesnoth.org"
#define TARGET_URL "/upload.cgi"
#define TARGET_PORT 80
struct upload_log::thread_info upload_log::thread_;
// On exit, kill the upload thread if it's still going.
upload_log::manager::~manager()
{
threading::thread *t = thread_.t;
if (t)
t->kill();
}
static void send_string(TCPsocket sock, const std::string &str)
{
if (SDLNet_TCP_Send(sock, (void *)str.c_str(), str.length())
!= str.length()) {
throw network::error("");
}
}
// Function which runs in a background thread to upload logs to server.
// Uses http POST to port 80 for maximum firewall penetration & other-end
// compatibility.
static int upload_logs(void *_ti)
{
TCPsocket sock = NULL;
upload_log::thread_info *ti = (upload_log::thread_info *)_ti;
const char *header =
"POST " TARGET_URL " HTTP/1.1\n"
"Host: " TARGET_HOST "\n"
"User-Agent: Wesnoth " VERSION "\n"
"Content-Type: text/plain\n";
try {
std::vector<std::string> files;
// These are sorted: send them one at a time until we get to lastfile.
get_files_in_dir(get_upload_dir(), &files, NULL, ENTIRE_FILE_PATH);
IPaddress ip;
if (SDLNet_ResolveHost(&ip, TARGET_HOST, TARGET_PORT) == 0) {
std::vector<std::string>::iterator i;
for (i = files.begin(); i!=files.end() && *i!=ti->lastfile; i++) {
std::string contents;
int resplen;
char response[strlen("HTTP/1.1 2")];
contents = read_file(*i);
sock = SDLNet_TCP_Open(&ip);
if (!sock)
break;
send_string(sock, header);
send_string(sock, "Content-length: ");
send_string(sock, lexical_cast<std::string>(contents.length()));
send_string(sock, "\n\n");
send_string(sock, contents.c_str());
if (SDLNet_TCP_Recv(sock, response, sizeof(response))
!= sizeof(response))
break;
// Must be version 1.x, must start with 2 (eg. 200) for success
if (memcmp(response, "HTTP/1.", strlen("HTTP/1.")) != 0)
break;
if (memcmp(response+8, " 2", strlen(" 2")) != 0)
break;
delete_directory(*i);
SDLNet_TCP_Close(sock);
sock = NULL;
}
}
} catch(...) { }
if (sock)
SDLNet_TCP_Close(sock);
ti->t = NULL;
return 0;
}
// Currently only enabled when playing campaigns.
upload_log::upload_log(bool enable) : game_(NULL), enabled_(enable)
{
filename_ = next_filename(get_upload_dir());
if (preferences::upload_log() && !thread_.t) {
// Thread can outlive us; it uploads everything up to the next
// filename, and unsets thread_.t when it's finished.
thread_.lastfile = filename_;
thread_.t = new threading::thread(upload_logs, &thread_);
}
config_["version"] = VERSION;
config_["format_version"] = "1";
config_["id"] = preferences::upload_id();
}
upload_log::~upload_log()
{
// If last game has a conclusion, add it.
if (game_finished(game_))
config_.add_child("game", *game_);
if (enabled_ && !config_.empty()) {
std::ostream *out = ostream_file(filename_);
write(*out, config_);
delete out;
// Try to upload latest log before exit.
if (preferences::upload_log() && !thread_.t) {
thread_.lastfile = next_filename(get_upload_dir());
thread_.t = new threading::thread(upload_logs, &thread_);
}
}
}
bool upload_log::game_finished(config *game)
{
if (!game)
return false;
return game->child("victory") || game->child("defeat") || game->child("quit");
}
config &upload_log::add_game_result(const std::string &str, int turn)
{
config &child = game_->add_child(str);
child["time"] = lexical_cast<std::string>(SDL_GetTicks() / 1000);
child["end_turn"] = lexical_cast<std::string>(turn);
return child;
}
// User starts a game (may be new campaign or saved).
void upload_log::start(game_state &state, const team &team,
int team_number,
const unit_map &units,
const t_string &turn,
int num_turns)
{
const config *player_conf;
std::vector<const unit*> all_units;
// If we have a previous game which is finished, add it.
if (game_finished(game_))
config_.add_child("game", *game_);
game_ = new config();
(*game_)["time"] = lexical_cast<std::string>(SDL_GetTicks() / 1000);
(*game_)["campaign"] = state.campaign_define;
(*game_)["difficulty"] = state.difficulty;
(*game_)["scenario"] = state.scenario;
if (!state.version.empty())
(*game_)["version"] = state.version;
if (!turn.empty())
(*game_)["start_turn"] = turn;
(*game_)["gold"] = lexical_cast<std::string>(team.gold());
(*game_)["num_turns"] = lexical_cast<std::string>(num_turns);
// We seem to have to walk the map to find some units, and the player's
// available_units for the rest.
for (unit_map::const_iterator un = units.begin(); un != units.end(); ++un){
if (un->second.side() == team_number) {
all_units.push_back(&un->second);
}
}
// FIXME: Assumes first player is "us"; is that valid?
player_info &player = state.players.begin()->second;
for (std::vector<unit>::iterator it = player.available_units.begin();
it != player.available_units.end();
++it) {
all_units.push_back(&*it);
}
// Record details of any special units.
std::vector<const unit*>::const_iterator i;
for (i = all_units.begin(); i != all_units.end(); ++i) {
if ((*i)->can_recruit()) {
config &sp = game_->add_child("special-unit");
sp["name"] = (*i)->name();
sp["level"] = lexical_cast<std::string>((*i)->type().level());
sp["experience"] = lexical_cast<std::string>((*i)->experience());
}
}
// Record summary of all units.
config &summ = game_->add_child("units-by-level");
bool higher_units = true;
for (int level = 0; higher_units; level++) {
std::map<std::string, int> tally;
higher_units = false;
for (i = all_units.begin(); i != all_units.end(); ++i) {
if ((*i)->type().level() > level)
higher_units = true;
else if ((*i)->type().level() == level) {
if (tally.find((*i)->type().id()) == tally.end())
tally[(*i)->type().id()] = 1;
else
tally[(*i)->type().id()]++;
}
}
if (!tally.empty()) {
config &tc = summ.add_child(lexical_cast<std::string>(level));
for (std::map<std::string, int>::iterator t = tally.begin();
t != tally.end();
t++) {
config &uc = tc.add_child(t->first);
uc["count"] = lexical_cast<std::string>(t->second);
}
}
}
}
// User finishes a scenario.
void upload_log::defeat(int turn)
{
add_game_result("defeat", turn);
}
void upload_log::victory(int turn, int gold)
{
config &e = add_game_result("victory", turn);
e["gold"] = lexical_cast<std::string>(gold);
}
void upload_log::quit(int turn)
{
std::string turnstr = lexical_cast<std::string>(turn);
// We only record the quit if they've actually played a turn.
if (!game_ || game_->get_attribute("start_turn") == turnstr || turn == 1)
return;
add_game_result("quit", turn);
}
void upload_log_dialog::show_beg_dialog(display& disp)
{
std::vector<gui::check_item> options;
options.push_back(gui::check_item("Enable summary uploads",
preferences::upload_log()));
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"))
+ _("You can see the summaries to be sent in ")
+ get_upload_dir() + "\n"
+ _("You can view the results at http://stats.wesnoth.org.\n");
gui::show_dialog(disp, NULL,
_("Help us make Wesnoth better for you!"),
msg,
gui::OK_ONLY,
NULL, NULL, "", NULL, 0, NULL, &options);
preferences::set_upload_log(options.front().checked);
}

69
src/upload_log.hpp Normal file
View file

@ -0,0 +1,69 @@
/* $Id$ */
/*
Copyright (C) 2005 by Rusty Russell <rusty@rustcorp.com.au>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#ifndef UPLOAD_LOG_H_INCLUDED
#define UPLOAD_LOG_H_INCLUDED
#include "config.hpp"
#include "display.hpp"
#include "gamestatus.hpp"
#include "team.hpp"
#include "thread.hpp"
#include "tstring.hpp"
#include "unit.hpp"
struct upload_log
{
struct manager {
manager() { };
~manager();
};
// We only enable logging when playing campaigns.
upload_log(bool enable);
~upload_log();
// User starts a game (may be new campaign or saved).
void start(game_state &state, const team &team,
int team_number, const unit_map &map, const t_string &turn,
int num_turns);
// User finishes a level.
void defeat(int turn);
void victory(int turn, int gold);
void quit(int turn);
// Argument passed to upload thread.
struct thread_info {
threading::thread *t;
std::string lastfile;
};
private:
config &add_game_result(const std::string &str, int turn);
bool game_finished(config *game);
static struct thread_info thread_;
config config_;
config *game_;
std::string filename_;
bool enabled_;
};
namespace upload_log_dialog
{
// Please please please upload stats?
void show_beg_dialog(display& disp);
};
#endif // UPLOAD_LOG_H_INCLUDED