2465: Sqlite3 support and prepared statements r=heinrich5991 a=Zwelf

This PR changes the abstraction layer of the score backend to thin abstractions over the MySQL and SQLite3 library. It executes all Queries in one worker thread making it easier to use the ddnet thread pool. This doesn't change much, because each the mysql-connection was locked with `m_SqlLock` beforehand, serializing writes and reads respectively.

Behavior change (even though I tried to minimize them):

* `sv_use_sql` is used to determine if mysql server should be added
* `sv_sql_failure_file` is replaced by `sv_sqlite_file`
* `sv_sqlite_file` is either used as a backup server when `sv_use_sql` is enabled or as the primary read+write server when `sv_use_sql` is disabled
* `/load` now escapes the like-string

Since I am not good at designing config file commands, I would appreciate feedback on this part.

WIP:
* [x] rewrite SQL statements to work in both MySQL and SQLite (preferable just ANSI-SQL)
  * [x] create tables (`COLLATE BINARY` and encoding info)
  * [x] store rank (UPSERT for points)
  * [x] load birthday (different function in sqlite for time handling)
  * [x] `/mapinfo` (`convert(? using utf8mb4) COLLATE utf8mb4_general_ci`)
  * [x] `/map` (`convert(? using utf8mb4) COLLATE utf8mb4_general_ci`)
  * [x] store teamrank (`GROUP_CONCAT`)
  * [x] `/teamrank` (`GROUP_CONCAT`)
  * [x] ~`/top5team` (`GROUP_CONCAT`)~ doesn't contain GROUP_CONCAT
  * [x] `/times` (`UNIX_TIMESTAMP`)
  * [x] `/load` without any arguments (`UNIX_TIMESTAMP`)
* [x] all commits compiling, making future bisect easier
* [x] write a sqlite_to_mysql script
* [x] write an old_file_server to sqlite script
* [x] gracefully shutdown DbPool

Co-authored-by: Zwelf <zwelf@strct.cc>
This commit is contained in:
bors[bot] 2020-08-09 23:30:08 +00:00 committed by GitHub
commit b06bea7c04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 3294 additions and 3227 deletions

View file

@ -62,7 +62,7 @@ jobs:
if: contains(matrix.os, 'ubuntu')
run: |
sudo apt-get update -y
sudo apt-get install pkg-config cmake libfreetype6-dev libnotify-dev libsdl2-dev -y
sudo apt-get install pkg-config cmake libfreetype6-dev libnotify-dev libsdl2-dev libsqlite3-dev -y
- name: Prepare Linux (fancy)
if: contains(matrix.os, 'ubuntu') && matrix.fancy

View file

