diff --git a/src/Makefile b/src/Makefile index 0e87b03..496d60a 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,30 +1,54 @@ #This is the makefile +USEPOSTGRESQL=1 +USESTDOUT=0 +BASE_LIBS=-lgcrypt +BASE_CFLAGS=-c -g -build/oastat: build/oastat.o build/db2stdout.o build/oastatstruct.o build/kill2db.o build/init2db.o build/shutdown2db.o build/userinfo2db.o build/disconnect2db.o - g++ -O -o build/oastat build/oastat.o build/db2stdout.o build/oastatstruct.o build/kill2db.o build/init2db.o build/shutdown2db.o build/userinfo2db.o build/disconnect2db.o -lgcrypt +O_FILES=build/oastat.o build/oastatstruct.o build/kill2db.o build/init2db.o build/shutdown2db.o build/userinfo2db.o build/disconnect2db.o + +ifeq ($(USESTDOUT),1) + O_FILES+= build/db2stdout.o + BASE_CFLAGS+= -DUSESTDOUT=1 +endif + +ifeq ($(USEPOSTGRESQL),1) + BASE_LIBS+= -lpq + O_FILES+= build/db2postgresql.o + BASE_CFLAGS+= -DUSEPOSTGRESQL=1 +endif + +build/oastat: $(O_FILES) + g++ -O -o build/oastat $(O_FILES) $(BASE_LIBS) build/oastat.o: oastat.cpp - g++ -c -g oastat.cpp -o build/oastat.o + g++ $(BASE_CFLAGS) oastat.cpp -o build/oastat.o +ifeq ($(USESTDOUT),1) build/db2stdout.o: db/db2stdout.cpp db/db2stdout.h db/database.hpp - g++ -c -g db/db2stdout.cpp -o build/db2stdout.o + g++ $(BASE_CFLAGS) db/db2stdout.cpp -o build/db2stdout.o +endif + +ifeq ($(USEPOSTGRESQL),1) +build/db2postgresql.o: db/Db2PostgreSQL.cpp db/Db2PostgreSQL.h db/database.hpp + g++ $(BASE_CFLAGS) db/Db2PostgreSQL.cpp -o build/db2postgresql.o +endif build/oastatstruct.o: oastatstruct.cpp oastatstruct.h - g++ -c -g oastatstruct.cpp -o build/oastatstruct.o + g++ $(BASE_CFLAGS) oastatstruct.cpp -o build/oastatstruct.o build/kill2db.o: oss2db/kill2db.cpp oss2db/kill2db.h - g++ -c -g oss2db/kill2db.cpp -o build/kill2db.o + g++ $(BASE_CFLAGS) oss2db/kill2db.cpp -o build/kill2db.o build/init2db.o: oss2db/init2db.cpp oss2db/init2db.h - g++ -c -g oss2db/init2db.cpp -o build/init2db.o + g++ $(BASE_CFLAGS) oss2db/init2db.cpp -o build/init2db.o build/shutdown2db.o: oss2db/shutdown2db.cpp oss2db/shutdown2db.h - g++ -c -g oss2db/shutdown2db.cpp -o build/shutdown2db.o + g++ $(BASE_CFLAGS) oss2db/shutdown2db.cpp -o build/shutdown2db.o build/userinfo2db.o: oss2db/userinfo2db.cpp oss2db/userinfo2db.h - g++ -c -g oss2db/userinfo2db.cpp -o build/userinfo2db.o + g++ $(BASE_CFLAGS) oss2db/userinfo2db.cpp -o build/userinfo2db.o build/disconnect2db.o: oss2db/Disconnect2Db.cpp oss2db/Disconnect2Db.h - g++ -c -g oss2db/Disconnect2Db.cpp -o build/disconnect2db.o + g++ $(BASE_CFLAGS) oss2db/Disconnect2Db.cpp -o build/disconnect2db.o diff --git a/src/db/Db2PostgreSQL.cpp b/src/db/Db2PostgreSQL.cpp new file mode 100644 index 0000000..1be0855 --- /dev/null +++ b/src/db/Db2PostgreSQL.cpp @@ -0,0 +1,207 @@ +/* +=========================================================================== +oastat - OpenArena stat program +Copyright (C) 2010 Poul Sander (oastat@poulsander.com) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +=========================================================================== +*/ + +#include "Db2PostgreSQL.h" +#include + + +#define STARTGAME "INSERT INTO oastat_games(gametype, mapname, basegame) VALUES (%i,'%s','%s')" +#define PLAYERSINSERT "INSERT INTO oastat_players(guid,nickname,lastseen,isBot, model, headmodel) VALUES ('%s','%s',now(),%s,'%s','%s')" +#define PLAYERSUPDATE "UPDATE oastat_players SET nickname = '%s',lastseen = now(),isBot = %s, model = '%s', headmodel = '%s' WHERE guid = '%s'" +#define USERINFOINSERT "INSERT INTO oastat_userinfo(gamenumber,second,guid,team,model,skill) VALUES (%i,%i,'%s',%i,'%s',%i)" +#define ENDGAME "UPDATE oastat_games SET second=%i,time = now() where gamenumber = %i" +#define KILL "INSERT INTO oastat_kills(gamenumber,second,attacker,target,modtype) VALUES(%i,%i,'%s','%s',%i)" +#define CAPTURE "INSERT INTO oastat_captures(gamenumber,second,player,team) VALUES (%i,%i,'%s',%i)" +#define AWARD "INSERT INTO oastat_awards(gamenumber,second,player,award) VALUES (%i,%i,'%s',%i)" + +#define PREP1 "PREPARE addkill(int,int,text,text,int) AS INSERT INTO oastat_kills(gamenumber,second,attacker,target,modtype) VALUES($1,$2,$3,$4,$5)" +#define PREP2 "PREPARE addgame(int,text,text) AS INSERT INTO oastat_games(gametype, mapname, basegame) VALUES ($1,$2,$3)" +#define PREP3 "PREPARE endgame(int,int) AS UPDATE oastat_games SET second=$2,time = now() where gamenumber = $1" +#define PREP4 "PREPARE addplayer(text,text,boolean,text,text) AS INSERT INTO oastat_players(guid,nickname,lastseen,isBot, model, headmodel) VALUES ($1,$2,now(),$3,$4,$5)" +#define PREP5 "PREPARE updateplayer(text,text,boolean,text,text) AS UPDATE oastat_players SET nickname = $2,lastseen = now(),isBot = $3, model = $4, headmodel = $5 WHERE guid = $6" +#define PREP6 "PREPARE adduserinfo(int,int,text,int,text,int) AS INSERT INTO oastat_userinfo(gamenumber,second,guid,team,model,skill) VALUES ($1,$2,$3,$4,$5,$6)" +#define PREP7 "PREPARE addcapture(int,int,text,int) AS INSERT INTO oastat_captures(gamenumber,second,player,team) VALUES ($1,$2,$3,$4)" +#define PREP8 "PREPARE addaward(int,int,text,int) AS INSERT INTO oastat_awards(gamenumber,second,player,award) VALUES ($1,$2,$3,$4)" + +static char* booltext[2] = {"false","true"}; + +/** + * Escapes special chaecters. The best solution should be to use PQescapeStringConn + * but unfortunatly I had limited success likely because the C++ string class + * already handles encoding. The only char postgreSql can be cheated by is the + * single quete, so that is the only one we removes. + * + * @param unescaped string + * @return escaped string + */ +string Db2PostgreSQL::sqlescape(string sql) { + #if 1 + string output = ""; + string::iterator itr; + for(itr = sql.begin();itr=0 if succes and/or >0 if returned tubles + */ +int Db2PostgreSQL::simpleQuery(const char* query) { + res = PQexec(conn, query_string); + int ret; + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + if (PQresultStatus(res) == PGRES_TUPLES_OK ) { + //If a SELECT STATEMENT count results + ret = PQntuples(res); + PQclear(res); + return ret; + } + PQclear(res); + cout << "FAIL: " << query << endl; + return -1; + } + PQclear(res); //We don't relly care about the result + cout << "SUCCES: " << query << endl; + return 0; +} + +Db2PostgreSQL::Db2PostgreSQL() { + conn = PQconnectdb("dbname=oastat"); //Connect to the database + if (PQstatus(conn) == CONNECTION_BAD) + throw "Failed to open connection to DB!"; + cout << "CONNECTED" << endl; +} + +Db2PostgreSQL::Db2PostgreSQL(string args) { + conn = PQconnectdb(args.c_str()); //Connect to the database + if (PQstatus(conn) == CONNECTION_BAD) + throw "Failed to open connection to DB!"; + cout << "CONNECTED" << endl; +} + +Db2PostgreSQL::Db2PostgreSQL(const Db2PostgreSQL& orig) { + throw "May not make copy of Db2PostgreSQL"; +} + +Db2PostgreSQL::~Db2PostgreSQL() { + PQfinish(conn); +} + +void Db2PostgreSQL::createTables() +{ + //Nothing to do +} + +void Db2PostgreSQL::startGame(int gametype, string mapname, string basegame) +{ + simpleQuery("BEGIN"); + simpleQuery("LOCK TABLE OASTAT_GAMES IN SHARE ROW EXCLUSIVE"); //Hold a lock until we have selected the just inserted line + gamenumber = -1; //Prevent any following sql commands from completing until this is set! + sprintf(query_string,STARTGAME,gametype,mapname.c_str(),basegame.c_str()); + res = PQexec(conn, query_string); + if (PQresultStatus(res) != PGRES_COMMAND_OK) { + PQclear(res); + cout << "FAIL: " << query_string << endl; + simpleQuery("ROLLBACK"); + return; + } + PQclear(res); + res = PQexec(conn, "SELECT MAX(gamenumber) FROM OASTAT_GAMES"); + if (PQresultStatus(res) != PGRES_TUPLES_OK ) { + PQclear(res); + simpleQuery("ROLLBACK"); + return; + } + gamenumber = atoi(PQgetvalue(res,0,0)); + cout << "GAME: " << PQgetvalue(res,0,0) << ":" << gamenumber << endl; + PQclear(res); + simpleQuery("COMMIT"); //release lock + simpleQuery("BEGIN"); //This is just for performace... +} + +int Db2PostgreSQL::getGameNumber() +{ + return gamenumber; //Only to standard out, normally this should be read from db!!! +} + +void Db2PostgreSQL::setPlayerInfo(string guid, string nickname, bool isBot, int second, int team, string model, string headmodel, int skill) +{ + //If team < 0 then it is a disconnect event that need not to be in + if(team>-1) + { + /* + Note: There is a possible race condition here! + * One might consider ending the transaction and using autocommit for this part. + */ + //simpleQuery("COMMIT"); + sprintf(query_string,"SELECT * FROM oastat_players WHERE GUID = '%s'",sqlescape(guid).c_str()); + if(simpleQuery(query_string)<1) { + sprintf(query_string,PLAYERSINSERT,sqlescape(guid).c_str(),sqlescape(nickname).c_str(),booltext[isBot],sqlescape(model).c_str(),sqlescape(headmodel).c_str()); + simpleQuery(query_string); + } + sprintf(query_string,PLAYERSUPDATE,sqlescape(nickname).c_str(),booltext[isBot],sqlescape(model).c_str(),sqlescape(headmodel).c_str(),guid.c_str()); + simpleQuery(query_string); + //simpleQuery("BEGIN"); + } + sprintf(query_string,USERINFOINSERT,getGameNumber(),second,sqlescape(guid).c_str(),team,sqlescape(model).c_str(),skill); + simpleQuery(query_string); +} + +void Db2PostgreSQL::addKill(int second, string attackerID, string targetID, int type) +{ + sprintf(query_string,KILL,gamenumber,second,sqlescape(attackerID).c_str(),sqlescape(targetID).c_str(),type); + simpleQuery(query_string); +} + +void Db2PostgreSQL::endGame(int second) +{ + sprintf(query_string,ENDGAME,second,gamenumber); + simpleQuery(query_string); + simpleQuery("COMMIT"); //This is just for performace... +} + +void Db2PostgreSQL::addCapture(int second, string player, int team) +{ + sprintf(query_string,CAPTURE,gamenumber,second,sqlescape(player).c_str(),team); + simpleQuery(query_string); +} + +void Db2PostgreSQL::addAward(int second, string player, int award) +{ + sprintf(query_string,AWARD,gamenumber,second,sqlescape(player).c_str(),award); + simpleQuery(query_string); +} \ No newline at end of file diff --git a/src/db/Db2PostgreSQL.h b/src/db/Db2PostgreSQL.h new file mode 100644 index 0000000..1110aaf --- /dev/null +++ b/src/db/Db2PostgreSQL.h @@ -0,0 +1,54 @@ +/* +=========================================================================== +oastat - OpenArena stat program +Copyright (C) 2010 Poul Sander (oastat@poulsander.com) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +=========================================================================== +*/ + +#ifndef _DB2POSTGRESQL_H +#define _DB2POSTGRESQL_H + +#include "database.hpp" +#include "postgresql/libpq-fe.h" + +using namespace std; + +class Db2PostgreSQL : public Database{ +public: + Db2PostgreSQL(); + Db2PostgreSQL(string args); + Db2PostgreSQL(const Db2PostgreSQL& orig); + virtual ~Db2PostgreSQL(); + void createTables(); + void startGame(int gametype, string mapname, string basegame); + void endGame(int second); + int getGameNumber(); + void setPlayerInfo(string guid, string nickname, bool isBot, int second, int team, string model, string headmodel, int skill); + void addKill(int second, string attackerID, string targetID, int type); + void addCapture(int second, string player, int team); + void addAward(int second, string player, int award); +private: + int simpleQuery(const char *); + string sqlescape(string sql); + PGconn *conn; + PGresult *res; + int gamenumber; + char query_string[4096]; +}; + +#endif /* _DB2POSTGRESQL_H */ + diff --git a/src/db/database.hpp b/src/db/database.hpp index 6178263..a7e1620 100644 --- a/src/db/database.hpp +++ b/src/db/database.hpp @@ -34,12 +34,41 @@ class Database { public: virtual void createTables() = 0; + /** + * Starts a new game in the database might also start a transaction + * + * @param gametype - integer of the gametype DM=0,CTF=4, Elim=8 and so on + * @param mapname - the mapname used for this game + * @param basegame - the mod... normally baseoa + */ virtual void startGame(int gametype, string mapname, string basegame) = 0; - + + /** + * Called then a game ends. + * + * @param second - relative time since game start + */ virtual void endGame(int second) = 0; + /** + * Document getGameNumber() here... + * + * @return the gamenumber that we are working on. Retrieved from the db. + */ virtual int getGameNumber() = 0; + /** + * Document setPlayerInfo(string,string,bool,int,int,string,string,int) here... + * + * @param guid - the hashed GUID of the player + * @param nickname - The players nickname + * @param isBot - true if a bot, false otherwise + * @param second - relative time for the event + * @param team - team number to join, -1 if leaving + * @param model - model used + * @param headmodel - headmodel used + * @param skill - bot skill, bots only + */ virtual void setPlayerInfo(string guid, string nickname, bool isBot, int second, int team, string model, string headmodel, int skill = -1) = 0; virtual void addKill(int second, string attackerID, string targetID, int type) = 0; diff --git a/src/db/db2stdout.cpp b/src/db/db2stdout.cpp index ec37bc6..b8fd868 100644 --- a/src/db/db2stdout.cpp +++ b/src/db/db2stdout.cpp @@ -37,6 +37,20 @@ static int gamecount = 1; //Only to standard out, normally this should be read f static char* booltext[2] = {"false","true"}; +static string sqlescape(string sql) { + string output = ""; + string::iterator itr; + for(itr = sql.begin();itr-1) { - printf(PLAYERSINSERT,guid.c_str(),nickname.c_str(),booltext[isBot],model.c_str(),headmodel.c_str()); + printf(PLAYERSINSERT,guid.c_str(),sqlescape(nickname).c_str(),booltext[isBot],sqlescape(model).c_str(),sqlescape(headmodel).c_str()); cout << endl; - printf(PLAYERSUPDATE,nickname.c_str(),booltext[isBot],model.c_str(),headmodel.c_str(),guid.c_str()); + printf(PLAYERSUPDATE,sqlescape(nickname).c_str(),booltext[isBot],sqlescape(model).c_str(),sqlescape(headmodel).c_str(),guid.c_str()); cout << endl; } - printf(USERINFOINSERT,getGameNumber(),second,guid.c_str(),team,model.c_str(),skill); + printf(USERINFOINSERT,getGameNumber(),second,guid.c_str(),team,sqlescape(model).c_str(),skill); cout << endl; } diff --git a/src/oastat.cpp b/src/oastat.cpp index 4270937..4b50a4a 100644 --- a/src/oastat.cpp +++ b/src/oastat.cpp @@ -30,7 +30,12 @@ Copyright (C) 2010 Poul Sander (oastat@poulsander.com) using namespace std; #include "db/database.hpp" +#ifdef USESTDOUT #include "db/db2stdout.h" +#endif +#ifdef USEPOSTGRESQL +#include "db/Db2PostgreSQL.h" +#endif #include "oastatstruct.h" #include "oss2db/struct2db.h" #include "local.h" @@ -44,7 +49,7 @@ string clientIdMap[MAX_ID]; static void processStdIn(); -DB2stdout *db; +Database *db; vector commands; @@ -67,20 +72,36 @@ void addCommands() } } -int main () +int main (int argc, const char* argv[]) { + string dbargs = ""; + for(int i=1;i> line >> endl; - - }*/ + }catch (const char *s) { + cout << "Crashed: " << s << endl; + } return 0; //Rest of file is for testing @@ -121,14 +142,15 @@ static void processStdIn() { /** * Hashes the user id so they cannot be recovered from the db. - * param1 The unhashed id - * return the hashed id + * + * @param unhashedID - The unhashed id + * @return the hashed id */ string getHashedId(string unhashedID) { int msg_len = unhashedID.length(); int hash_len = gcry_md_get_algo_dlen( GCRY_MD_SHA1 ); unsigned char hash_binary[ hash_len ]; - char hash_hex[ hash_len*2+1 ]; + char hash_hex[ hash_len*2+1 ]; //surpriseingly this works char *out = hash_hex; //(char *) malloc( sizeof(char) * ((hash_len*2)+1) ); char *p = out;