mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 01:58:19 +00:00
Merge #2465
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:
commit
b06bea7c04
2
.github/workflows/build.yaml
vendored
2
.github/workflows/build.yaml
vendored
|
@ -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
|
||||
|
|
|
@ -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
37
cmake/FindSQLite3.cmake
Normal 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
84
scripts/import_file_score.py
Executable 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
90
scripts/move_sqlite.py
Executable 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()
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
84
src/engine/server/databases/connection.cpp
Normal file
84
src/engine/server/databases/connection.cpp
Normal 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());
|
||||
}
|
85
src/engine/server/databases/connection.h
Normal file
85
src/engine/server/databases/connection.h
Normal 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
|
228
src/engine/server/databases/connection_pool.cpp
Normal file
228
src/engine/server/databases/connection_pool.cpp
Normal 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;
|
||||
}
|
||||
|
66
src/engine/server/databases/connection_pool.h
Normal file
66
src/engine/server/databases/connection_pool.h
Normal 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
|
328
src/engine/server/databases/mysql.cpp
Normal file
328
src/engine/server/databases/mysql.cpp
Normal 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();
|
||||
}
|
82
src/engine/server/databases/mysql.h
Normal file
82
src/engine/server/databases/mysql.h
Normal 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
|
241
src/engine/server/databases/sqlite.cpp
Normal file
241
src/engine/server/databases/sqlite.cpp
Normal 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();
|
||||
}
|
65
src/engine/server/databases/sqlite.h
Normal file
65
src/engine/server/databases/sqlite.h
Normal 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
|
|
@ -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,59 +3115,41 @@ 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;
|
||||
|
||||
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);
|
||||
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));
|
||||
|
||||
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());
|
||||
"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);
|
||||
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");
|
||||
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;
|
||||
{
|
||||
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)
|
||||
ReadOnly = true;
|
||||
{
|
||||
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)
|
||||
{
|
||||
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
int Pos = 0;
|
||||
int DstPos = 0;
|
||||
while(DstPos + 2 < DstSize)
|
||||
{
|
||||
if(pSrc[Pos] == '\0')
|
||||
break;
|
||||
if(pSrc[Pos] == '\\' || pSrc[Pos] == '%' || pSrc[Pos] == '_' || pSrc[Pos] == '[')
|
||||
pDst[DstPos++] = '\\';
|
||||
pDst[DstPos++] = pSrc[Pos++];
|
||||
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
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];
|
||||
}
|
||||
}
|
||||
|
||||
newString[pos] = '\0';
|
||||
|
||||
str_copy(pString, newString, size);
|
||||
delete [] newString;
|
||||
pDst[DstPos++] = '\0';
|
||||
return DstPos;
|
||||
}
|
||||
|
||||
void sqlstr::AgoTimeToString(int AgoTime, char *pAgoString)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
class CAntibot;
|
||||
class CGameTeams;
|
||||
class CSaveTee;
|
||||
struct CAntibotCharacterData;
|
||||
|
||||
enum
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
;
|
||||
}
|
|
@ -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
|
@ -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
|
Loading…
Reference in a new issue