@ -317,12 +317,18 @@ endif()
find_package(ZLIB)
find_package(Crypto)
find_package(Curl)
if(VIDEORECORDER)
find_package(FFMPEG)
endif()
find_package(Freetype)
if(DOWNLOAD_GTEST)
find_package(Git)
endif()
find_package(GLEW)
find_package(GTest)
if(UPNP)
find_package(Miniupnpc)
endif()
if(MYSQL)
find_package(MySQL)
else()
@ -331,15 +337,10 @@ endif()
find_package(Ogg)
find_package(Opus)
find_package(Opusfile)
if(UPNP)
find_package(Miniupnpc)
endif()
find_package(Pnglite)
find_package(PythonInterp 3)
find_package(SDL2)
if(VIDEORECORDER)
find_package(FFMPEG)
endif()
find_package(SQLite3)
find_package(Threads)
find_package(Wavpack)
if(WEBSOCKETS)
@ -395,6 +396,9 @@ show_dependency_status("Curl" CURL)
if(TARGET_OS AND TARGET_OS STREQUAL "mac")
show_dependency_status("Dmg tools" DMGTOOLS)
endif()
if(VIDEORECORDER)
show_dependency_status("FFmpeg" FFMPEG)
endif()
show_dependency_status("Freetype" FREETYPE)
if(DOWNLOAD_GTEST)
show_dependency_status("Git" GIT)
@ -404,6 +408,9 @@ show_dependency_status("GTest" GTEST)
if(TARGET_OS AND TARGET_OS STREQUAL "mac")
show_dependency_status("Hdiutil" HDIUTIL)
endif()
if(UPNP)
show_dependency_status("Miniupnpc" MINIUPNPC)
endif()
if(MYSQL)
show_dependency_status("MySQL" MYSQL)
endif()
@ -411,15 +418,10 @@ show_dependency_status("Ogg" OGG)
show_dependency_status("OpenSSL Crypto" CRYPTO)
show_dependency_status("Opus" OPUS)
show_dependency_status("Opusfile" OPUSFILE)
if(UPNP)
show_dependency_status("Miniupnpc" MINIUPNPC)
endif()
show_dependency_status("Pnglite" PNGLITE)
show_dependency_status("PythonInterp" PYTHONINTERP)
show_dependency_status("SDL2" SDL2)
if(VIDEORECORDER)
show_dependency_status("FFmpeg" FFMPEG)
endif()
show_dependency_status("SQLite3" SQLite3)
show_dependency_status("Wavpack" WAVPACK)
show_dependency_status("Zlib" ZLIB)
if(WEBSOCKETS)
@ -432,6 +434,9 @@ endif()
if(NOT(PYTHONINTERP_FOUND))
message(SEND_ERROR "You must install Python to compile DDNet")
endif()
if(NOT(SQLite3_FOUND))
message(SEND_ERROR "You must install SQLite3 to compile DDNet")
endif()
if(MYSQL AND NOT(MYSQL_FOUND))
message(SEND_ERROR "You must install MySQL to compile the DDNet server with MySQL support")
@ -1814,21 +1819,25 @@ set_src(ANTIBOT_SRC GLOB src/antibot
antibot_null.cpp
)
set_src(ENGINE_SERVER GLOB src/engine/server
set_src(ENGINE_SERVER GLOB_RECURSE src/engine/server
antibot.cpp
antibot.h
authmanager.cpp
authmanager.h
databases/connection.cpp
databases/connection.h
databases/connection_pool.cpp
databases/connection_pool.h
databases/mysql.cpp
databases/mysql.h
databases/sqlite.cpp
databases/sqlite.h
name_ban.cpp
name_ban.h
register.cpp
register.h
server.cpp
server.h
sql_connector.cpp
sql_connector.h
sql_server.cpp
sql_server.h
sql_string_helpers.cpp
sql_string_helpers.h
upnp.cpp
@ -1877,10 +1886,6 @@ set_src(GAME_SERVER GLOB_RECURSE src/game/server
save.h
score.cpp
score.h
score/file_score.cpp
score/file_score.h
score/sql_score.cpp
score/sql_score.h
teams.cpp
teams.h
teehistorian.cpp
@ -1910,6 +1915,7 @@ endif()
set(LIBS_SERVER
${LIBS}
${MYSQL_LIBRARIES}
${SQLite3_LIBRARIES}
${TARGET_ANTIBOT}
${MINIUPNPC_LIBRARIES}
# Add pthreads (on non-Windows) at the end, so that other libraries can depend
@ -2484,7 +2490,7 @@ foreach(target ${TARGETS_OWN})
target_include_directories(${target} PRIVATE ${PROJECT_BINARY_DIR}/src)
target_include_directories(${target} PRIVATE src)
target_compile_definitions(${target} PRIVATE $<$<CONFIG:Debug>:CONF_DEBUG>)
target_include_directories(${target} PRIVATE ${ZLIB_INCLUDE_DIRS})
target_include_directories(${target} PRIVATE ${SQLite3_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS})
target_compile_definitions(${target} PRIVATE GLEW_STATIC)
if(CRYPTO_FOUND)
target_compile_definitions(${target} PRIVATE CONF_OPENSSL)

37
cmake/FindSQLite3.cmake Normal file
View file

@ -0,0 +1,37 @@
if(NOT PREFER_BUNDLED_LIBS)
set(CMAKE_MODULE_PATH ${ORIGINAL_CMAKE_MODULE_PATH})
find_package(SQLite3)
set(CMAKE_MODULE_PATH ${OWN_CMAKE_MODULE_PATH})
endif()
if(NOT SQLite3_FOUND)
if(NOT CMAKE_CROSSCOMPILING)
find_package(PkgConfig QUIET)
pkg_check_modules(PC_SQLite3 sqlite3)
endif()
set_extra_dirs_lib(SQLite3 sqlite3)
find_library(SQLite3_LIBRARY
NAMES sqlite3
HINTS ${HINTS_SQLite3_LIBDIR} ${PC_SQLite3_LIBDIR} ${PC_SQLite3_LIBRARY_DIRS}
PATHS ${PATHS_SQLite3_LIBDIR}
${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
)
set_extra_dirs_include(SQLite3 sqlite3 "${SQLite3_LIBRARY}")
find_path(SQLite3_INCLUDEDIR sqlite3.h
PATH_SUFFIXES sqlite3
HINTS ${HINTS_SQLite3_INCLUDEDIR} ${PC_SQLite3_INCLUDEDIR} ${PC_SQLite3_INCLUDE_DIRS}
PATHS ${PATHS_SQLite3_INCLUDEDIR}
${CROSSCOMPILING_NO_CMAKE_SYSTEM_PATH}
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(SQLite3 DEFAULT_MSG SQLite3_INCLUDEDIR SQLite3_LIBRARY)
mark_as_advanced(SQLite3_INCLUDEDIR SQLite3_LIBRARY)
endif()
if(SQLite3_FOUND)
is_bundled(SQLite3_BUNDLED "${SQLite3_LIBRARY}")
set(SQLite3_LIBRARIES ${SQLite3_LIBRARY})
set(SQLite3_INCLUDE_DIRS ${SQLite3_INCLUDEDIR})
endif()

84
scripts/import_file_score.py Executable file
View file

@ -0,0 +1,84 @@
#!/usr/bin/env python3
from collections import namedtuple
from decimal import Decimal
import os.path
import re
import sqlite3
import sys
def chunks(l, n):
for i in range(0, len(l), n):
yield l[i:i+n]
class Record(namedtuple('Record', 'name time checkpoints')):
@staticmethod
def parse(lines):
if len(lines) != 3:
raise ValueError("wrong amount of lines for record")
name = lines[0]
time = Decimal(lines[1])
checkpoints_str = lines[2].split(' ')
if len(checkpoints_str) != 26 or checkpoints_str[25] != "":
raise ValueError("wrong amount of checkpoint times: {}".format(len(checkpoints_str)))
checkpoints_str = checkpoints_str[:25]
checkpoints = tuple(Decimal(c) for c in checkpoints_str)
return Record(name=name, time=time, checkpoints=checkpoints)
def unparse(self):
return "\n".join([self.name, str(self.time), " ".join([str(cp) for cp in self.checkpoints] + [""]), ""])
def read_records(file):
contents = file.read().splitlines()
return [Record.parse(c) for c in chunks(contents, 3)]
MAP_RE=re.compile(r"^(?P<map>.*)_record\.dtb$")
def main():
import argparse
p = argparse.ArgumentParser(description="Merge multiple DDNet race database files", formatter_class=argparse.ArgumentDefaultsHelpFormatter)
p.add_argument("--out", default="ddnet-server.sqlite", help="Output SQLite database")
p.add_argument("in_", metavar="IN", nargs='+', help="Text score databases to import; must have the format MAPNAME_record.dtb")
p.add_argument("--dry-run", "-n", action='store_true', help="Don't write out the resulting SQLite database")
p.add_argument("--stats", action='store_true', help="Display some stats at the end of the import process")
args = p.parse_args()
records = {}
for in_ in args.in_:
m = MAP_RE.match(os.path.basename(in_))
if not m:
raise ValueError("Invalid text score database name, does not end in '_record.dtb': {}".format(in_))
map = m.group("map")
if map in records:
raise ValueError("Two text score databases refer to the same map: {}".format(in_))
with open(in_) as f:
records[map] = read_records(f)
if not args.dry_run:
conn = sqlite3.connect(args.out)
c = conn.cursor()
c.execute("CREATE TABLE IF NOT EXISTS record_race ("
"Map VARCHAR(128) COLLATE BINARY NOT NULL, "
"Name VARCHAR(16) COLLATE BINARY NOT NULL, "
"Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
"Time FLOAT DEFAULT 0, "
"Server CHAR(4), " +
"".join("cp{} FLOAT DEFAULT 0, ".format(i + 1) for i in range(25)) +
"GameID VARCHAR(64), "
"DDNet7 BOOL DEFAULT FALSE"
");");
c.executemany(
"INSERT INTO record_race (Map, Name, Time, Server, " +
"".join("cp{}, ".format(i + 1) for i in range(25)) +
"GameID, DDNet7) " +
"VALUES ({})".format(",".join("?" * 31)),
[(map, r.name, float(r.time), "TEXT", *[float(c) for c in r.checkpoints], None, False) for map in records for r in records[map]]
)
conn.commit()
conn.close()
if args.stats:
print("Number of imported text databases: {}".format(len(records)), file=sys.stderr)
print("Number of imported ranks: {}".format(sum(len(r) for r in records.values()), file=sys.stderr))
if __name__ == '__main__':
sys.exit(main())

90
scripts/move_sqlite.py Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# This script is intended to be called automatically every day (e.g. via cron).
# It only output stuff if new ranks have to be inserted. Therefore the output
# may be redirected to email notifying about manual action to transfer the
# ranks to MySQL.
#
# Configure cron as the user running the DDNet-Server processes
#
# $ crontab -e
# 30 5 * * * /path/to/this/script/move_sqlite.py --from /path/to/ddnet-server.sqlite
#
# Afterwards configure a MTA (e.g. postfix) and the users email address.
import sqlite3
import argparse
from time import strftime
import os
def sqlite_num_transfer(conn, table):
c = conn.cursor()
c.execute('SELECT COUNT(*) FROM {}'.format(table))
num = c.fetchone()[0]
return num
def transfer(file_from, file_to):
conn_to = sqlite3.connect(file_to, isolation_level='EXCLUSIVE')
cursor_to = conn_to.cursor()
conn_from = sqlite3.connect(file_from, isolation_level='EXCLUSIVE')
for line in conn_from.iterdump():
cursor_to.execute(line)
print(line)
cursor_to.close()
conn_to.commit()
conn_to.close()
cursor_from = conn_from.cursor()
cursor_from.execute('DELETE FROM record_race')
cursor_from.execute('DELETE FROM record_teamrace')
cursor_from.execute('DELETE FROM record_saves')
cursor_from.close()
conn_from.commit()
conn_from.close()
def main():
default_output = 'ddnet-server-' + strftime('%Y-%m-%d') + '.sqlite'
parser = argparse.ArgumentParser(
description='Move DDNet ranks, teamranks and saves from a possible active SQLite3 to a new one',
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument('--from', '-f', dest='f',
default='ddnet-server.sqlite',
help='Input file where ranks are deleted from when moved successfully (default: ddnet-server.sqlite)')
parser.add_argument('--to', '-t',
default=default_output,
help='Output file where ranks are saved adds current date by default')
args = parser.parse_args()
conn = sqlite3.connect(args.f)
num_ranks = sqlite_num_transfer(conn, 'record_race')
num_teamranks = sqlite_num_transfer(conn, 'record_teamrace')
num_saves = sqlite_num_transfer(conn, 'record_saves')
num = num_ranks + num_teamranks + num_saves
conn.close()
if num == 0:
return
print('{} new entries in backup database found ({} ranks, {} teamranks, {} saves'.format(num, num_ranks, num_teamranks, num_saves))
print('Moving entries from {} to {}'.format(
os.path.abspath(args.f),
os.path.abspath(args.to)))
sql_file = 'ddnet-server-' + strftime('%Y-%m-%d') + '.sql'
print("You can use the following commands to import the entries to MySQL (use sed 's/record_/prefix_/' for other database prefixes):")
print()
print((" echo '.dump --preserve-rowids' | sqlite3 {} | " + # including rowids, this forces sqlite to name all columns in each INSERT statement
"grep -E '^INSERT INTO record_(race|teamrace|saves)' | " + # filter out inserts
"sed -e 's/rowid,//' -e 's/VALUES([0-9]*,/VALUES(/' > {}") # filter out rowids again
.format(os.path.abspath(args.to), sql_file))
print(" mysql -u teeworlds -p'PW2' teeworlds < {}".format(sql_file))
print()
print("When the ranks are transfered successfully to mysql {} and {} can be removed".format(
os.path.abspath(args.f), os.path.abspath(args.to)))
print()
print("Log of the transfer:")
print()
transfer(args.f, args.to)
if __name__ == '__main__':
main()

View file

@ -3,64 +3,6 @@
#include "../system.h"
/*
atomic_inc - should return the value after increment
atomic_dec - should return the value after decrement
atomic_compswap - should return the value before the eventual swap
sync_barrier - creates a full hardware fence
*/
#if defined(__GNUC__)
inline unsigned atomic_inc(volatile unsigned *pValue)
{
return __sync_add_and_fetch(pValue, 1);
}
inline unsigned atomic_dec(volatile unsigned *pValue)
{
return __sync_add_and_fetch(pValue, -1);
}
inline unsigned atomic_compswap(volatile unsigned *pValue, unsigned comperand, unsigned value)
{
return __sync_val_compare_and_swap(pValue, comperand, value);
}
inline void sync_barrier()
{
__sync_synchronize();
}
#elif defined(_MSC_VER)
#include <intrin.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
inline unsigned atomic_inc(volatile unsigned *pValue)
{
return _InterlockedIncrement((volatile long *)pValue);
}
inline unsigned atomic_dec(volatile unsigned *pValue)
{
return _InterlockedDecrement((volatile long *)pValue);
}
inline unsigned atomic_compswap(volatile unsigned *pValue, unsigned comperand, unsigned value)
{
return _InterlockedCompareExchange((volatile long *)pValue, (long)value, (long)comperand);
}
inline void sync_barrier()
{
MemoryBarrier();
}
#else
#error missing atomic implementation for this compiler
#endif
class semaphore
{
SEMAPHORE sem;

View file

@ -43,6 +43,25 @@ extern "C"
}
#endif
/*
sync_barrier - creates a full hardware fence
*/
#if defined(__GNUC__)
inline void sync_barrier()
{
__sync_synchronize();
}
#elif defined(_MSC_VER)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
inline void sync_barrier()
{
MemoryBarrier();
}
#else
#error missing atomic implementation for this compiler
#endif
// ------------ CGraphicsBackend_Threaded
void CGraphicsBackend_Threaded::ThreadFunc(void *pUser)

View file

@ -252,7 +252,7 @@ public:
virtual void OnMapChange(char *pNewMapName, int MapNameSize) = 0;
// FullShutdown is true if the program is about to exit (not if the map is changed)
virtual void OnShutdown(bool FullShutdown = false) = 0;
virtual void OnShutdown() = 0;
virtual void OnTick() = 0;
virtual void OnPreSnap() = 0;

View file

@ -0,0 +1,84 @@
#include "connection.h"
#include <engine/shared/protocol.h>
void IDbConnection::FormatCreateRace(char *aBuf, unsigned int BufferSize)
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_race ("
"Map VARCHAR(128) COLLATE %s NOT NULL, "
"Name VARCHAR(%d) COLLATE %s NOT NULL, "
"Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
"Time FLOAT DEFAULT 0, "
"Server CHAR(4), "
"cp1 FLOAT DEFAULT 0, cp2 FLOAT DEFAULT 0, cp3 FLOAT DEFAULT 0, "
"cp4 FLOAT DEFAULT 0, cp5 FLOAT DEFAULT 0, cp6 FLOAT DEFAULT 0, "
"cp7 FLOAT DEFAULT 0, cp8 FLOAT DEFAULT 0, cp9 FLOAT DEFAULT 0, "
"cp10 FLOAT DEFAULT 0, cp11 FLOAT DEFAULT 0, cp12 FLOAT DEFAULT 0, "
"cp13 FLOAT DEFAULT 0, cp14 FLOAT DEFAULT 0, cp15 FLOAT DEFAULT 0, "
"cp16 FLOAT DEFAULT 0, cp17 FLOAT DEFAULT 0, cp18 FLOAT DEFAULT 0, "
"cp19 FLOAT DEFAULT 0, cp20 FLOAT DEFAULT 0, cp21 FLOAT DEFAULT 0, "
"cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, "
"cp25 FLOAT DEFAULT 0, "
"GameID VARCHAR(64), "
"DDNet7 BOOL DEFAULT FALSE"
");",
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate());
}
void IDbConnection::FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType)
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_teamrace ("
"Map VARCHAR(128) COLLATE %s NOT NULL, "
"Name VARCHAR(%d) COLLATE %s NOT NULL, "
"Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
"Time FLOAT DEFAULT 0, "
"ID %s NOT NULL, " // VARBINARY(16) for MySQL and BLOB for SQLite
"GameID VARCHAR(64), "
"DDNet7 BOOL DEFAULT FALSE"
");",
GetPrefix(), BinaryCollate(), MAX_NAME_LENGTH, BinaryCollate(), pIdType);
}
void IDbConnection::FormatCreateMaps(char *aBuf, unsigned int BufferSize)
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_maps ("
"Map VARCHAR(128) COLLATE %s NOT NULL, "
"Server VARCHAR(32) COLLATE %s NOT NULL, "
"Mapper VARCHAR(128) COLLATE %s NOT NULL, "
"Points INT DEFAULT 0, "
"Stars INT DEFAULT 0, "
"Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, "
"PRIMARY KEY (Map)"
");",
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
}
void IDbConnection::FormatCreateSaves(char *aBuf, unsigned int BufferSize)
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_saves ("
"Savegame TEXT COLLATE %s NOT NULL, "
"Map VARCHAR(128) COLLATE %s NOT NULL, "
"Code VARCHAR(128) COLLATE %s NOT NULL, "
"Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
"Server CHAR(4), "
"DDNet7 BOOL DEFAULT FALSE, "
"SaveID VARCHAR(36) DEFAULT NULL, "
"PRIMARY KEY (Map, Code)"
");",
GetPrefix(), BinaryCollate(), BinaryCollate(), BinaryCollate());
}
void IDbConnection::FormatCreatePoints(char *aBuf, unsigned int BufferSize)
{
str_format(aBuf, BufferSize,
"CREATE TABLE IF NOT EXISTS %s_points ("
"Name VARCHAR(%d) COLLATE %s NOT NULL, "
"Points INT DEFAULT 0, "
"PRIMARY KEY (Name)"
");",
GetPrefix(), MAX_NAME_LENGTH, BinaryCollate());
}

View file

@ -0,0 +1,85 @@
#ifndef ENGINE_SERVER_DATABASES_CONNECTION_H
#define ENGINE_SERVER_DATABASES_CONNECTION_H
#include <base/system.h>
class IConsole;
// can hold one PreparedStatement with Results
class IDbConnection
{
public:
IDbConnection(const char *pPrefix)
{
str_copy(m_aPrefix, pPrefix, sizeof(m_aPrefix));
}
virtual ~IDbConnection() {}
IDbConnection& operator=(const IDbConnection&) = delete;
virtual void Print(IConsole *pConsole, const char *Mode) = 0;
// copies the credentials, not the active connection
virtual IDbConnection *Copy() = 0;
// returns the database prefix
const char *GetPrefix() { return m_aPrefix; }
virtual const char *BinaryCollate() const = 0;
// can be inserted into queries to convert a timestamp variable to the unix timestamp
virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize) = 0;
// since MySQL automatically converts timestamps to utc, meanwhile sqlite code has to
// explicitly convert before inserting timestamps, NOTE: CURRENT_TIMESTAMP in SQLite is UTC by
// default and doesn't have to be converted
virtual const char *InsertTimestampAsUtc() const = 0;
// can be used in the context of `LIKE Map`, adds `? COLLATE`
virtual const char *CollateNocase() const = 0;
enum Status
{
IN_USE,
SUCCESS,
FAILURE,
};
// tries to allocate the connection from the pool established
virtual Status Connect() = 0;
// has to be called to return the connection back to the pool
virtual void Disconnect() = 0;
// get exclusive read/write access to the database
virtual void Lock(const char *pTable) = 0;
virtual void Unlock() = 0;
// ? for Placeholders, connection has to be established, can overwrite previous prepared statements
virtual void PrepareStatement(const char *pStmt) = 0;
// PrepareStatement has to be called beforehand,
virtual void BindString(int Idx, const char *pString) = 0;
virtual void BindBlob(int Idx, unsigned char *pBlob, int Size) = 0;
virtual void BindInt(int Idx, int Value) = 0;
virtual void BindFloat(int Idx, float Value) = 0;
// executes the query and returns if a result row exists and selects it
// when called multiple times the next row is selected
virtual bool Step() = 0;
virtual bool IsNull(int Col) const = 0;
virtual float GetFloat(int Col) const = 0;
virtual int GetInt(int Col) const = 0;
// ensures that the string is null terminated
virtual void GetString(int Col, char *pBuffer, int BufferSize) const = 0;
// returns number of bytes read into the buffer
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const = 0;
// SQL statements, that can't be abstracted, has side effects to the result
virtual void AddPoints(const char *pPlayer, int Points) = 0;
private:
char m_aPrefix[64];
protected:
void FormatCreateRace(char *aBuf, unsigned int BufferSize);
void FormatCreateTeamrace(char *aBuf, unsigned int BufferSize, const char *pIdType);
void FormatCreateMaps(char *aBuf, unsigned int BufferSize);
void FormatCreateSaves(char *aBuf, unsigned int BufferSize);
void FormatCreatePoints(char *aBuf, unsigned int BufferSize);
};
#endif // ENGINE_SERVER_DATABASES_CONNECTION_H

View file

@ -0,0 +1,228 @@
#include "connection_pool.h"
#include "connection.h"
#include <engine/console.h>
#if defined(CONF_SQL)
#include <cppconn/exception.h>
#endif
#include <stdexcept>
// helper struct to hold thread data
struct CSqlExecData
{
CSqlExecData(
CDbConnectionPool::FRead pFunc,
std::unique_ptr<const ISqlData> pThreadData,
const char *pName);
CSqlExecData(
CDbConnectionPool::FWrite pFunc,
std::unique_ptr<const ISqlData> pThreadData,
const char *pName);
~CSqlExecData() {}
enum
{
READ_ACCESS,
WRITE_ACCESS,
} m_Mode;
union
{
CDbConnectionPool::FRead m_pReadFunc;
CDbConnectionPool::FWrite m_pWriteFunc;
} m_Ptr;
std::unique_ptr<const ISqlData> m_pThreadData;
const char *m_pName;
};
CSqlExecData::CSqlExecData(
CDbConnectionPool::FRead pFunc,
std::unique_ptr<const ISqlData> pThreadData,
const char *pName) :
m_Mode(READ_ACCESS),
m_pThreadData(std::move(pThreadData)),
m_pName(pName)
{
m_Ptr.m_pReadFunc = pFunc;
}
CSqlExecData::CSqlExecData(
CDbConnectionPool::FWrite pFunc,
std::unique_ptr<const ISqlData> pThreadData,
const char *pName) :
m_Mode(WRITE_ACCESS),
m_pThreadData(std::move(pThreadData)),
m_pName(pName)
{
m_Ptr.m_pWriteFunc = pFunc;
}
CDbConnectionPool::CDbConnectionPool() :
m_NumElem(),
FirstElem(0),
LastElem(0)
{
thread_init_and_detach(CDbConnectionPool::Worker, this, "database worker thread");
}
CDbConnectionPool::~CDbConnectionPool()
{
}
void CDbConnectionPool::Print(IConsole *pConsole, Mode DatabaseMode)
{
const char *ModeDesc[] = {"Read", "Write", "WriteBackup"};
for(unsigned int i = 0; i < m_aapDbConnections[DatabaseMode].size(); i++)
{
m_aapDbConnections[DatabaseMode][i]->Print(pConsole, ModeDesc[DatabaseMode]);
}
}
void CDbConnectionPool::RegisterDatabase(std::unique_ptr<IDbConnection> pDatabase, Mode DatabaseMode)
{
if(DatabaseMode < 0 || NUM_MODES <= DatabaseMode)
return;
m_aapDbConnections[DatabaseMode].push_back(std::move(pDatabase));
}
void CDbConnectionPool::Execute(
FRead pFunc,
std::unique_ptr<const ISqlData> pThreadData,
const char *pName)
{
m_aTasks[FirstElem++].reset(new CSqlExecData(pFunc, std::move(pThreadData), pName));
FirstElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]);
m_NumElem.signal();
}
void CDbConnectionPool::ExecuteWrite(
FWrite pFunc,
std::unique_ptr<const ISqlData> pThreadData,
const char *pName)
{
m_aTasks[FirstElem++].reset(new CSqlExecData(pFunc, std::move(pThreadData), pName));
FirstElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]);
m_NumElem.signal();
}
void CDbConnectionPool::OnShutdown()
{
m_Shutdown.store(true);
m_NumElem.signal();
int i = 0;
while(m_Shutdown.load())
{
if (i > 600) {
dbg_msg("sql", "Waited 60 seconds for score-threads to complete, quitting anyway");
break;
}
// print a log about every two seconds
if (i % 20 == 0)
dbg_msg("sql", "Waiting for score-threads to complete (%ds)", i / 10);
++i;
thread_sleep(100000);
}
}
void CDbConnectionPool::Worker(void *pUser)
{
CDbConnectionPool *pThis = (CDbConnectionPool *)pUser;
pThis->Worker();
}
void CDbConnectionPool::Worker()
{
while(1)
{
m_NumElem.wait();
auto pThreadData = std::move(m_aTasks[LastElem++]);
// work through all database jobs after OnShutdown is called before exiting the thread
if(pThreadData == nullptr)
{
m_Shutdown.store(false);
return;
}
LastElem %= sizeof(m_aTasks) / sizeof(m_aTasks[0]);
bool Success = false;
switch(pThreadData->m_Mode)
{
case CSqlExecData::READ_ACCESS:
{
for(int i = 0; i < (int)m_aapDbConnections[Mode::READ].size(); i++)
{
if(ExecSqlFunc(m_aapDbConnections[Mode::READ][i].get(), pThreadData.get(), false))
{
Success = true;
break;
}
}
} break;
case CSqlExecData::WRITE_ACCESS:
{
for(int i = 0; i < (int)m_aapDbConnections[Mode::WRITE].size(); i++)
{
if(ExecSqlFunc(m_aapDbConnections[Mode::WRITE][i].get(), pThreadData.get(), false))
{
Success = true;
break;
}
}
if(!Success)
{
for(int i = 0; i < (int)m_aapDbConnections[Mode::WRITE_BACKUP].size(); i++)
{
if(ExecSqlFunc(m_aapDbConnections[Mode::WRITE_BACKUP][i].get(), pThreadData.get(), true))
{
Success = true;
break;
}
}
}
} break;
}
if(Success)
dbg_msg("sql", "%s done", pThreadData->m_pName);
}
}
bool CDbConnectionPool::ExecSqlFunc(IDbConnection *pConnection, CSqlExecData *pData, bool Failure)
{
if(pConnection->Connect() != IDbConnection::SUCCESS)
return false;
bool Success = false;
try
{
switch(pData->m_Mode)
{
case CSqlExecData::READ_ACCESS:
if(pData->m_Ptr.m_pReadFunc(pConnection, pData->m_pThreadData.get()))
Success = true;
break;
case CSqlExecData::WRITE_ACCESS:
if(pData->m_Ptr.m_pWriteFunc(pConnection, pData->m_pThreadData.get(), Failure))
Success = true;
break;
}
}
#if defined(CONF_SQL)
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}
#endif
catch (std::runtime_error &e)
{
dbg_msg("sql", "SQLite Error: %s", e.what());
}
catch (...)
{
dbg_msg("sql", "Unexpected exception caught");
}
pConnection->Unlock();
pConnection->Disconnect();
if(!Success)
dbg_msg("sql", "%s failed", pData->m_pName);
return Success;
}

