commit f12203f0
Now with PostgreSql support and demands it
git-svn-id: http://oastat.googlecode.com/svn/trunk@4 a36be8df-c75f-1e49-46ad-4f0ee64bdc62
Changed files
| M | src/Makefile before |
| A | src/db/Db2PostgreSQL.cpp |
| A | src/db/Db2PostgreSQL.h |
| M | src/db/database.hpp before |
| M | src/db/db2stdout.cpp before |
| M | src/oastat.cpp before |
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 <stdlib.h>
+
+
+#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<sql.end();itr++) {
+ if(*itr == '\'') {
+ output+="\'\'";
+ } else
+ {
+ output += *itr;
+ }
+ }
+ return output;
+ #else
+ char* tmp = (char*)malloc(sql.size()*2);
+ PQescapeStringConn(conn,tmp,sql.c_str(),sql.size()*2,NULL);
+ string res = tmp;
+ free(tmp);
+ return tmp;
+ #endif
+}
+
+/**
+ * Connects to the database and executes the query.
+ *
+ * @param query to execute
+ * @return <0 if fail, >=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<sql.end();itr++) {
+ if(*itr == '\'') {
+ output+="\'\'";
+ } else
+ {
+ output += *itr;
+ }
+ }
+ return output;
+}
+
DB2stdout::DB2stdout() {
}
@@ -46,7 +60,6 @@ DB2stdout::~DB2stdout() {
void DB2stdout::createTables()
{
//Nothing to do
- cout << "CREATE TABLE(S) ..." << endl;
}
void DB2stdout::startGame(int gametype, string mapname, string basegame)
@@ -66,12 +79,12 @@ void DB2stdout::setPlayerInfo(string guid, string nickname, bool isBot, int seco
//If team < 0 then it is a disconnect event that need not to be in
if(team>-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<Struct2Db*> commands;
@@ -67,20 +72,36 @@ void addCommands()
}
}
-int main ()
+int main (int argc, const char* argv[])
{
+ string dbargs = "";
+ for(int i=1;i<argc;i++) {
+ bool onemore = i+1<argc;
+ if(string(argv[i]) == "-dbarg" && onemore ) {
+ i++;
+ dbargs = string(argv[i]);
+ }
+ }
+ try{
+ #if USESTDOUT
cout << "INFO: oastat " << VERSION << " ready to process data" << endl;
db = new DB2stdout();
+ #endif
+
+ #if USEPOSTGRESQL
+ if(dbargs.length()<1)
+ db = new Db2PostgreSQL();
+ else
+ db = new Db2PostgreSQL(dbargs);
+ #endif
addCommands();
processStdIn();
- /*while(true) {
- string line;
- cin >> 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;