View file

@ -0,0 +1,66 @@
#ifndef ENGINE_SERVER_DATABASES_CONNECTION_POOL_H
#define ENGINE_SERVER_DATABASES_CONNECTION_POOL_H
#include <base/tl/threading.h>
#include <atomic>
#include <memory>
#include <vector>
class IDbConnection;
struct ISqlData
{
virtual ~ISqlData() {};
};
class IConsole;
class CDbConnectionPool
{
public:
CDbConnectionPool();
~CDbConnectionPool();
CDbConnectionPool& operator=(const CDbConnectionPool&) = delete;
typedef bool (*FRead)(IDbConnection *, const ISqlData *);
typedef bool (*FWrite)(IDbConnection *, const ISqlData *, bool);
enum Mode
{
READ,
WRITE,
WRITE_BACKUP,
NUM_MODES,
};
void Print(IConsole *pConsole, Mode DatabaseMode);
void RegisterDatabase(std::unique_ptr<IDbConnection> pDatabase, Mode DatabaseMode);
void Execute(
FRead pFunc,
std::unique_ptr<const ISqlData> pSqlRequestData,
const char *pName);
// writes to WRITE_BACKUP server in case of failure
void ExecuteWrite(
FWrite pFunc,
std::unique_ptr<const ISqlData> pSqlRequestData,
const char *pName);
void OnShutdown();
private:
std::vector<std::unique_ptr<IDbConnection>> m_aapDbConnections[NUM_MODES];
static void Worker(void *pUser);
void Worker();
bool ExecSqlFunc(IDbConnection *pConnection, struct CSqlExecData *pData, bool Failure);
std::atomic_bool m_Shutdown;
semaphore m_NumElem;
int FirstElem;
int LastElem;
std::unique_ptr<struct CSqlExecData> m_aTasks[512];
};
#endif // ENGINE_SERVER_DATABASES_CONNECTION_POOL_H

View file

@ -0,0 +1,328 @@
#include "mysql.h"
#include <base/tl/threading.h>
#include <engine/console.h>
#if defined(CONF_SQL)
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/prepared_statement.h>
#include <cppconn/statement.h>
#endif
#include <string>
lock CMysqlConnection::m_SqlDriverLock;
CMysqlConnection::CMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup) :
IDbConnection(pPrefix),
#if defined(CONF_SQL)
m_NewQuery(false),
m_Locked(false),
#endif
m_Port(Port),
m_Setup(Setup),
m_InUse(false)
{
str_copy(m_aDatabase, pDatabase, sizeof(m_aDatabase));
str_copy(m_aUser, pUser, sizeof(m_aUser));
str_copy(m_aPass, pPass, sizeof(m_aPass));
str_copy(m_aIp, pIp, sizeof(m_aIp));
#ifndef CONF_SQL
dbg_msg("sql", "Adding MySQL server failed due to MySQL support not enabled during compile time");
#endif
}
CMysqlConnection::~CMysqlConnection()
{
#if defined(CONF_SQL)
m_pStmt.release();
m_pPreparedStmt.release();
m_pConnection.release();
#endif
}
void CMysqlConnection::Print(IConsole *pConsole, const char *Mode)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"MySQL-%s: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
Mode, m_aDatabase, GetPrefix(), m_aUser, m_aIp, m_Port);
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
}
CMysqlConnection *CMysqlConnection::Copy()
{
return new CMysqlConnection(m_aDatabase, GetPrefix(), m_aUser, m_aPass, m_aIp, m_Port, m_Setup);
}
void CMysqlConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
{
str_format(aBuf, BufferSize, "UNIX_TIMESTAMP(%s)", pTimestamp);
}
IDbConnection::Status CMysqlConnection::Connect()
{
#if defined(CONF_SQL)
if(m_InUse.exchange(true))
return Status::IN_USE;
m_NewQuery = true;
if(m_pConnection != nullptr)
{
try
{
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
return Status::SUCCESS;
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}
catch (const std::exception& ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.what());
}
catch (const std::string& ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.c_str());
}
catch (...)
{
dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector");
}
dbg_msg("sql", "FAILURE: SQL connection failed, trying to reconnect");
}
try
{
m_pConnection.release();
m_pPreparedStmt.release();
m_pResults.release();
sql::ConnectOptionsMap connection_properties;
connection_properties["hostName"] = sql::SQLString(m_aIp);
connection_properties["port"] = m_Port;
connection_properties["userName"] = sql::SQLString(m_aUser);
connection_properties["password"] = sql::SQLString(m_aPass);
connection_properties["OPT_CONNECT_TIMEOUT"] = 10;
connection_properties["OPT_READ_TIMEOUT"] = 10;
connection_properties["OPT_WRITE_TIMEOUT"] = 20;
connection_properties["OPT_RECONNECT"] = true;
connection_properties["OPT_CHARSET_NAME"] = sql::SQLString("utf8mb4");
connection_properties["OPT_SET_CHARSET_NAME"] = sql::SQLString("utf8mb4");
// Create connection
{
scope_lock GlobalLockScope(&m_SqlDriverLock);
sql::Driver *pDriver = get_driver_instance();
m_pConnection.reset(pDriver->connect(connection_properties));
}
// Create Statement
m_pStmt = std::unique_ptr<sql::Statement>(m_pConnection->createStatement());
// Apparently OPT_CHARSET_NAME and OPT_SET_CHARSET_NAME are not enough
m_pStmt->execute("SET CHARACTER SET utf8mb4;");
if(m_Setup)
{
char aBuf[1024];
// create database
str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase);
m_pStmt->execute(aBuf);
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
FormatCreateRace(aBuf, sizeof(aBuf));
m_pStmt->execute(aBuf);
FormatCreateTeamrace(aBuf, sizeof(aBuf), "VARBINARY(16)");
m_pStmt->execute(aBuf);
FormatCreateMaps(aBuf, sizeof(aBuf));
m_pStmt->execute(aBuf);
FormatCreateSaves(aBuf, sizeof(aBuf));
m_pStmt->execute(aBuf);
FormatCreatePoints(aBuf, sizeof(aBuf));
m_pStmt->execute(aBuf);
m_Setup = false;
}
else
{
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
}
dbg_msg("sql", "sql connection established");
return Status::SUCCESS;
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}
catch (const std::exception& ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.what());
}
catch (const std::string& ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.c_str());
}
catch (...)
{
dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector");
}
m_InUse.store(false);
#endif
dbg_msg("sql", "FAILURE: sql connection failed");
return Status::FAILURE;
}
void CMysqlConnection::Disconnect()
{
m_InUse.store(false);
}
void CMysqlConnection::Lock(const char *pTable)
{
#if defined(CONF_SQL)
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "LOCK TABLES %s;", pTable);
m_pStmt->execute(aBuf);
m_Locked = true;
#endif
}
void CMysqlConnection::Unlock()
{
#if defined(CONF_SQL)
if(m_Locked)
{
m_pStmt->execute("UNLOCK TABLES;");
m_Locked = false;
}
#endif
}
void CMysqlConnection::PrepareStatement(const char *pStmt)
{
#if defined(CONF_SQL)
m_pPreparedStmt.reset(m_pConnection->prepareStatement(pStmt));
m_NewQuery = true;
#endif
}
void CMysqlConnection::BindString(int Idx, const char *pString)
{
#if defined(CONF_SQL)
m_pPreparedStmt->setString(Idx, pString);
m_NewQuery = true;
#endif
}
void CMysqlConnection::BindBlob(int Idx, unsigned char *pBlob, int Size)
{
#if defined(CONF_SQL)
// copy blob into string
auto Blob = std::string(pBlob, pBlob+Size);
m_pPreparedStmt->setString(Idx, Blob);
m_NewQuery = true;
#endif
}
void CMysqlConnection::BindInt(int Idx, int Value)
{
#if defined(CONF_SQL)
m_pPreparedStmt->setInt(Idx, Value);
m_NewQuery = true;
#endif
}
void CMysqlConnection::BindFloat(int Idx, float Value)
{
#if defined(CONF_SQL)
m_pPreparedStmt->setDouble(Idx, (double)Value);
m_NewQuery = true;
#endif
}
bool CMysqlConnection::Step()
{
#if defined(CONF_SQL)
if(m_NewQuery)
{
m_NewQuery = false;
m_pResults.reset(m_pPreparedStmt->executeQuery());
}
return m_pResults->next();
#else
return false;
#endif
}
bool CMysqlConnection::IsNull(int Col) const
{
#if defined(CONF_SQL)
return m_pResults->isNull(Col);
#else
return false;
#endif
}
float CMysqlConnection::GetFloat(int Col) const
{
#if defined(CONF_SQL)
return (float)m_pResults->getDouble(Col);
#else
return 0.0;
#endif
}
int CMysqlConnection::GetInt(int Col) const
{
#if defined(CONF_SQL)
return m_pResults->getInt(Col);
#else
return 0;
#endif
}
void CMysqlConnection::GetString(int Col, char *pBuffer, int BufferSize) const
{
#if defined(CONF_SQL)
auto String = m_pResults->getString(Col);
str_copy(pBuffer, String.c_str(), BufferSize);
#endif
}
int CMysqlConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const
{
#if defined(CONF_SQL)
auto Blob = m_pResults->getBlob(Col);
Blob->read((char *)pBuffer, BufferSize);
return Blob->gcount();
#else
return 0;
#endif
}
void CMysqlConnection::AddPoints(const char *pPlayer, int Points)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"INSERT INTO %s_points(Name, Points) "
"VALUES (?, ?) "
"ON DUPLICATE KEY UPDATE Points=Points+?;",
GetPrefix());
PrepareStatement(aBuf);
BindString(1, pPlayer);
BindInt(2, Points);
BindInt(3, Points);
Step();
}

View file

@ -0,0 +1,82 @@
#ifndef ENGINE_SERVER_DATABASES_MYSQL_H
#define ENGINE_SERVER_DATABASES_MYSQL_H
#include <engine/server/databases/connection.h>
#include <atomic>
#include <memory>
class lock;
namespace sql {
class Connection;
class PreparedStatement;
class ResultSet;
class Statement;
} /* namespace sql */
class CMysqlConnection : public IDbConnection
{
public:
CMysqlConnection(
const char *pDatabase,
const char *pPrefix,
const char *pUser,
const char *pPass,
const char *pIp,
int Port,
bool Setup);
virtual ~CMysqlConnection();
virtual void Print(IConsole *pConsole, const char *Mode);
virtual CMysqlConnection *Copy();
virtual const char *BinaryCollate() const { return "utf8mb4_bin"; }
virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize);
virtual const char *InsertTimestampAsUtc() const { return "?"; }
virtual const char *CollateNocase() const { return "CONVERT(? USING utf8mb4) COLLATE utf8mb4_general_ci"; }
virtual Status Connect();
virtual void Disconnect();
virtual void Lock(const char *pTable);
virtual void Unlock();
virtual void PrepareStatement(const char *pStmt);
virtual void BindString(int Idx, const char *pString);
virtual void BindBlob(int Idx, unsigned char *pBlob, int Size);
virtual void BindInt(int Idx, int Value);
virtual void BindFloat(int Idx, float Value);
virtual bool Step();
virtual bool IsNull(int Col) const;
virtual float GetFloat(int Col) const;
virtual int GetInt(int Col) const;
virtual void GetString(int Col, char *pBuffer, int BufferSize) const;
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const;
virtual void AddPoints(const char *pPlayer, int Points);
private:
#if defined(CONF_SQL)
std::unique_ptr<sql::Connection> m_pConnection;
std::unique_ptr<sql::PreparedStatement> m_pPreparedStmt;
std::unique_ptr<sql::Statement> m_pStmt;
std::unique_ptr<sql::ResultSet> m_pResults;
bool m_NewQuery;
bool m_Locked;
#endif
// copy of config vars
char m_aDatabase[64];
char m_aUser[64];
char m_aPass[64];
char m_aIp[64];
int m_Port;
bool m_Setup;
std::atomic_bool m_InUse;
static lock m_SqlDriverLock;
};
#endif // ENGINE_SERVER_DATABASES_MYSQL_H

View file

@ -0,0 +1,241 @@
#include "sqlite.h"
#include <base/math.h>
#include <engine/console.h>
#include <stdexcept>
#include <sqlite3.h>
CSqliteConnection::CSqliteConnection(const char *pFilename, bool Setup) :
IDbConnection("record"),
m_Setup(Setup),
m_pDb(nullptr),
m_pStmt(nullptr),
m_Done(true),
m_Locked(false),
m_InUse(false)
{
str_copy(m_aFilename, pFilename, sizeof(m_aFilename));
}
CSqliteConnection::~CSqliteConnection()
{
if(m_pStmt != nullptr)
sqlite3_finalize(m_pStmt);
sqlite3_close(m_pDb);
m_pDb = nullptr;
}
void CSqliteConnection::Print(IConsole *pConsole, const char *Mode)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"SQLite-%s: DB: '%s'",
Mode, m_aFilename);
pConsole->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
}
void CSqliteConnection::ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize)
{
str_format(aBuf, BufferSize, "strftime('%%s', %s)", pTimestamp);
}
CSqliteConnection *CSqliteConnection::Copy()
{
return new CSqliteConnection(m_aFilename, m_Setup);
}
IDbConnection::Status CSqliteConnection::Connect()
{
if(m_InUse.exchange(true))
return Status::IN_USE;
if(m_pDb != nullptr)
return Status::SUCCESS;
int Result = sqlite3_open(m_aFilename, &m_pDb);
if(Result != SQLITE_OK)
{
dbg_msg("sql", "Can't open sqlite database: '%s'", sqlite3_errmsg(m_pDb));
return Status::FAILURE;
}
// wait for database to unlock so we don't have to handle SQLITE_BUSY errors
sqlite3_busy_timeout(m_pDb, -1);
if(m_Setup)
{
char aBuf[1024];
FormatCreateRace(aBuf, sizeof(aBuf));
if(!Execute(aBuf))
return Status::FAILURE;
FormatCreateTeamrace(aBuf, sizeof(aBuf), "BLOB");
if(!Execute(aBuf))
return Status::FAILURE;
FormatCreateMaps(aBuf, sizeof(aBuf));
if(!Execute(aBuf))
return Status::FAILURE;
FormatCreateSaves(aBuf, sizeof(aBuf));
if(!Execute(aBuf))
return Status::FAILURE;
FormatCreatePoints(aBuf, sizeof(aBuf));
if(!Execute(aBuf))
return Status::FAILURE;
m_Setup = false;
}
m_Locked = false;
return Status::SUCCESS;
}
void CSqliteConnection::Disconnect()
{
if(m_pStmt != nullptr)
sqlite3_finalize(m_pStmt);
m_pStmt = nullptr;
m_InUse.store(false);
}
void CSqliteConnection::Lock(const char *pTable)
{
// locks the whole database read/write
Execute("BEGIN EXCLUSIVE TRANSACTION;");
m_Locked = true;
}
void CSqliteConnection::Unlock()
{
if(m_Locked)
{
Execute("COMMIT TRANSACTION;");
m_Locked = false;
}
}
void CSqliteConnection::PrepareStatement(const char *pStmt)
{
if(m_pStmt != nullptr)
sqlite3_finalize(m_pStmt);
m_pStmt = nullptr;
int Result = sqlite3_prepare_v2(
m_pDb,
pStmt,
-1, // pStmt can be any length
&m_pStmt,
NULL);
ExceptionOnError(Result);
m_Done = false;
}
void CSqliteConnection::BindString(int Idx, const char *pString)
{
int Result = sqlite3_bind_text(m_pStmt, Idx, pString, -1, NULL);
ExceptionOnError(Result);
m_Done = false;
}
void CSqliteConnection::BindBlob(int Idx, unsigned char *pBlob, int Size)
{
int Result = sqlite3_bind_blob(m_pStmt, Idx, pBlob, Size, NULL);
ExceptionOnError(Result);
m_Done = false;
}
void CSqliteConnection::BindInt(int Idx, int Value)
{
int Result = sqlite3_bind_int(m_pStmt, Idx, Value);
ExceptionOnError(Result);
m_Done = false;
}
void CSqliteConnection::BindFloat(int Idx, float Value)
{
int Result = sqlite3_bind_double(m_pStmt, Idx, (double)Value);
ExceptionOnError(Result);
m_Done = false;
}
bool CSqliteConnection::Step()
{
if(m_Done)
return false;
int Result = sqlite3_step(m_pStmt);
if(Result == SQLITE_ROW)
{
return true;
}
else if(Result == SQLITE_DONE)
{
m_Done = true;
return false;
}
else
{
ExceptionOnError(Result);
}
return false;
}
bool CSqliteConnection::IsNull(int Col) const
{
return sqlite3_column_type(m_pStmt, Col - 1) == SQLITE_NULL;
}
float CSqliteConnection::GetFloat(int Col) const
{
return (float)sqlite3_column_double(m_pStmt, Col - 1);
}
int CSqliteConnection::GetInt(int Col) const
{
return sqlite3_column_int(m_pStmt, Col - 1);
}
void CSqliteConnection::GetString(int Col, char *pBuffer, int BufferSize) const
{
str_copy(pBuffer, (const char *)sqlite3_column_text(m_pStmt, Col - 1), BufferSize);
}
int CSqliteConnection::GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const
{
int Size = sqlite3_column_bytes(m_pStmt, Col - 1);
Size = minimum(Size, BufferSize);
mem_copy(pBuffer, sqlite3_column_blob(m_pStmt, Col - 1), Size);
return Size;
}
bool CSqliteConnection::Execute(const char *pQuery)
{
char *pErrorMsg;
int Result = sqlite3_exec(m_pDb, pQuery, NULL, NULL, &pErrorMsg);
if(Result != SQLITE_OK)
{
dbg_msg("sql", "error executing query: '%s'", pErrorMsg);
sqlite3_free(pErrorMsg);
}
return Result == SQLITE_OK;
}
void CSqliteConnection::ExceptionOnError(int Result)
{
if(Result != SQLITE_OK)
{
throw std::runtime_error(sqlite3_errmsg(m_pDb));
}
}
void CSqliteConnection::AddPoints(const char *pPlayer, int Points)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"INSERT INTO %s_points(Name, Points) "
"VALUES (?, ?) "
"ON CONFLICT(Name) UPDATE SET Points=Points+?;",
GetPrefix());
PrepareStatement(aBuf);
BindString(1, pPlayer);
BindInt(2, Points);
BindInt(3, Points);
Step();
}

View file

@ -0,0 +1,65 @@
#ifndef ENGINE_SERVER_DATABASES_SQLITE_H
#define ENGINE_SERVER_DATABASES_SQLITE_H
#include "connection.h"
#include <atomic>
struct sqlite3;
struct sqlite3_stmt;
class CSqliteConnection : public IDbConnection
{
public:
CSqliteConnection(const char *pFilename, bool Setup);
virtual ~CSqliteConnection();
virtual void Print(IConsole *pConsole, const char *Mode);
virtual CSqliteConnection *Copy();
virtual const char *BinaryCollate() const { return "BINARY"; }
virtual void ToUnixTimestamp(const char *pTimestamp, char *aBuf, unsigned int BufferSize);
virtual const char *InsertTimestampAsUtc() const { return "DATETIME(?, 'utc')"; }
virtual const char *CollateNocase() const { return "? COLLATE NOCASE"; }
virtual Status Connect();
virtual void Disconnect();
virtual void Lock(const char *pTable);
virtual void Unlock();
virtual void PrepareStatement(const char *pStmt);
virtual void BindString(int Idx, const char *pString);
virtual void BindBlob(int Idx, unsigned char *pBlob, int Size);
virtual void BindInt(int Idx, int Value);
virtual void BindFloat(int Idx, float Value);
virtual bool Step();
virtual bool IsNull(int Col) const;
virtual float GetFloat(int Col) const;
virtual int GetInt(int Col) const;
virtual void GetString(int Col, char *pBuffer, int BufferSize) const;
// passing a negative buffer size is undefined behavior
virtual int GetBlob(int Col, unsigned char *pBuffer, int BufferSize) const;
virtual void AddPoints(const char *pPlayer, int Points);
private:
// copy of config vars
char m_aFilename[512];
bool m_Setup;
sqlite3 *m_pDb;
sqlite3_stmt *m_pStmt;
bool m_Done; // no more rows available for Step
bool m_Locked;
// returns true, if the query succeded
bool Execute(const char *pQuery);
void ExceptionOnError(int Result);
std::atomic_bool m_InUse;
};
#endif // ENGINE_SERVER_DATABASES_SQLITE_H

View file

@ -44,6 +44,10 @@
#include <windows.h>
#endif
#include <engine/server/databases/mysql.h>
#include <engine/server/databases/sqlite.h>
#include <engine/server/databases/connection_pool.h>
CSnapIDPool::CSnapIDPool()
{
@ -297,16 +301,7 @@ CServer::CServer(): m_Register(false), m_RegSixup(true)
m_ConnLoggingSocketCreated = false;
#endif
#if defined (CONF_SQL)
for (int i = 0; i < MAX_SQLSERVERS; i++)
{
m_apSqlReadServers[i] = 0;
m_apSqlWriteServers[i] = 0;
}
CSqlConnector::SetReadServers(m_apSqlReadServers);
CSqlConnector::SetWriteServers(m_apSqlWriteServers);
#endif
m_pConnectionPool = new CDbConnectionPool();
m_aErrorShutdownReason[0] = 0;
@ -2307,6 +2302,23 @@ int CServer::Run()
return -1;
}
if(g_Config.m_SvSqliteFile[0] != '\0')
{
auto pSqlServers = std::unique_ptr<CSqliteConnection>(new CSqliteConnection(
g_Config.m_SvSqliteFile, true));
if(g_Config.m_SvUseSQL)
{
DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::WRITE_BACKUP);
}
else
{
auto pCopy = std::unique_ptr<CSqliteConnection>(pSqlServers->Copy());
DbPool()->RegisterDatabase(std::move(pSqlServers), CDbConnectionPool::READ);
DbPool()->RegisterDatabase(std::move(pCopy), CDbConnectionPool::WRITE);
}
}
// start server
NETADDR BindAddr;
int NetType = g_Config.m_SvIpv4Only ? NETTYPE_IPV4 : NETTYPE_ALL;
@ -2584,22 +2596,14 @@ int CServer::Run()
m_Fifo.Shutdown();
#endif
GameServer()->OnShutdown(true);
GameServer()->OnShutdown();
m_pMap->Unload();
for(int i = 0; i < 2; i++)
free(m_apCurrentMapData[i]);
#if defined (CONF_SQL)
for (int i = 0; i < MAX_SQLSERVERS; i++)
{
if (m_apSqlReadServers[i])
delete m_apSqlReadServers[i];
if (m_apSqlWriteServers[i])
delete m_apSqlWriteServers[i];
}
#endif
DbPool()->OnShutdown();
delete m_pConnectionPool;
#if defined (CONF_UPNP)
m_UPnP.Shutdown();
@ -3065,6 +3069,8 @@ void CServer::ConLogout(IConsole::IResult *pResult, void *pUser)
void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser)
{
if(!g_Config.m_SvUseSQL)
return;
CServer *pServer = (CServer *)pUser;
if(pServer->m_RconClientID >= 0 && pServer->m_RconClientID < MAX_CLIENTS &&
@ -3084,10 +3090,10 @@ void CServer::ConShowIps(IConsole::IResult *pResult, void *pUser)
}
}
#if defined (CONF_SQL)
void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
{
if(!g_Config.m_SvUseSQL)
return;
CServer *pSelf = (CServer *)pUserData;
if (pResult->NumArguments() != 7 && pResult->NumArguments() != 8)
@ -3109,60 +3115,42 @@ void CServer::ConAddSqlServer(IConsole::IResult *pResult, void *pUserData)
bool SetUpDb = pResult->NumArguments() == 8 ? pResult->GetInteger(7) : true;
CSqlServer** apSqlServers = ReadOnly ? pSelf->m_apSqlReadServers : pSelf->m_apSqlWriteServers;
auto pSqlServers = std::unique_ptr<CMysqlConnection>(new CMysqlConnection(
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6),
SetUpDb));
for (int i = 0; i < MAX_SQLSERVERS; i++)
{
if (!apSqlServers[i])
{
apSqlServers[i] = new CSqlServer(pResult->GetString(1), pResult->GetString(2), pResult->GetString(3), pResult->GetString(4), pResult->GetString(5), pResult->GetInteger(6), &pSelf->m_GlobalSqlLock, ReadOnly, SetUpDb);
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"Added new Sql%sServer: %d: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(),
apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(),
apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort());
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
if(SetUpDb)
{
if(!apSqlServers[i]->CreateTables())
pSelf->SetErrorShutdown("database create tables failed");
}
return;
}
}
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "failed to add new sqlserver: limit of sqlservers reached");
char aBuf[512];
str_format(aBuf, sizeof(aBuf),
"Added new Sql%sServer: DB: '%s' Prefix: '%s' User: '%s' IP: <{'%s'}> Port: %d",
ReadOnly ? "Read" : "Write",
pResult->GetString(1), pResult->GetString(2), pResult->GetString(3),
pResult->GetString(5), pResult->GetInteger(6));
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
pSelf->DbPool()->RegisterDatabase(std::move(pSqlServers), ReadOnly ? CDbConnectionPool::READ : CDbConnectionPool::WRITE);
}
void CServer::ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData)
{
CServer *pSelf = (CServer *)pUserData;
bool ReadOnly;
if (str_comp_nocase(pResult->GetString(0), "w") == 0)
ReadOnly = false;
else if (str_comp_nocase(pResult->GetString(0), "r") == 0)
ReadOnly = true;
if(str_comp_nocase(pResult->GetString(0), "w") == 0)
{
pSelf->DbPool()->Print(pSelf->Console(), CDbConnectionPool::WRITE);
pSelf->DbPool()->Print(pSelf->Console(), CDbConnectionPool::WRITE_BACKUP);
}
else if(str_comp_nocase(pResult->GetString(0), "r") == 0)
{
pSelf->DbPool()->Print(pSelf->Console(), CDbConnectionPool::READ);
}
else
{
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", "choose either 'r' for SqlReadServer or 'w' for SqlWriteServer");
return;
}
CSqlServer** apSqlServers = ReadOnly ? pSelf->m_apSqlReadServers : pSelf->m_apSqlWriteServers;
for (int i = 0; i < MAX_SQLSERVERS; i++)
if (apSqlServers[i])
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "SQL-%s %d: DB: '%s' Prefix: '%s' User: '%s' Pass: '%s' IP: <{'%s'}> Port: %d", ReadOnly ? "Read" : "Write", i, apSqlServers[i]->GetDatabase(), apSqlServers[i]->GetPrefix(), apSqlServers[i]->GetUser(), apSqlServers[i]->GetPass(), apSqlServers[i]->GetIP(), apSqlServers[i]->GetPort());
pSelf->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "server", aBuf);
}
}
#endif
void CServer::ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData)
{
pfnCallback(pResult, pCallbackUserData);
@ -3363,10 +3351,8 @@ void CServer::RegisterCommands()
Console()->Register("reload", "", CFGFLAG_SERVER, ConMapReload, this, "Reload the map");
#if defined(CONF_SQL)
Console()->Register("add_sqlserver", "s['r'|'w'] s[Database] s[Prefix] s[User] s[Password] s[IP] i[Port] ?i[SetUpDatabase ?]", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConAddSqlServer, this, "add a sqlserver");
Console()->Register("dump_sqlservers", "s['r'|'w']", CFGFLAG_SERVER, ConDumpSqlServers, this, "dumps all sqlservers readservers = r, writeservers = w");
#endif
Console()->Register("auth_add", "s[ident] s[level] s[pw]", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConAuthAdd, this, "Add a rcon key");
Console()->Register("auth_add_p", "s[ident] s[level] s[hash] s[salt]", CFGFLAG_SERVER|CFGFLAG_NONTEEHISTORIC, ConAuthAddHashed, this, "Add a prehashed rcon key");

View file

@ -33,11 +33,6 @@
#include "upnp.h"
#endif
#if defined (CONF_SQL)
#include "sql_connector.h"
#include "sql_server.h"
#endif
class CSnapIDPool
{
enum
@ -102,24 +97,20 @@ class CServer : public IServer
CUPnP m_UPnP;
#endif
#if defined(CONF_SQL)
lock m_GlobalSqlLock;
CSqlServer *m_apSqlReadServers[MAX_SQLSERVERS];
CSqlServer *m_apSqlWriteServers[MAX_SQLSERVERS];
#endif
#if defined(CONF_FAMILY_UNIX)
UNIXSOCKETADDR m_ConnLoggingDestAddr;
bool m_ConnLoggingSocketCreated;
UNIXSOCKET m_ConnLoggingSocket;
#endif
class CDbConnectionPool *m_pConnectionPool;
public:
class IGameServer *GameServer() { return m_pGameServer; }
class IConsole *Console() { return m_pConsole; }
class IStorage *Storage() { return m_pStorage; }
class IEngineAntibot *Antibot() { return m_pAntibot; }
class CDbConnectionPool *DbPool() { return m_pConnectionPool; }
enum
{
@ -395,11 +386,9 @@ public:
static void ConNameUnban(IConsole::IResult *pResult, void *pUser);
static void ConNameBans(IConsole::IResult *pResult, void *pUser);
#if defined (CONF_SQL)
// console commands for sqlmasters
static void ConAddSqlServer(IConsole::IResult *pResult, void *pUserData);
static void ConDumpSqlServers(IConsole::IResult *pResult, void *pUserData);
#endif
static void ConchainSpecialInfoupdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);
static void ConchainMaxclientsperipUpdate(IConsole::IResult *pResult, void *pUserData, IConsole::FCommandCallback pfnCallback, void *pCallbackUserData);

View file

@ -1,41 +0,0 @@
#if defined(CONF_SQL)
#include <base/system.h>
#include "sql_connector.h"
CSqlServer** CSqlConnector::ms_ppSqlReadServers = 0;
CSqlServer** CSqlConnector::ms_ppSqlWriteServers = 0;
int CSqlConnector::ms_ReachableReadServer = 0;
int CSqlConnector::ms_ReachableWriteServer = 0;
CSqlConnector::CSqlConnector() :
m_pSqlServer(0),
m_NumReadRetries(0),
m_NumWriteRetries(0)
{}
bool CSqlConnector::ConnectSqlServer(bool ReadOnly)
{
ReadOnly ? ++m_NumReadRetries : ++m_NumWriteRetries;
int& ReachableServer = ReadOnly ? ms_ReachableReadServer : ms_ReachableWriteServer;
int NumServers = ReadOnly ? CSqlServer::ms_NumReadServer : CSqlServer::ms_NumWriteServer;
for (int i = ReachableServer, ID = ReachableServer; i < ReachableServer + NumServers && SqlServer(i % NumServers, ReadOnly); i++, ID = i % NumServers)
{
if (SqlServer(ID, ReadOnly) && SqlServer(ID, ReadOnly)->Connect())
{
m_pSqlServer = SqlServer(ID, ReadOnly);
ReachableServer = ID;
return true;
}
if (SqlServer(ID, ReadOnly))
dbg_msg("sql", "Warning: Unable to connect to Sql%sServer %d ('%s'), trying next...", ReadOnly ? "Read" : "Write", ID, SqlServer(ID, ReadOnly)->GetIP());
}
dbg_msg("sql", "FATAL ERROR: No Sql%sServers available", ReadOnly ? "Read" : "Write");
m_pSqlServer = 0;
return false;
}
#endif

View file

@ -1,47 +0,0 @@
#ifndef ENGINE_SERVER_SQL_CONNECTOR_H
#define ENGINE_SERVER_SQL_CONNECTOR_H
#include "sql_server.h"
enum
{
MAX_SQLSERVERS=15
};
// implementation to provide sqlservers
class CSqlConnector
{
public:
CSqlConnector();
CSqlServer* SqlServer(int i, bool ReadOnly = true) { return ReadOnly ? ms_ppSqlReadServers[i] : ms_ppSqlWriteServers[i]; }
// always returns the last connected sql-server
CSqlServer* SqlServer() { return m_pSqlServer; }
static void SetReadServers(CSqlServer** ppReadServers) { ms_ppSqlReadServers = ppReadServers; }
static void SetWriteServers(CSqlServer** ppWriteServers) { ms_ppSqlWriteServers = ppWriteServers; }
static void ResetReachable() { ms_ReachableReadServer = 0; ms_ReachableWriteServer = 0; }
bool ConnectSqlServer(bool ReadOnly = true);
bool MaxTriesReached(bool ReadOnly = true) { return ReadOnly ? m_NumReadRetries >= CSqlServer::ms_NumReadServer : m_NumWriteRetries >= CSqlServer::ms_NumWriteServer; }
private:
CSqlServer *m_pSqlServer;
static CSqlServer **ms_ppSqlReadServers;
static CSqlServer **ms_ppSqlWriteServers;
static int ms_NumReadServer;
static int ms_NumWriteServer;
static int ms_ReachableReadServer;
static int ms_ReachableWriteServer;
int m_NumReadRetries;
int m_NumWriteRetries;
};
#endif

View file

@ -1,223 +0,0 @@
#if defined(CONF_SQL)
#include <base/system.h>
#include <engine/shared/protocol.h>
#include <engine/shared/config.h>
#include "sql_server.h"
int CSqlServer::ms_NumReadServer = 0;
int CSqlServer::ms_NumWriteServer = 0;
CSqlServer::CSqlServer(const char *pDatabase, const char *pPrefix, const char *pUser, const char *pPass, const char *pIp, int Port, lock *pGlobalLock, bool ReadOnly, bool SetUpDb) :
m_Port(Port),
m_SetUpDB(SetUpDb),
m_SqlLock(),
m_pGlobalLock(pGlobalLock)
{
str_copy(m_aDatabase, pDatabase, sizeof(m_aDatabase));
str_copy(m_aPrefix, pPrefix, sizeof(m_aPrefix));
str_copy(m_aUser, pUser, sizeof(m_aUser));
str_copy(m_aPass, pPass, sizeof(m_aPass));
str_copy(m_aIp, pIp, sizeof(m_aIp));
m_pDriver = 0;
m_pConnection = 0;
m_pResults = 0;
m_pStatement = 0;
ReadOnly ? ms_NumReadServer++ : ms_NumWriteServer++;
}
CSqlServer::~CSqlServer()
{
scope_lock LockScope(&m_SqlLock);
try
{
if (m_pResults)
delete m_pResults;
if (m_pConnection)
{
delete m_pConnection;
m_pConnection = 0;
}
dbg_msg("sql", "SQL connection disconnected");
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "ERROR: No SQL connection: %s", e.what());
}
catch (const std::exception& ex)
{
dbg_msg("sql", "ERROR: No SQL connection: %s", ex.what());
}
catch (const std::string& ex)
{
dbg_msg("sql", "ERROR: No SQL connection: %s", ex.c_str());
}
catch (...)
{
dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector");
}
}
bool CSqlServer::Connect()
{
m_SqlLock.take();
if (m_pDriver != NULL && m_pConnection != NULL)
{
try
{
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
return true;
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}
catch (const std::exception& ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.what());
}
catch (const std::string& ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.c_str());
}
catch (...)
{
dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector");
}
m_SqlLock.release();
dbg_msg("sql", "ERROR: SQL connection failed");
return false;
}
try
{
m_pDriver = 0;
m_pConnection = 0;
m_pStatement = 0;
sql::ConnectOptionsMap connection_properties;
connection_properties["hostName"] = sql::SQLString(m_aIp);
connection_properties["port"] = m_Port;
connection_properties["userName"] = sql::SQLString(m_aUser);
connection_properties["password"] = sql::SQLString(m_aPass);
connection_properties["OPT_CONNECT_TIMEOUT"] = 10;
connection_properties["OPT_READ_TIMEOUT"] = 10;
connection_properties["OPT_WRITE_TIMEOUT"] = 20;
connection_properties["OPT_RECONNECT"] = true;
connection_properties["OPT_CHARSET_NAME"] = sql::SQLString("utf8mb4");
connection_properties["OPT_SET_CHARSET_NAME"] = sql::SQLString("utf8mb4");
// Create connection
{
scope_lock GlobalLockScope(m_pGlobalLock);
m_pDriver = get_driver_instance();
}
m_pConnection = m_pDriver->connect(connection_properties);
// Create Statement
m_pStatement = m_pConnection->createStatement();
// Apparently OPT_CHARSET_NAME and OPT_SET_CHARSET_NAME are not enough
m_pStatement->execute("SET CHARACTER SET utf8mb4;");
if (m_SetUpDB)
{
char aBuf[128];
// create database
str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s CHARACTER SET utf8mb4", m_aDatabase);
m_pStatement->execute(aBuf);
}
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
dbg_msg("sql", "sql connection established");
return true;
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}
catch (const std::exception& ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.what());
}
catch (const std::string& ex)
{
dbg_msg("sql", "MySQL Error: %s", ex.c_str());
}
catch (...)
{
dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector");
}
dbg_msg("sql", "ERROR: sql connection failed");
m_SqlLock.release();
return false;
}
void CSqlServer::Disconnect()
{
m_SqlLock.release();
}
bool CSqlServer::CreateTables()
{
if (!Connect())
return false;
bool Success = false;
try
{
char aBuf[1024];
// create tables
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_race (Map VARCHAR(128) BINARY NOT NULL, Name VARCHAR(%d) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Time FLOAT DEFAULT 0, Server CHAR(4), cp1 FLOAT DEFAULT 0, cp2 FLOAT DEFAULT 0, cp3 FLOAT DEFAULT 0, cp4 FLOAT DEFAULT 0, cp5 FLOAT DEFAULT 0, cp6 FLOAT DEFAULT 0, cp7 FLOAT DEFAULT 0, cp8 FLOAT DEFAULT 0, cp9 FLOAT DEFAULT 0, cp10 FLOAT DEFAULT 0, cp11 FLOAT DEFAULT 0, cp12 FLOAT DEFAULT 0, cp13 FLOAT DEFAULT 0, cp14 FLOAT DEFAULT 0, cp15 FLOAT DEFAULT 0, cp16 FLOAT DEFAULT 0, cp17 FLOAT DEFAULT 0, cp18 FLOAT DEFAULT 0, cp19 FLOAT DEFAULT 0, cp20 FLOAT DEFAULT 0, cp21 FLOAT DEFAULT 0, cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, cp25 FLOAT DEFAULT 0, GameID VARCHAR(64), DDNet7 BOOL DEFAULT FALSE, KEY (Map, Name)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH);
executeSql(aBuf);
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_teamrace (Map VARCHAR(128) BINARY NOT NULL, Name VARCHAR(%d) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Time FLOAT DEFAULT 0, ID VARBINARY(16) NOT NULL, GameID VARCHAR(64), DDNet7 BOOL DEFAULT FALSE, KEY Map (Map)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH);
executeSql(aBuf);
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_maps (Map VARCHAR(128) BINARY NOT NULL, Server VARCHAR(32) BINARY NOT NULL, Mapper VARCHAR(128) BINARY NOT NULL, Points INT DEFAULT 0, Stars INT DEFAULT 0, Timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY Map (Map)) CHARACTER SET utf8mb4;", m_aPrefix);
executeSql(aBuf);
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_saves (Savegame TEXT CHARACTER SET utf8mb4 BINARY NOT NULL, Map VARCHAR(128) BINARY NOT NULL, Code VARCHAR(128) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Server CHAR(4), DDNet7 BOOL DEFAULT FALSE, SaveID VARCHAR(36) DEFAULT NULL, UNIQUE KEY (Map, Code)) CHARACTER SET utf8mb4;", m_aPrefix);
executeSql(aBuf);
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_points (Name VARCHAR(%d) BINARY NOT NULL, Points INT DEFAULT 0, UNIQUE KEY Name (Name)) CHARACTER SET utf8mb4;", m_aPrefix, MAX_NAME_LENGTH);
executeSql(aBuf);
dbg_msg("sql", "Tables were created successfully");
Success = true;
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}
Disconnect();
return Success;
}
void CSqlServer::executeSql(const char *pCommand)
{
m_pStatement->execute(pCommand);
}
void CSqlServer::executeSqlQuery(const char *pQuery)
{
if (m_pResults)
delete m_pResults;
// set it to 0, so exceptions raised from executeQuery can not make m_pResults point to invalid memory
m_pResults = 0;
m_pResults = m_pStatement->executeQuery(pQuery);
}
#endif

View file

@ -1,58 +0,0 @@
#ifndef ENGINE_SERVER_SQL_SERVER_H
#define ENGINE_SERVER_SQL_SERVER_H
#include <base/tl/threading.h>
#include <mysql_connection.h>
#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/statement.h>
class CSqlServer
{
public:
CSqlServer(const char *pDatabase, const char *pPrefix, const char *pUser, const char *pPass, const char *pIp, int Port, lock *pGlobalLock, bool ReadOnly = true, bool SetUpDb = false);
~CSqlServer();
bool Connect();
void Disconnect();
bool CreateTables();
void executeSql(const char *pCommand);
void executeSqlQuery(const char *pQuery);
sql::ResultSet* GetResults() { return m_pResults; }
const char* GetDatabase() { return m_aDatabase; }
const char* GetPrefix() { return m_aPrefix; }
const char* GetUser() { return m_aUser; }
const char* GetPass() { return m_aPass; }
const char* GetIP() { return m_aIp; }
int GetPort() { return m_Port; }
sql::Connection *Connection() const { return m_pConnection; }
static int ms_NumReadServer;
static int ms_NumWriteServer;
private:
sql::Driver *m_pDriver;
sql::Connection *m_pConnection;
sql::Statement *m_pStatement;
sql::ResultSet *m_pResults;
// copy of config vars
char m_aDatabase[64];
char m_aPrefix[64];
char m_aUser[64];
char m_aPass[64];
char m_aIp[64];
int m_Port;
bool m_SetUpDB;
lock m_SqlLock;
lock *m_pGlobalLock;
};
#endif

View file

@ -1,8 +1,8 @@
#include "sql_string_helpers.h"
#include <base/system.h>
#include <cmath>
#include <cstring>
#include <base/system.h>
#include "sql_string_helpers.h"
void sqlstr::FuzzyString(char *pString, int size)
{
@ -24,39 +24,21 @@ void sqlstr::FuzzyString(char *pString, int size)
delete [] newString;
}
// anti SQL injection
void sqlstr::ClearString(char *pString, int size)
int sqlstr::EscapeLike(char *pDst, const char *pSrc, int DstSize)
{
char *newString = new char [size * 2 - 1];
int pos = 0;
for(int i = 0; i < size; i++)
int Pos = 0;
int DstPos = 0;
while(DstPos + 2 < DstSize)
{
if(pString[i] == '\\')
{
newString[pos++] = '\\';
newString[pos++] = '\\';
}
else if(pString[i] == '\'')
{
newString[pos++] = '\\';
newString[pos++] = '\'';
}
else if(pString[i] == '"')
{
newString[pos++] = '\\';
newString[pos++] = '"';
}
else
{
newString[pos++] = pString[i];
}
if(pSrc[Pos] == '\0')
break;
if(pSrc[Pos] == '\\' || pSrc[Pos] == '%' || pSrc[Pos] == '_' || pSrc[Pos] == '[')
pDst[DstPos++] = '\\';
pDst[DstPos++] = pSrc[Pos++];
}
newString[pos] = '\0';
str_copy(pString, newString, size);
delete [] newString;
pDst[DstPos++] = '\0';
return DstPos;
}
void sqlstr::AgoTimeToString(int AgoTime, char *pAgoString)

View file

@ -1,52 +1,16 @@
#ifndef ENGINE_SERVER_SQL_STRING_HELPERS_H
#define ENGINE_SERVER_SQL_STRING_HELPERS_H
#include <base/system.h>
namespace sqlstr
{
void FuzzyString(char *pString, int size);
// anti SQL injection
void ClearString(char *pString, int size = 32);
// written number of added bytes
int EscapeLike(char *pDst, const char *pSrc, int DstSize);
void AgoTimeToString(int agoTime, char *pAgoString);
template<unsigned int size>
class CSqlString
{
public:
CSqlString() {}
CSqlString(const char *pStr)
{
str_copy(m_aString, pStr, size);
str_copy(m_aClearString, pStr, size);
ClearString(m_aClearString, sizeof(m_aClearString));
}
const char* Str() const { return m_aString; }
const char* ClrStr() const { return m_aClearString; }
CSqlString& operator=(const char *pStr)
{
str_copy(m_aString, pStr, size);
str_copy(m_aClearString, pStr, size);
ClearString(m_aClearString, sizeof(m_aClearString));
return *this;
}
bool operator<(const CSqlString& other) const
{
return str_comp(m_aString, other.m_aString) < 0;
}
private:
char m_aString[size];
char m_aClearString[size * 2 - 1];
};
}
#endif

View file

@ -217,11 +217,9 @@ MACRO_CONFIG_STR(SvSqlServerName, sv_sql_servername, 5, "UNK", CFGFLAG_SERVER, "
MACRO_CONFIG_STR(SvSqlValidServerNames, sv_sql_valid_servernames, 64, "UNK", CFGFLAG_SERVER, "Comma separated list of valid server names for saving a game to ([A-Z][A-Z][A-Z].?")
MACRO_CONFIG_INT(SvSaveGames, sv_savegames, 1, 0, 1, CFGFLAG_SERVER, "Enables savegames (/save and /load)")
MACRO_CONFIG_INT(SvSaveGamesDelay, sv_savegames_delay, 60, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame")
#if defined(CONF_SQL)
MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables SQL DB instead of record file")
MACRO_CONFIG_STR(SvSqlFailureFile, sv_sql_failure_file, 64, "failed_sql.sql", CFGFLAG_SERVER, "File to store failed Sql-Inserts (ranks)")
MACRO_CONFIG_INT(SvSqlQueriesDelay, sv_sql_queries_delay, 1, 0, 20, CFGFLAG_SERVER, "Delay in seconds between SQL queries of a single player")
#endif
MACRO_CONFIG_STR(SvSqliteFile, sv_sqlite_file, 64, "ddnet-server.sqlite", CFGFLAG_SERVER, "File to store ranks in case sv_use_sql is turned off or used as backup sql server")
#if defined(CONF_UPNP)
MACRO_CONFIG_INT(SvUseUPnP, sv_use_upnp, 0, 0, 1, CFGFLAG_SERVER, "Enables UPnP support.")

View file

@ -55,6 +55,14 @@ void FormatUuid(CUuid Uuid, char *pBuffer, unsigned BufferLength)
p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]);
}
void ParseUuid(CUuid *pUuid, char *pBuffer)
{
unsigned char *p = pUuid->m_aData;
sscanf(pBuffer, "%02hhX%02hhX%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX-%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
&p[0], &p[1], &p[2], &p[3], &p[4], &p[5], &p[6], &p[7],
&p[8], &p[9], &p[10], &p[11], &p[12], &p[13], &p[14], &p[15]);
}
bool CUuid::operator==(const CUuid& Other)
{
return mem_comp(this, &Other, sizeof(*this)) == 0;

View file

@ -25,6 +25,7 @@ CUuid RandomUuid();
CUuid CalculateUuid(const char *pName);
// The buffer length should be at least UUID_MAXSTRSIZE.
void FormatUuid(CUuid Uuid, char *pBuffer, unsigned BufferLength);
void ParseUuid(CUuid *pUuid, char *pBuffer);
struct CName
{

View file

@ -13,7 +13,6 @@
class CAntibot;
class CGameTeams;
class CSaveTee;
struct CAntibotCharacterData;
enum

View file

@ -7,7 +7,7 @@
#include <game/server/gamemodes/DDRace.h>
#include "plasma.h"
const float ACCEL = 1.1f;
const float PLASMA_ACCEL = 1.1f;
CPlasma::CPlasma(CGameWorld *pGameWorld, vec2 Pos, vec2 Dir, bool Freeze,
bool Explosive, int ResponsibleTeam) :
@ -44,7 +44,7 @@ bool CPlasma::HitCharacter()
void CPlasma::Move()
{
m_Pos += m_Core;
m_Core *= ACCEL;
m_Core *= PLASMA_ACCEL;
}
void CPlasma::Reset()

View file

@ -24,10 +24,6 @@
#include "gamemodes/DDRace.h"
#include "score.h"
#include "score/file_score.h"
#if defined(CONF_SQL)
#include "score/sql_score.h"
#endif
enum
{
@ -3124,17 +3120,11 @@ void CGameContext::OnInit(/*class IKernel *pKernel*/)
}
}
// delete old score object
if(m_pScore)
delete m_pScore;
if(!m_pScore)
{
m_pScore = new CScore(this, ((CServer *)Server())->DbPool());
}
// create score object (add sql later)
#if defined(CONF_SQL)
if(g_Config.m_SvUseSQL)
m_pScore = new CSqlScore(this);
else
#endif
m_pScore = new CFileScore(this);
// setup core world
//for(int i = 0; i < MAX_CLIENTS; i++)
// game.players[i].core.world = &game.world.core;
@ -3384,11 +3374,8 @@ void CGameContext::OnMapChange(char *pNewMapName, int MapNameSize)
str_copy(m_aDeleteTempfile, aTemp, sizeof(m_aDeleteTempfile));
}
void CGameContext::OnShutdown(bool FullShutdown)
void CGameContext::OnShutdown()
{
if (FullShutdown)
Score()->OnShutdown();
Antibot()->RoundEnd();
if(m_TeeHistorianActive)

View file

@ -18,7 +18,6 @@
#include "player.h"
#include "teehistorian.h"
#include "score.h"
#ifdef _MSC_VER
typedef __int32 int32_t;
typedef unsigned __int32 uint32_t;
@ -54,6 +53,7 @@ enum
NUM_TUNEZONES = 256
};
class CScore;
class IConsole;
class IEngine;
class IStorage;
@ -228,7 +228,7 @@ public:
virtual void OnInit();
virtual void OnConsoleInit();
virtual void OnMapChange(char *pNewMapName, int MapNameSize);
virtual void OnShutdown(bool FullShutdown = false);
virtual void OnShutdown();
virtual void OnTick();
virtual void OnPreSnap();
@ -279,7 +279,7 @@ public:
private:
bool m_VoteWillPass;
class IScore *m_pScore;
class CScore *m_pScore;
//DDRace Console Commands
@ -412,7 +412,7 @@ private:
public:
CLayers *Layers() { return &m_Layers; }
class IScore *Score() { return m_pScore; }
class CScore *Score() { return m_pScore; }
enum
{

View file

@ -290,7 +290,7 @@ char* CSaveTee::GetString(const CSaveTeam *pTeam)
return m_aString;
}
int CSaveTee::LoadString(const char* String)
int CSaveTee::FromString(const char* String)
{
int Num;
Num = sscanf(String,
@ -552,7 +552,7 @@ char* CSaveTeam::GetString()
return m_aString;
}
int CSaveTeam::LoadString(const char* String)
int CSaveTeam::FromString(const char* String)
{
char TeamStats[MAX_CLIENTS];
char Switcher[64];
@ -636,7 +636,7 @@ int CSaveTeam::LoadString(const char* String)
if(StrSize < sizeof(SaveTee))
{
str_copy(SaveTee, CopyPos, StrSize);
int Num = m_pSavedTees[n].LoadString(SaveTee);
int Num = m_pSavedTees[n].FromString(SaveTee);
if(Num)
{
dbg_msg("load", "failed to load tee");

View file

@ -18,7 +18,7 @@ public:
void save(CCharacter* pchr);
void load(CCharacter* pchr, int Team);
char* GetString(const CSaveTeam *pTeam);
int LoadString(const char* String);
int FromString(const char* String);
void LoadHookedPlayer(const CSaveTeam *pTeam);
vec2 GetPos() const { return m_Pos; }
const char* GetName() const { return m_aName; }
@ -115,7 +115,7 @@ public:
char* GetString();
int GetMembersCount() const { return m_MembersCount; }
// MatchPlayers has to be called afterwards
int LoadString(const char* String);
int FromString(const char* String);
// returns true if a team can load, otherwise writes a nice error Message in pMessage
bool MatchPlayers(const char (*paNames)[MAX_NAME_LENGTH], const int *pClientID, int NumPlayer, char *pMessage, int MessageLen);
int save(int Team);

File diff suppressed because it is too large Load diff

View file

@ -3,11 +3,21 @@
#include <memory>
#include <atomic>
#include <string>
#include <vector>
#include <game/voting.h>
#include <engine/server/databases/connection_pool.h>
#include <engine/map.h>
#include <game/voting.h>
#include <game/prng.h>
#include "save.h"
struct ISqlData;
class IDbConnection;
class IServer;
class CGameContext;
enum
{
NUM_CHECKPOINTS = 25,
@ -132,43 +142,213 @@ public:
float m_aBestCpTime[NUM_CHECKPOINTS];
};
class IScore
struct CSqlInitData : ISqlData
{
CSqlInitData(std::shared_ptr<CScoreInitResult> pResult) :
m_pResult(pResult)
{}
std::shared_ptr<CScoreInitResult> m_pResult;
// current map
char m_Map[MAX_MAP_LENGTH];
};
struct CSqlPlayerRequest : ISqlData
{
CSqlPlayerRequest(std::shared_ptr<CScorePlayerResult> pResult) :
m_pResult(pResult)
{}
std::shared_ptr<CScorePlayerResult> m_pResult;
// object being requested, either map (128 bytes) or player (16 bytes)
char m_Name[MAX_MAP_LENGTH];
// current map
char m_Map[MAX_MAP_LENGTH];
char m_RequestingPlayer[MAX_NAME_LENGTH];
// relevant for /top5 kind of requests
int m_Offset;
};
struct CSqlRandomMapRequest : ISqlData
{
CSqlRandomMapRequest(std::shared_ptr<CScoreRandomMapResult> pResult) :
m_pResult(pResult)
{}
std::shared_ptr<CScoreRandomMapResult> m_pResult;
char m_ServerType[32];
char m_CurrentMap[MAX_MAP_LENGTH];
char m_RequestingPlayer[MAX_NAME_LENGTH];
int m_Stars;
};
struct CSqlScoreData : ISqlData
{
CSqlScoreData(std::shared_ptr<CScorePlayerResult> pResult) :
m_pResult(pResult)
{}
virtual ~CSqlScoreData() {};
std::shared_ptr<CScorePlayerResult> m_pResult;
char m_Map[MAX_MAP_LENGTH];
char m_GameUuid[UUID_MAXSTRSIZE];
char m_Name[MAX_MAP_LENGTH];
int m_ClientID;
float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
float m_aCpCurrent[NUM_CHECKPOINTS];
int m_Num;
bool m_Search;
char m_aRequestingPlayer[MAX_NAME_LENGTH];
};
struct CSqlTeamScoreData : ISqlData
{
char m_GameUuid[UUID_MAXSTRSIZE];
char m_Map[MAX_MAP_LENGTH];
float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
unsigned int m_Size;
char m_aNames[MAX_CLIENTS][MAX_NAME_LENGTH];
};
struct CSqlTeamSave : ISqlData
{
CSqlTeamSave(std::shared_ptr<CScoreSaveResult> pResult) :
m_pResult(pResult)
{}
virtual ~CSqlTeamSave() {};
std::shared_ptr<CScoreSaveResult> m_pResult;
char m_ClientName[MAX_NAME_LENGTH];
char m_Map[MAX_MAP_LENGTH];
char m_Code[128];
char m_aGeneratedCode[128];
char m_Server[5];
};
struct CSqlTeamLoad : ISqlData
{
CSqlTeamLoad(std::shared_ptr<CScoreSaveResult> pResult) :
m_pResult(pResult)
{}
virtual ~CSqlTeamLoad() {};
std::shared_ptr<CScoreSaveResult> m_pResult;
char m_Code[128];
char m_Map[MAX_MAP_LENGTH];
char m_RequestingPlayer[MAX_NAME_LENGTH];
int m_ClientID;
// struct holding all player names in the team or an empty string
char m_aClientNames[MAX_CLIENTS][MAX_NAME_LENGTH];
int m_aClientID[MAX_CLIENTS];
int m_NumPlayer;
};
struct CTeamrank {
CUuid m_TeamID;
char m_aaNames[MAX_CLIENTS][MAX_NAME_LENGTH];
unsigned int m_NumNames;
CTeamrank();
// Assumes that a database query equivalent to
//
// SELECT TeamID, Name [, ...] -- the order is important
// FROM record_teamrace
// ORDER BY TeamID, Name
//
// was executed and that the result line of the first team member is already selected.
// Afterwards the team member of the next team is selected.
// Returns if another team can be extracted
bool NextSqlResult(IDbConnection *pSqlServer);
bool SamePlayers(const std::vector<std::string> *aSortedNames);
};
class CScore
{
CPlayerData m_aPlayerData[MAX_CLIENTS];
CDbConnectionPool *m_pPool;
static bool Init(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool RandomMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool RandomUnfinishedMapThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool MapVoteThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool LoadPlayerDataThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool MapInfoThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool ShowRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool ShowTeamRankThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool ShowTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool ShowTeamTop5Thread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool ShowTimesThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool ShowPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool ShowTopPointsThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool GetSavesThread(IDbConnection *pSqlServer, const ISqlData *pGameData);
static bool SaveTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure);
static bool LoadTeamThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure);
static bool SaveScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure);
static bool SaveTeamScoreThread(IDbConnection *pSqlServer, const ISqlData *pGameData, bool Failure);
CGameContext *GameServer() const { return m_pGameServer; }
IServer *Server() const { return m_pServer; }
CGameContext *m_pGameServer;
IServer *m_pServer;
std::vector<std::string> m_aWordlist;
CPrng m_Prng;
void GeneratePassphrase(char *pBuf, int BufSize);
// returns new SqlResult bound to the player, if no current Thread is active for this player
std::shared_ptr<CScorePlayerResult> NewSqlPlayerResult(int ClientID);
// Creates for player database requests
void ExecPlayerThread(
bool (*pFuncPtr) (IDbConnection *, const ISqlData *),
const char *pThreadName,
int ClientID,
const char *pName,
int Offset);
// returns true if the player should be rate limited
bool RateLimitPlayer(int ClientID);
public:
virtual ~IScore() {}
CScore(CGameContext *pGameServer, CDbConnectionPool *pPool);
~CScore() {}
CPlayerData *PlayerData(int ID) { return &m_aPlayerData[ID]; }
virtual void MapInfo(int ClientID, const char *pMapName) = 0;
virtual void MapVote(int ClientID, const char *pMapName) = 0;
virtual void LoadPlayerData(int ClientID) = 0;
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp, float aCpTime[NUM_CHECKPOINTS], bool NotEligible) = 0;
void MapInfo(int ClientID, const char *pMapName);
void MapVote(int ClientID, const char *pMapName);
void LoadPlayerData(int ClientID);
void SaveScore(int ClientID, float Time, const char *pTimestamp, float aCpTime[NUM_CHECKPOINTS], bool NotEligible);
virtual void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp) = 0;
void SaveTeamScore(int *pClientIDs, unsigned int Size, float Time, const char *pTimestamp);
virtual void ShowTop5(int ClientID, int Offset=1) = 0;
virtual void ShowRank(int ClientID, const char *pName) = 0;
void ShowTop5(int ClientID, int Offset=1);
void ShowRank(int ClientID, const char *pName);
virtual void ShowTeamTop5(int ClientID, int Offset=1) = 0;
virtual void ShowTeamRank(int ClientID, const char *pName) = 0;
void ShowTeamTop5(int ClientID, int Offset=1);
void ShowTeamRank(int ClientID, const char *pName);
virtual void ShowTopPoints(int ClientID, int Offset=1) = 0;
virtual void ShowPoints(int ClientID, const char *pName) = 0;
void ShowTopPoints(int ClientID, int Offset=1);
void ShowPoints(int ClientID, const char *pName);
virtual void ShowTimes(int ClientID, const char *pName, int Offset = 1) = 0;
virtual void ShowTimes(int ClientID, int Offset = 1) = 0;
void ShowTimes(int ClientID, const char *pName, int Offset = 1);
void ShowTimes(int ClientID, int Offset = 1);
virtual void RandomMap(int ClientID, int Stars) = 0;
virtual void RandomUnfinishedMap(int ClientID, int Stars) = 0;
void RandomMap(int ClientID, int Stars);
void RandomUnfinishedMap(int ClientID, int Stars);
virtual void SaveTeam(int ClientID, const char *pCode, const char *pServer) = 0;
virtual void LoadTeam(const char *pCode, int ClientID) = 0;
virtual void GetSaves(int ClientID) = 0;
// called when the server is shut down but not on mapchange/reload
virtual void OnShutdown() = 0;
void SaveTeam(int ClientID, const char *pCode, const char *pServer);
void LoadTeam(const char *pCode, int ClientID);
void GetSaves(int ClientID);
};
#endif // GAME_SERVER_SCORE_H

View file

@ -1,372 +0,0 @@
/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */
/* copyright (c) 2008 rajh and gregwar. Score stuff */
#include <base/tl/sorted_array.h>
#include <engine/shared/config.h>
#include <sstream>
#include <fstream>
#include "../gamemodes/DDRace.h"
#include "file_score.h"
#include <engine/shared/console.h>
static LOCK gs_ScoreLock = 0;
CFileScore::CPlayerScore::CPlayerScore(const char *pName, float Score,
float aCpTime[NUM_CHECKPOINTS])
{
str_copy(m_aName, pName, sizeof(m_aName));
m_Score = Score;
for (int i = 0; i < NUM_CHECKPOINTS; i++)
m_aCpTime[i] = aCpTime[i];
}
CFileScore::CFileScore(CGameContext *pGameServer) :
m_pGameServer(pGameServer), m_pServer(pGameServer->Server())
{
if (gs_ScoreLock == 0)
gs_ScoreLock = lock_create();
Init();
}
CFileScore::~CFileScore()
{
lock_wait(gs_ScoreLock);
// clear list
m_Top.clear();
lock_unlock(gs_ScoreLock);
}
std::string CFileScore::SaveFile()
{
std::ostringstream oss;
char aBuf[256];
str_copy(aBuf, Server()->GetMapName(), sizeof(aBuf));
for(int i = 0; i < 256; i++) if(aBuf[i] == '/') aBuf[i] = '-';
if (g_Config.m_SvScoreFolder[0])
oss << g_Config.m_SvScoreFolder << "/" << aBuf << "_record.dtb";
else
oss << Server()->GetMapName() << "_record.dtb";
return oss.str();
}
void CFileScore::MapInfo(int ClientID, const char* MapName)
{
// TODO: implement
}
void CFileScore::MapVote(int ClientID, const char* MapName)
{
// TODO: implement
}
void CFileScore::SaveScoreThread(void *pUser)
{
CFileScore *pSelf = (CFileScore *) pUser;
lock_wait(gs_ScoreLock);
std::fstream f;
f.open(pSelf->SaveFile().c_str(), std::ios::out);
if(f.fail())
{
dbg_msg("filescore", "opening '%s' for writing failed", pSelf->SaveFile().c_str());
}
else
{
int t = 0;
for (sorted_array<CPlayerScore>::range r = pSelf->m_Top.all();
!r.empty(); r.pop_front())
{
f << r.front().m_aName << std::endl << r.front().m_Score
<< std::endl;
if (g_Config.m_SvCheckpointSave)
{
for (int c = 0; c < NUM_CHECKPOINTS; c++)
f << r.front().m_aCpTime[c] << " ";
f << std::endl;
}
t++;
if (t % 50 == 0)
thread_sleep(1000);
}
}
f.close();
lock_unlock(gs_ScoreLock);
}
void CFileScore::Save()
{
thread_init_and_detach(SaveScoreThread, this, "FileScore save");
}
void CFileScore::Init()
{
lock_wait(gs_ScoreLock);
// create folder if not exist
if (g_Config.m_SvScoreFolder[0])
fs_makedir(g_Config.m_SvScoreFolder);
std::fstream f;
f.open(SaveFile().c_str(), std::ios::in);
if(f.fail())
{
dbg_msg("filescore", "opening '%s' for reading failed", SaveFile().c_str());
}
while (!f.eof() && !f.fail())
{
std::string TmpName, TmpScore, TmpCpLine;
std::getline(f, TmpName);
if (!f.eof() && TmpName != "")
{
std::getline(f, TmpScore);
float aTmpCpTime[NUM_CHECKPOINTS] =
{ 0 };
if (g_Config.m_SvCheckpointSave)
{
std::getline(f, TmpCpLine);
std::istringstream iss(TmpCpLine);
int i = 0;
for(std::string p; std::getline(iss, p, ' '); i++)
aTmpCpTime[i] = std::stof(p, NULL);
}
m_Top.add(
*new CPlayerScore(TmpName.c_str(), atof(TmpScore.c_str()),
aTmpCpTime));
}
}
f.close();
lock_unlock(gs_ScoreLock);
// save the current best score
if (m_Top.size())
((CGameControllerDDRace*) GameServer()->m_pController)->m_CurrentRecord =
m_Top[0].m_Score;
}
CFileScore::CPlayerScore *CFileScore::SearchName(const char *pName,
int *pPosition, bool NoCase)
{
CPlayerScore *pPlayer = 0;
int Pos = 1;
int Found = 0;
for (sorted_array<CPlayerScore>::range r = m_Top.all(); !r.empty();
r.pop_front())
{
if (str_find_nocase(r.front().m_aName, pName))
{
if (pPosition)
*pPosition = Pos;
if (NoCase)
{
Found++;
pPlayer = &r.front();
}
if (!str_comp(r.front().m_aName, pName))
return &r.front();
}
Pos++;
}
if (Found > 1)
{
if (pPosition)
*pPosition = -1;
return 0;
}
return pPlayer;
}
void CFileScore::UpdatePlayer(int ID, float Score,
float aCpTime[NUM_CHECKPOINTS])
{
const char *pName = Server()->ClientName(ID);
lock_wait(gs_ScoreLock);
CPlayerScore *pPlayer = SearchScore(ID, 0);
if (pPlayer)
{
for (int c = 0; c < NUM_CHECKPOINTS; c++)
pPlayer->m_aCpTime[c] = aCpTime[c];
pPlayer->m_Score = Score;
str_copy(pPlayer->m_aName, pName, sizeof(pPlayer->m_aName));
sort(m_Top.all());
}
else
m_Top.add(*new CPlayerScore(pName, Score, aCpTime));
lock_unlock(gs_ScoreLock);
Save();
}
void CFileScore::LoadPlayerData(int ClientID)
{
CPlayerScore *pPlayer = SearchScore(ClientID, 0);
if (pPlayer)
{
lock_wait(gs_ScoreLock);
lock_unlock(gs_ScoreLock);
Save();
}
// set score
if (pPlayer)
{
PlayerData(ClientID)->Set(pPlayer->m_Score, pPlayer->m_aCpTime);
GameServer()->m_apPlayers[ClientID]->m_HasFinishScore = true;
}
}
void CFileScore::SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp)
{
dbg_msg("filescore", "saveteamscore not implemented for filescore");
}
void CFileScore::SaveScore(int ClientID, float Time, const char *pTimestamp,
float CpTime[NUM_CHECKPOINTS], bool NotEligible)
{
CConsole* pCon = (CConsole*) GameServer()->Console();
if (!pCon->m_Cheated || g_Config.m_SvRankCheats)
UpdatePlayer(ClientID, Time, CpTime);
}
void CFileScore::ShowTop5(int ClientID, int Offset)
{
char aBuf[512];
Offset = maximum(1, Offset < 0 ? m_Top.size() + Offset - 3 : Offset);
GameServer()->SendChatTarget(ClientID, "----------- Top 5 -----------");
for (int i = 0; i < 5; i++)
{
if (i + Offset > m_Top.size())
break;
CPlayerScore *r = &m_Top[i + Offset - 1];
str_format(aBuf, sizeof(aBuf),
"%d. %s Time: %d minute(s) %5.2f second(s)", i + Offset,
r->m_aName, (int)r->m_Score / 60,
r->m_Score - ((int)r->m_Score / 60 * 60));
GameServer()->SendChatTarget(ClientID, aBuf);
}
GameServer()->SendChatTarget(ClientID, "------------------------------");
}
void CFileScore::ShowRank(int ClientID, const char* pName)
{
CPlayerScore *pScore;
int Pos = -2;
char aBuf[512];
pScore = SearchName(pName, &Pos, 1);
if (pScore && Pos > -1)
{
float Time = pScore->m_Score;
if (g_Config.m_SvHideScore)
str_format(aBuf, sizeof(aBuf),
"Your time: %d minute(s) %5.2f second(s)", (int)Time / 60,
Time - ((int)Time / 60 * 60));
else
str_format(aBuf, sizeof(aBuf),
"%d. %s Time: %d minute(s) %5.2f second(s), requested by (%s)", Pos,
pScore->m_aName, (int)Time / 60,
Time - ((int)Time / 60 * 60), Server()->ClientName(ClientID));
if (g_Config.m_SvHideScore)
GameServer()->SendChatTarget(ClientID, aBuf);
else
GameServer()->SendChat(-1, CGameContext::CHAT_ALL, aBuf, ClientID);
return;
}
else if (Pos == -1)
str_format(aBuf, sizeof(aBuf), "Several players were found.");
else
str_format(aBuf, sizeof(aBuf), "%s is not ranked", pName);
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::ShowTeamTop5(int ClientID, int Offset)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::ShowTeamRank(int ClientID, const char* pName)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::ShowTopPoints(int ClientID, int Offset)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Team ranks not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::ShowPoints(int ClientID, const char* pName)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Points not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::ShowTimes(int ClientID, const char *pName, int Offset)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Show times not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::ShowTimes(int ClientID, int Offset)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Show times not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::RandomMap(int ClientID, int Stars)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Random map not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::RandomUnfinishedMap(int ClientID, int Stars)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Random unfinished map not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::SaveTeam(int ClientID, const char* Code, const char* Server)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::LoadTeam(const char* Code, int ClientID)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::GetSaves(int ClientID)
{
char aBuf[512];
str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}
void CFileScore::OnShutdown()
{
;
}

View file

@ -1,92 +0,0 @@
/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */
/* copyright (c) 2008 rajh and gregwar. Score stuff */
#ifndef GAME_SERVER_SCORE_FILE_SCORE_H
#define GAME_SERVER_SCORE_FILE_SCORE_H
#include <base/tl/sorted_array.h>
#include <string>
#include "../score.h"
class CFileScore: public IScore
{
CGameContext *m_pGameServer;
IServer *m_pServer;
class CPlayerScore
{
public:
char m_aName[MAX_NAME_LENGTH];
float m_Score;
float m_aCpTime[NUM_CHECKPOINTS];
CPlayerScore() {}
CPlayerScore(const char *pName, float Score,
float aCpTime[NUM_CHECKPOINTS]);
bool operator<(const CPlayerScore& other)
{
return (this->m_Score < other.m_Score);
}
};
sorted_array<CPlayerScore> m_Top;
CGameContext *GameServer()
{
return m_pGameServer;
}
IServer *Server()
{
return m_pServer;
}
CPlayerScore *SearchScore(int ID, int *pPosition)
{
return SearchName(Server()->ClientName(ID), pPosition, 0);
}
CPlayerScore *SearchName(const char *pName, int *pPosition, bool MatchCase);
void UpdatePlayer(int ID, float Score, float aCpTime[NUM_CHECKPOINTS]);
void Init();
void Save();
static void SaveScoreThread(void *pUser);
public:
CFileScore(CGameContext *pGameServer);
~CFileScore();
virtual void LoadPlayerData(int ClientID);
virtual void MapInfo(int ClientID, const char* MapName);
virtual void MapVote(int ClientID, const char* MapName);
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp,
float CpTime[NUM_CHECKPOINTS], bool NotEligible);
virtual void SaveTeamScore(int* ClientIDs, unsigned int Size, float Time, const char *pTimestamp);
virtual void ShowTop5(int ClientID, int Offset = 1);
virtual void ShowRank(int ClientID, const char* pName);
virtual void ShowTeamTop5(int ClientID, int Offset = 1);
virtual void ShowTeamRank(int ClientID, const char* pName);
virtual void ShowTopPoints(int ClientID, int Offset);
virtual void ShowPoints(int ClientID, const char* pName);
virtual void ShowTimes(int ClientID, const char *pName, int Offset = 1);
virtual void ShowTimes(int ClientID, int Offset = 1);
virtual void RandomMap(int ClientID, int Stars);
virtual void RandomUnfinishedMap(int ClientID, int Stars);
virtual void SaveTeam(int ClientID, const char* Code, const char* Server);
virtual void LoadTeam(const char* Code, int ClientID);
virtual void GetSaves(int ClientID);
virtual void OnShutdown();
private:
std::string SaveFile();
};
#endif // GAME_SERVER_SCORE_FILE_SCORE_H

File diff suppressed because it is too large Load diff

View file

@ -1,220 +0,0 @@
/* (c) Shereef Marzouk. See "licence DDRace.txt" and the readme.txt in the root of the distribution for more information. */
/* Based on Race mod stuff and tweaked by GreYFoX@GTi and others to fit our DDRace needs. */
/* CSqlScore Class by Sushi Tee*/
#ifndef GAME_SERVER_SCORE_SQL_H
#define GAME_SERVER_SCORE_SQL_H
#include <string>
#include <vector>
#include <engine/map.h>
#include <engine/server/sql_string_helpers.h>
#include <game/prng.h>
#include "../score.h"
class CSqlServer;
// holding relevant data for one thread, and function pointer for return values
template < typename TResult >
struct CSqlData
{
CSqlData(std::shared_ptr<TResult> pSqlResult) :
m_pResult(pSqlResult)
{ }
std::shared_ptr<TResult> m_pResult;
virtual ~CSqlData() = default;
};
struct CSqlInitData : CSqlData<CScoreInitResult>
{
using CSqlData<CScoreInitResult>::CSqlData;
// current map
sqlstr::CSqlString<MAX_MAP_LENGTH> m_Map;
};
struct CSqlPlayerRequest : CSqlData<CScorePlayerResult>
{
using CSqlData<CScorePlayerResult>::CSqlData;
// object being requested, either map (128 bytes) or player (16 bytes)
sqlstr::CSqlString<MAX_MAP_LENGTH> m_Name;
// current map
sqlstr::CSqlString<MAX_MAP_LENGTH> m_Map;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_RequestingPlayer;
// relevant for /top5 kind of requests
int m_Offset;
};
struct CSqlRandomMapRequest : CSqlData<CScoreRandomMapResult>
{
using CSqlData<CScoreRandomMapResult>::CSqlData;
sqlstr::CSqlString<32> m_ServerType;
sqlstr::CSqlString<MAX_MAP_LENGTH> m_CurrentMap;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_RequestingPlayer;
int m_Stars;
};
struct CSqlScoreData : CSqlData<CScorePlayerResult>
{
using CSqlData<CScorePlayerResult>::CSqlData;
sqlstr::CSqlString<MAX_MAP_LENGTH> m_Map;
char m_GameUuid[UUID_MAXSTRSIZE];
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name;
int m_ClientID;
float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
float m_aCpCurrent[NUM_CHECKPOINTS];
int m_Num;
bool m_Search;
char m_aRequestingPlayer[MAX_NAME_LENGTH];
};
struct CSqlTeamScoreData : CSqlData<void>
{
using CSqlData<void>::CSqlData;
char m_GameUuid[UUID_MAXSTRSIZE];
sqlstr::CSqlString<MAX_MAP_LENGTH> m_Map;
float m_Time;
char m_aTimestamp[TIMESTAMP_STR_LENGTH];
unsigned int m_Size;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_aNames[MAX_CLIENTS];
};
struct CSqlTeamSave : CSqlData<CScoreSaveResult>
{
using CSqlData<CScoreSaveResult>::CSqlData;
virtual ~CSqlTeamSave() {};
char m_ClientName[MAX_NAME_LENGTH];
char m_Map[MAX_MAP_LENGTH];
char m_Code[128];
char m_aGeneratedCode[128];
char m_Server[5];
};
struct CSqlTeamLoad : CSqlData<CScoreSaveResult>
{
using CSqlData<CScoreSaveResult>::CSqlData;
sqlstr::CSqlString<128> m_Code;
sqlstr::CSqlString<MAX_MAP_LENGTH> m_Map;
sqlstr::CSqlString<MAX_NAME_LENGTH> m_RequestingPlayer;
int m_ClientID;
// struct holding all player names in the team or an empty string
char m_aClientNames[MAX_CLIENTS][MAX_NAME_LENGTH];
int m_aClientID[MAX_CLIENTS];
int m_NumPlayer;
};
// controls one thread
template < typename TResult >
struct CSqlExecData
{
CSqlExecData(
bool (*pFuncPtr) (CSqlServer*, const CSqlData<TResult> *, bool),
CSqlData<TResult> *pSqlResult,
bool ReadOnly = true
);
~CSqlExecData();
bool (*m_pFuncPtr) (CSqlServer*, const CSqlData<TResult> *, bool);
CSqlData<TResult> *m_pSqlData;
bool m_ReadOnly;
static void ExecSqlFunc(void *pUser);
};
class IServer;
class CGameContext;
class CSqlScore: public IScore
{
static LOCK ms_FailureFileLock;
static bool Init(CSqlServer* pSqlServer, const CSqlData<CScoreInitResult> *pGameData, bool HandleFailure);
static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData<CScoreRandomMapResult> *pGameData, bool HandleFailure = false);
static bool RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData<CScoreRandomMapResult> *pGameData, bool HandleFailure = false);
static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool LoadPlayerDataThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool MapInfoThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowRankThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTimesThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowPointsThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool GetSavesThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool SaveTeamThread(CSqlServer* pSqlServer, const CSqlData<CScoreSaveResult> *pGameData, bool HandleFailure = false);
static bool LoadTeamThread(CSqlServer* pSqlServer, const CSqlData<CScoreSaveResult> *pGameData, bool HandleFailure = false);
static bool SaveScoreThread(CSqlServer* pSqlServer, const CSqlData<CScorePlayerResult> *pGameData, bool HandleFailure = false);
static bool SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData<void> *pGameData, bool HandleFailure = false);
CGameContext *GameServer() { return m_pGameServer; }
IServer *Server() { return m_pServer; }
CGameContext *m_pGameServer;
IServer *m_pServer;
std::vector<std::string> m_aWordlist;
CPrng m_Prng;
void GeneratePassphrase(char *pBuf, int BufSize);
// returns new SqlResult bound to the player, if no current Thread is active for this player
std::shared_ptr<CScorePlayerResult> NewSqlPlayerResult(int ClientID);
// Creates for player database requests
void ExecPlayerThread(
bool (*pFuncPtr) (CSqlServer*, const CSqlData<CScorePlayerResult> *, bool),
const char* pThreadName,
int ClientID,
const char* pName,
int Offset
);
// returns true if the player should be rate limited
bool RateLimitPlayer(int ClientID);
public:
// keeps track of score-threads
static std::atomic_int ms_InstanceCount;
CSqlScore(CGameContext *pGameServer);
~CSqlScore() {}
// Requested by game context, shouldn't fail in case the player started another thread
virtual void RandomMap(int ClientID, int Stars);
virtual void RandomUnfinishedMap(int ClientID, int Stars);
virtual void MapVote(int ClientID, const char* MapName);
virtual void LoadPlayerData(int ClientID);
// Requested by players (fails if another request by this player is active)
virtual void MapInfo(int ClientID, const char* MapName);
virtual void ShowRank(int ClientID, const char* pName);
virtual void ShowTeamRank(int ClientID, const char* pName);
virtual void ShowPoints(int ClientID, const char* pName);
virtual void ShowTimes(int ClientID, const char* pName, int Offset = 1);
virtual void ShowTimes(int ClientID, int Offset = 1);
virtual void ShowTop5(int ClientID, int Offset = 1);
virtual void ShowTeamTop5(int ClientID, int Offset = 1);
virtual void ShowTopPoints(int ClientID, int Offset = 1);
virtual void GetSaves(int ClientID);
// requested by teams
virtual void SaveTeam(int ClientID, const char* Code, const char* Server);
virtual void LoadTeam(const char* Code, int ClientID);
// Game relevant not allowed to fail due to an ongoing SQL request.
virtual void SaveScore(int ClientID, float Time, const char *pTimestamp,
float CpTime[NUM_CHECKPOINTS], bool NotEligible);
virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time, const char *pTimestamp);
virtual void OnShutdown();
};
#endif // GAME_SERVER_SCORE_SQL_H