1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00

Extension of lobby server functionality

Support for:
- listing of active players
- listing of active rooms
- joining and leaving rooms
- placeholder support for multiple chat rooms
- proxy connections
- invites into private rooms

(only lobby server side for now, client and match server need work)
This commit is contained in:
Ivan Savenko 2023-12-30 00:41:16 +02:00
parent 50c1452221
commit 4271fb3c95
11 changed files with 1031 additions and 162 deletions

View File

@ -75,7 +75,7 @@
"vcmi.lobby.login.title" : "VCMI Lobby",
"vcmi.lobby.login.username" : "Username:",
"vcmi.lobby.login.connecting" : "Connecting...",
// "vcmi.lobby.login.connectionFailed" : "Connection failed: %s",
"vcmi.lobby.login.error" : "Connection error: %s",
"vcmi.client.errors.missingCampaigns" : "{Missing data files}\n\nCampaigns data files were not found! You may be using incomplete or corrupted Heroes 3 data files. Please reinstall game data.",
"vcmi.server.errors.existingProcess" : "Another VCMI server process is running. Please terminate it before starting a new game.",

View File

@ -15,6 +15,9 @@ class NetworkConnection;
class NetworkServer;
class NetworkClient;
using NetworkConnectionPtr = std::shared_ptr<NetworkConnection>;
using NetworkConnectionWeakPtr = std::weak_ptr<NetworkConnection>;
class DLL_LINKAGE INetworkConnectionListener
{
friend class NetworkConnection;
@ -22,7 +25,8 @@ protected:
virtual void onDisconnected(const std::shared_ptr<NetworkConnection> & connection) = 0;
virtual void onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message) = 0;
~INetworkConnectionListener() = default;
public:
virtual ~INetworkConnectionListener() = default;
};
class DLL_LINKAGE INetworkServerListener : public INetworkConnectionListener
@ -32,7 +36,8 @@ protected:
virtual void onNewConnection(const std::shared_ptr<NetworkConnection> &) = 0;
virtual void onTimer() = 0;
~INetworkServerListener() = default;
public:
virtual ~INetworkServerListener() = default;
};
class DLL_LINKAGE INetworkClientListener : public INetworkConnectionListener
@ -43,7 +48,8 @@ protected:
virtual void onConnectionEstablished(const std::shared_ptr<NetworkConnection> &) = 0;
virtual void onTimer() = 0;
~INetworkClientListener() = default;
public:
virtual ~INetworkClientListener() = default;
};

View File

@ -11,6 +11,7 @@ set(lobby_HEADERS
StdInc.h
LobbyDatabase.h
LobbyDefines.h
LobbyServer.h
SQLiteConnection.h
)

View File

@ -11,14 +11,15 @@
#include "LobbyServer.h"
static const std::string DATABASE_PATH = "/home/ivan/vcmi.db";
#include "../lib/VCMIDirs.h"
static const int LISTENING_PORT = 30303;
//static const std::string SERVER_NAME = GameConstants::VCMI_VERSION + " (server)";
//static const std::string SERVER_UUID = boost::uuids::to_string(boost::uuids::random_generator()());
int main(int argc, const char * argv[])
{
LobbyServer server(DATABASE_PATH);
auto databasePath = VCMIDirs::get().userDataPath() / "vcmiLobby.db";
LobbyServer server(databasePath);
server.start(LISTENING_PORT);
server.run();

View File

@ -12,76 +12,256 @@
#include "SQLiteConnection.h"
void LobbyDatabase::createTables()
{
static const std::string createChatMessages = R"(
CREATE TABLE IF NOT EXISTS chatMessages (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
senderName TEXT,
roomType TEXT,
messageText TEXT,
creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
)";
static const std::string createTableGameRooms = R"(
CREATE TABLE IF NOT EXISTS gameRooms (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
roomID TEXT,
hostAccountID TEXT,
status INTEGER NOT NULL DEFAULT 0,
playerLimit INTEGER NOT NULL,
creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
)";
static const std::string createTableGameRoomPlayers = R"(
CREATE TABLE IF NOT EXISTS gameRoomPlayers (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
roomID TEXT,
accountID TEXT
);
)";
static const std::string createTableAccounts = R"(
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
accountID TEXT,
displayName TEXT,
online INTEGER NOT NULL,
lastLoginTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
)";
static const std::string createTableAccountCookies = R"(
CREATE TABLE IF NOT EXISTS accountCookies (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
accountID TEXT,
cookieUUID TEXT,
creationTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
)";
static const std::string createTableGameRoomInvites = R"(
CREATE TABLE IF NOT EXISTS gameRoomInvites (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
roomID TEXT,
accountID TEXT
);
)";
database->prepare(createChatMessages)->execute();
database->prepare(createTableGameRoomPlayers)->execute();
database->prepare(createTableGameRooms)->execute();
database->prepare(createTableAccounts)->execute();
database->prepare(createTableAccountCookies)->execute();
database->prepare(createTableGameRoomInvites)->execute();
}
void LobbyDatabase::prepareStatements()
{
// INSERT INTO
static const std::string insertChatMessageText = R"(
INSERT INTO chatMessages(senderName, messageText) VALUES( ?, ?);
)";
static const std::string insertAccountText = R"(
INSERT INTO accounts(accountID, displayName, online) VALUES(?,?,0);
)";
static const std::string insertAccessCookieText = R"(
INSERT INTO accountCookies(accountID, cookieUUID) VALUES(?,?);
)";
static const std::string insertGameRoomText = R"(
INSERT INTO gameRooms(roomID, hostAccountID, status, playerLimit) VALUES(?, ?, 'empty', 8);
)";
static const std::string insertGameRoomPlayersText = R"(
INSERT INTO gameRoomPlayers(roomID, accountID) VALUES(?,?);
)";
static const std::string insertGameRoomInvitesText = R"(
INSERT INTO gameRoomInvites(roomID, accountID) VALUES(?,?);
)";
// DELETE FROM
static const std::string deleteGameRoomPlayersText = R"(
DELETE FROM gameRoomPlayers WHERE gameRoomID = ? AND accountID = ?
)";
static const std::string deleteGameRoomInvitesText = R"(
DELETE FROM gameRoomInvites WHERE gameRoomID = ? AND accountID = ?
)";
// UPDATE
static const std::string setGameRoomStatusText = R"(
UPDATE gameRooms
SET status = ?
WHERE roomID = ?
)";
static const std::string setGameRoomPlayerLimitText = R"(
UPDATE gameRooms
SET playerLimit = ?
WHERE roomID = ?
)";
// SELECT FROM
static const std::string getRecentMessageHistoryText = R"(
SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',sendTime) AS secondsElapsed
SELECT senderName, messageText, strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) AS secondsElapsed
FROM chatMessages
WHERE secondsElapsed < 60*60*24
ORDER BY sendTime DESC
WHERE secondsElapsed < 60*60*18
ORDER BY creationTime DESC
LIMIT 100
)";
insertChatMessageStatement = database->prepare(insertChatMessageText);
getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText);
}
void LobbyDatabase::createTableChatMessages()
{
static const std::string statementText = R"(
CREATE TABLE IF NOT EXISTS chatMessages (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
senderName TEXT,
messageText TEXT,
sendTime TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
static const std::string getIdleGameRoomText = R"(
SELECT roomID
FROM gameRooms
WHERE hostAccountID = ? AND status = 'idle'
LIMIT 1
)";
auto statement = database->prepare(statementText);
statement->execute();
}
static const std::string getAccountGameRoomText = R"(
SELECT roomID
FROM gameRoomPlayers grp
LEFT JOIN gameRooms gr ON gr.roomID = grp.roomID
WHERE accountID = ? AND status IN ('public', 'private', 'busy')
LIMIT 1
)";
void LobbyDatabase::initializeDatabase()
{
createTableChatMessages();
static const std::string getActiveAccountsText = R"(
SELECT accountID, displayName
FROM accounts
WHERE online <> 1
)";
static const std::string isAccountCookieValidText = R"(
SELECT COUNT(accountID)
FROM accountCookies
WHERE accountID = ? AND cookieUUID = ? AND strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) < ?
)";
static const std::string isGameRoomCookieValidText = R"(
SELECT COUNT(roomID)
FROM gameRooms
LEFT JOIN accountCookies ON accountCookies.accountID = gameRooms.hostAccountID
WHERE roomID = ? AND cookieUUID = ? AND strftime('%s',CURRENT_TIMESTAMP)- strftime('%s',creationTime) < ?
)";
static const std::string isPlayerInGameRoomText = R"(
SELECT COUNT(accountID)
FROM gameRoomPlayers
WHERE accountID = ? AND roomID = ?
)";
static const std::string isPlayerInAnyGameRoomText = R"(
SELECT COUNT(accountID)
FROM gameRoomPlayers
WHERE accountID = ?
)";
static const std::string isAccountExistsText = R"(
SELECT COUNT(accountID)
FROM accounts
WHERE accountID = ?
)";
insertChatMessageStatement = database->prepare(insertChatMessageText);
insertAccountStatement = database->prepare(insertAccountText);
insertAccessCookieStatement = database->prepare(insertAccessCookieText);
insertGameRoomStatement = database->prepare(insertGameRoomText);
insertGameRoomPlayersStatement = database->prepare(insertGameRoomPlayersText);
insertGameRoomInvitesStatement = database->prepare(insertGameRoomInvitesText);
deleteGameRoomPlayersStatement = database->prepare(deleteGameRoomPlayersText);
deleteGameRoomInvitesStatement = database->prepare(deleteGameRoomInvitesText);
setGameRoomStatusStatement = database->prepare(setGameRoomStatusText);
setGameRoomPlayerLimitStatement = database->prepare(setGameRoomPlayerLimitText);
getRecentMessageHistoryStatement = database->prepare(getRecentMessageHistoryText);
getIdleGameRoomStatement = database->prepare(getIdleGameRoomText);
getAccountGameRoomStatement = database->prepare(getAccountGameRoomText);
getActiveAccountsStatement = database->prepare(getActiveAccountsText);
isAccountCookieValidStatement = database->prepare(isAccountCookieValidText);
isPlayerInGameRoomStatement = database->prepare(isPlayerInGameRoomText);
isPlayerInAnyGameRoomStatement = database->prepare(isPlayerInAnyGameRoomText);
isAccountExistsStatement = database->prepare(isAccountExistsText);
}
LobbyDatabase::~LobbyDatabase() = default;
LobbyDatabase::LobbyDatabase(const std::string & databasePath)
LobbyDatabase::LobbyDatabase(const boost::filesystem::path & databasePath)
{
database = SQLiteInstance::open(databasePath, true);
if(!database)
throw std::runtime_error("Failed to open SQLite database!");
initializeDatabase();
createTables();
prepareStatements();
}
void LobbyDatabase::insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText)
{
insertChatMessageStatement->setBinds(sender, messageText);
insertChatMessageStatement->execute();
insertChatMessageStatement->reset();
insertChatMessageStatement->executeOnce(sender, messageText);
}
bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountName)
bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID)
{
return false; //TODO
bool result = false;
isPlayerInAnyGameRoomStatement->setBinds(accountID);
if (isPlayerInAnyGameRoomStatement->execute())
isPlayerInAnyGameRoomStatement->getColumns(result);
isPlayerInAnyGameRoomStatement->reset();
return result;
}
std::vector<LobbyDatabase::ChatMessage> LobbyDatabase::getRecentMessageHistory()
bool LobbyDatabase::isPlayerInGameRoom(const std::string & accountID, const std::string & roomID)
{
std::vector<LobbyDatabase::ChatMessage> result;
bool result = false;
isPlayerInGameRoomStatement->setBinds(accountID, roomID);
if (isPlayerInGameRoomStatement->execute())
isPlayerInGameRoomStatement->getColumns(result);
isPlayerInGameRoomStatement->reset();
return result;
}
std::vector<LobbyChatMessage> LobbyDatabase::getRecentMessageHistory()
{
std::vector<LobbyChatMessage> result;
while(getRecentMessageHistoryStatement->execute())
{
LobbyDatabase::ChatMessage message;
LobbyChatMessage message;
getRecentMessageHistoryStatement->getColumns(message.sender, message.messageText, message.age);
result.push_back(message);
}
@ -89,3 +269,144 @@ std::vector<LobbyDatabase::ChatMessage> LobbyDatabase::getRecentMessageHistory()
return result;
}
void LobbyDatabase::setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus)
{
setGameRoomStatusStatement->executeOnce(vstd::to_underlying(roomStatus), roomID);
}
void LobbyDatabase::setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit)
{
setGameRoomPlayerLimitStatement->executeOnce(playerLimit, roomID);
}
void LobbyDatabase::insertPlayerIntoGameRoom(const std::string & accountID, const std::string & roomID)
{
insertGameRoomPlayersStatement->executeOnce(roomID, accountID);
}
void LobbyDatabase::deletePlayerFromGameRoom(const std::string & accountID, const std::string & roomID)
{
deleteGameRoomPlayersStatement->executeOnce(roomID, accountID);
}
void LobbyDatabase::deleteGameRoomInvite(const std::string & targetAccountID, const std::string & roomID)
{
deleteGameRoomInvitesStatement->executeOnce(roomID, targetAccountID);
}
void LobbyDatabase::insertGameRoomInvite(const std::string & targetAccountID, const std::string & roomID)
{
insertGameRoomInvitesStatement->executeOnce(roomID, targetAccountID);
}
void LobbyDatabase::insertGameRoom(const std::string & roomID, const std::string & hostAccountID)
{
insertGameRoomStatement->executeOnce(roomID, hostAccountID);
}
void LobbyDatabase::insertAccount(const std::string & accountID, const std::string & displayName)
{
insertAccountStatement->executeOnce(accountID, displayName);
}
void LobbyDatabase::insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID)
{
insertAccessCookieStatement->executeOnce(accountID, accessCookieUUID);
}
void LobbyDatabase::updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID)
{
}
void LobbyDatabase::updateAccountLoginTime(const std::string & accountID)
{
}
void LobbyDatabase::updateActiveAccount(const std::string & accountID, bool isActive)
{
}
std::string LobbyDatabase::getAccountDisplayName(const std::string & accountID)
{
return {};
}
LobbyCookieStatus LobbyDatabase::getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime)
{
return {};
}
LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime)
{
return {};
}
LobbyCookieStatus LobbyDatabase::getAccountCookieStatus(const std::string & accountID, std::chrono::seconds cookieLifetime)
{
return {};
}
LobbyInviteStatus LobbyDatabase::getAccountInviteStatus(const std::string & accountID, const std::string & roomID)
{
return {};
}
LobbyRoomState LobbyDatabase::getGameRoomStatus(const std::string & roomID)
{
return {};
}
uint32_t LobbyDatabase::getGameRoomFreeSlots(const std::string & roomID)
{
return 0;
}
bool LobbyDatabase::isAccountExists(const std::string & accountID)
{
return false;
}
std::vector<LobbyGameRoom> LobbyDatabase::getActiveGameRooms()
{
return {};
}
std::vector<LobbyAccount> LobbyDatabase::getActiveAccounts()
{
std::vector<LobbyAccount> result;
while(getActiveAccountsStatement->execute())
{
LobbyAccount entry;
getActiveAccountsStatement->getColumns(entry.accountID, entry.displayName);
result.push_back(entry);
}
getActiveAccountsStatement->reset();
return result;
}
std::string LobbyDatabase::getIdleGameRoom(const std::string & hostAccountID)
{
std::string result;
if (getIdleGameRoomStatement->execute())
getIdleGameRoomStatement->getColumns(result);
getIdleGameRoomStatement->reset();
return result;
}
std::string LobbyDatabase::getAccountGameRoom(const std::string & accountID)
{
std::string result;
if (getAccountGameRoomStatement->execute())
getAccountGameRoomStatement->getColumns(result);
getAccountGameRoomStatement->reset();
return result;
}

View File

@ -9,6 +9,8 @@
*/
#pragma once
#include "LobbyDefines.h"
class SQLiteInstance;
class SQLiteStatement;
@ -23,61 +25,68 @@ class LobbyDatabase
SQLiteStatementPtr insertAccountStatement;
SQLiteStatementPtr insertAccessCookieStatement;
SQLiteStatementPtr insertGameRoomStatement;
SQLiteStatementPtr insertGameRoomPlayersStatement;
SQLiteStatementPtr insertGameRoomInvitesStatement;
SQLiteStatementPtr checkAccessCookieStatement;
SQLiteStatementPtr isPlayerInGameRoomStatement;
SQLiteStatementPtr isAccountNameAvailableStatement;
SQLiteStatementPtr deleteGameRoomPlayersStatement;
SQLiteStatementPtr deleteGameRoomInvitesStatement;
SQLiteStatementPtr getRecentMessageHistoryStatement;
SQLiteStatementPtr setGameRoomStatusStatement;
SQLiteStatementPtr setGameRoomPlayerLimitStatement;
SQLiteStatementPtr insertPlayerIntoGameRoomStatement;
SQLiteStatementPtr removePlayerFromGameRoomStatement;
void initializeDatabase();
SQLiteStatementPtr getRecentMessageHistoryStatement;
SQLiteStatementPtr getIdleGameRoomStatement;
SQLiteStatementPtr getAccountGameRoomStatement;
SQLiteStatementPtr getActiveAccountsStatement;
SQLiteStatementPtr isAccountCookieValidStatement;
SQLiteStatementPtr isGameRoomCookieValidStatement;
SQLiteStatementPtr isPlayerInGameRoomStatement;
SQLiteStatementPtr isPlayerInAnyGameRoomStatement;
SQLiteStatementPtr isAccountExistsStatement;
void prepareStatements();
void createTableChatMessages();
void createTableGameRoomPlayers();
void createTableGameRooms();
void createTableAccounts();
void createTableAccountCookies();
void createTables();
public:
struct GameRoom
{
std::string roomUUID;
std::string roomStatus;
std::chrono::seconds age;
uint32_t playersCount;
uint32_t playersLimit;
};
struct ChatMessage
{
std::string sender;
std::string messageText;
std::chrono::seconds age;
};
explicit LobbyDatabase(const std::string & databasePath);
explicit LobbyDatabase(const boost::filesystem::path & databasePath);
~LobbyDatabase();
void setGameRoomStatus(const std::string & roomUUID, const std::string & roomStatus);
void setGameRoomPlayerLimit(const std::string & roomUUID, uint32_t playerLimit);
void setGameRoomStatus(const std::string & roomID, LobbyRoomState roomStatus);
void setGameRoomPlayerLimit(const std::string & roomID, uint32_t playerLimit);
void insertPlayerIntoGameRoom(const std::string & accountName, const std::string & roomUUID);
void removePlayerFromGameRoom(const std::string & accountName, const std::string & roomUUID);
void insertPlayerIntoGameRoom(const std::string & accountID, const std::string & roomID);
void deletePlayerFromGameRoom(const std::string & accountID, const std::string & roomID);
void insertGameRoom(const std::string & roomUUID, const std::string & hostAccountName);
void insertAccount(const std::string & accountName);
void insertAccessCookie(const std::string & accountName, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime);
void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomName, const std::string & messageText);
void deleteGameRoomInvite(const std::string & targetAccountID, const std::string & roomID);
void insertGameRoomInvite(const std::string & targetAccountID, const std::string & roomID);
std::vector<GameRoom> getActiveGameRooms();
std::vector<ChatMessage> getRecentMessageHistory();
void insertGameRoom(const std::string & roomID, const std::string & hostAccountID);
void insertAccount(const std::string & accountID, const std::string & displayName);
void insertAccessCookie(const std::string & accountID, const std::string & accessCookieUUID);
void insertChatMessage(const std::string & sender, const std::string & roomType, const std::string & roomID, const std::string & messageText);
bool checkAccessCookie(const std::string & accountName, const std::string & accessCookieUUID);
bool isPlayerInGameRoom(const std::string & accountName);
bool isAccountNameAvailable(const std::string & accountName);
void updateAccessCookie(const std::string & accountID, const std::string & accessCookieUUID);
void updateAccountLoginTime(const std::string & accountID);
void updateActiveAccount(const std::string & accountID, bool isActive);
std::vector<LobbyGameRoom> getActiveGameRooms();
std::vector<LobbyAccount> getActiveAccounts();
std::vector<LobbyAccount> getAccountsInRoom(const std::string & roomID);
std::vector<LobbyChatMessage> getRecentMessageHistory();
std::string getIdleGameRoom(const std::string & hostAccountID);
std::string getAccountGameRoom(const std::string & accountID);
std::string getAccountDisplayName(const std::string & accountID);
LobbyCookieStatus getGameRoomCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime);
LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, const std::string & accessCookieUUID, std::chrono::seconds cookieLifetime);
LobbyCookieStatus getAccountCookieStatus(const std::string & accountID, std::chrono::seconds cookieLifetime);
LobbyInviteStatus getAccountInviteStatus(const std::string & accountID, const std::string & roomID);
LobbyRoomState getGameRoomStatus(const std::string & roomID);
uint32_t getGameRoomFreeSlots(const std::string & roomID);
bool isPlayerInGameRoom(const std::string & accountID);
bool isPlayerInGameRoom(const std::string & accountID, const std::string & roomID);
bool isAccountExists(const std::string & accountID);
};

56
lobby/LobbyDefines.h Normal file
View File

@ -0,0 +1,56 @@
/*
* LobbyDefines.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
struct LobbyAccount
{
std::string accountID;
std::string displayName;
//std::string status;
};
struct LobbyGameRoom
{
std::string roomUUID;
std::string roomStatus;
uint32_t playersCount;
uint32_t playersLimit;
};
struct LobbyChatMessage
{
std::string sender;
std::string messageText;
std::chrono::seconds age;
};
enum class LobbyCookieStatus : int32_t
{
INVALID,
EXPIRED,
VALID
};
enum class LobbyInviteStatus : int32_t
{
NOT_INVITED,
INVITED,
DECLINED
};
enum class LobbyRoomState : int32_t
{
IDLE, // server is ready but no players are in the room
PUBLIC, // host has joined and allows anybody to join
PRIVATE, // host has joined but only allows those he invited to join
//BUSY, // match is ongoing
//CANCELLED, // game room was cancelled without starting the game
//CLOSED, // game room was closed after playing for some time
};

View File

@ -14,126 +14,530 @@
#include "../lib/JsonNode.h"
#include "../lib/network/NetworkServer.h"
#include "../lib/network/NetworkConnection.h"
void LobbyServer::sendMessage(const std::shared_ptr<NetworkConnection> & target, const JsonNode & json)
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
static const auto accountCookieLifetime = std::chrono::hours(24*7);
bool LobbyServer::isAccountNameValid(const std::string & accountName)
{
//FIXME: copy-paste from LobbyClient::sendMessage
if (accountName.size() < 4)
return false;
if (accountName.size() < 20)
return false;
for (auto const & c : accountName)
if (!std::isalnum(c))
return false;
return true;
}
std::string LobbyServer::sanitizeChatMessage(const std::string & inputString) const
{
// TODO: sanitize message and remove any "weird" symbols from it
return inputString;
}
NetworkConnectionPtr LobbyServer::findAccount(const std::string & accountID)
{
for (auto const & account : activeAccounts)
if (account.second.accountID == accountID)
return account.first;
return nullptr;
}
NetworkConnectionPtr LobbyServer::findGameRoom(const std::string & gameRoomID)
{
for (auto const & account : activeGameRooms)
if (account.second.roomID == gameRoomID)
return account.first;
return nullptr;
}
void LobbyServer::sendMessage(const NetworkConnectionPtr & target, const JsonNode & json)
{
//NOTE: copy-paste from LobbyClient::sendMessage
std::string payloadString = json.toJson(true);
// FIXME: find better approach
uint8_t * payloadBegin = reinterpret_cast<uint8_t *>(payloadString.data());
uint8_t * payloadEnd = payloadBegin + payloadString.size();
// TODO: find better approach
const uint8_t * payloadBegin = reinterpret_cast<uint8_t *>(payloadString.data());
const uint8_t * payloadEnd = payloadBegin + payloadString.size();
std::vector<uint8_t> payloadBuffer(payloadBegin, payloadEnd);
networkServer->sendPacket(target, payloadBuffer);
}
void LobbyServer::sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie)
{
JsonNode reply;
reply["type"].String() = "accountCreated";
reply["accountID"].String() = accountID;
reply["accountCookie"].String() = accountCookie;
sendMessage(target, reply);
}
void LobbyServer::sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID)
{
JsonNode reply;
reply["type"].String() = "inviteReceived";
reply["accountID"].String() = accountID;
reply["gameRoomID"].String() = gameRoomID;
sendMessage(target, reply);
}
void LobbyServer::sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason)
{
JsonNode reply;
reply["type"].String() = "loginFailed";
reply["reason"].String() = reason;
sendMessage(target, reply);
}
void LobbyServer::sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie)
{
JsonNode reply;
reply["type"].String() = "loginSuccess";
reply["accountCookie"].String() = accountCookie;
sendMessage(target, reply);
}
void LobbyServer::sendChatHistory(const NetworkConnectionPtr & target, const std::vector<LobbyChatMessage> & history)
{
JsonNode reply;
reply["type"].String() = "chatHistory";
for(const auto & message : boost::adaptors::reverse(history))
{
JsonNode jsonEntry;
jsonEntry["messageText"].String() = message.messageText;
jsonEntry["senderName"].String() = message.sender;
jsonEntry["ageSeconds"].Integer() = message.age.count();
reply["messages"].Vector().push_back(jsonEntry);
}
sendMessage(target, reply);
}
void LobbyServer::broadcastActiveAccounts()
{
auto activeAccountsStats = database->getActiveAccounts();
JsonNode reply;
reply["type"].String() = "activeAccounts";
for(const auto & account : activeAccountsStats)
{
JsonNode jsonEntry;
jsonEntry["accountID"].String() = account.accountID;
jsonEntry["displayName"].String() = account.displayName;
// jsonEntry["status"].String() = account.status;
reply["accounts"].Vector().push_back(jsonEntry);
}
for(const auto & connection : activeAccounts)
sendMessage(connection.first, reply);
}
void LobbyServer::broadcastActiveGameRooms()
{
auto activeGameRoomStats = database->getActiveGameRooms();
JsonNode reply;
reply["type"].String() = "activeGameRooms";
for(const auto & gameRoom : activeGameRoomStats)
{
JsonNode jsonEntry;
jsonEntry["gameRoomID"].String() = gameRoom.roomUUID;
jsonEntry["status"].String() = gameRoom.roomStatus;
jsonEntry["status"].Integer() = gameRoom.playersCount;
jsonEntry["status"].Integer() = gameRoom.playersLimit;
reply["gameRooms"].Vector().push_back(jsonEntry);
}
for(const auto & connection : activeAccounts)
sendMessage(connection.first, reply);
}
void LobbyServer::sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID)
{
JsonNode reply;
reply["type"].String() = "accountJoinsRoom";
reply["accountID"].String() = accountID;
sendMessage(target, reply);
}
void LobbyServer::sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID)
{
JsonNode reply;
reply["type"].String() = "joinRoomSuccess";
reply["gameRoomID"].String() = gameRoomID;
sendMessage(target, reply);
}
void LobbyServer::sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & senderName, const std::string & messageText)
{
JsonNode reply;
reply["type"].String() = "chatMessage";
reply["messageText"].String() = messageText;
reply["senderName"].String() = senderName;
reply["roomMode"].String() = roomMode;
reply["roomName"].String() = roomName;
sendMessage(target, reply);
}
void LobbyServer::onTimer()
{
// no-op
}
void LobbyServer::onNewConnection(const std::shared_ptr<NetworkConnection> & connection)
{}
void LobbyServer::onDisconnected(const std::shared_ptr<NetworkConnection> & connection)
void LobbyServer::onNewConnection(const NetworkConnectionPtr & connection)
{
activeAccounts.erase(connection);
// no-op - waiting for incoming data
}
void LobbyServer::onPacketReceived(const std::shared_ptr<NetworkConnection> & connection, const std::vector<uint8_t> & message)
void LobbyServer::onDisconnected(const NetworkConnectionPtr & connection)
{
// FIXME: find better approach
// NOTE: lost connection can be in only one of these lists (or in none of them)
// calling on all possible containers since calling std::map::erase() with non-existing key is legal
activeAccounts.erase(connection);
activeProxies.erase(connection);
activeGameRooms.erase(connection);
broadcastActiveAccounts();
broadcastActiveGameRooms();
}
void LobbyServer::onPacketReceived(const NetworkConnectionPtr & connection, const std::vector<uint8_t> & message)
{
// proxy connection - no processing, only redirect
if (activeProxies.count(connection))
{
auto lockedPtr = activeProxies.at(connection).lock();
if (lockedPtr)
lockedPtr->sendPacket(message);
return;
}
JsonNode json(message.data(), message.size());
if(json["type"].String() == "sendChatMessage")
return receiveSendChatMessage(connection, json);
// TODO: check for json parsing errors
// TODO: validate json based on received message type
if(json["type"].String() == "authentication")
return receiveAuthentication(connection, json);
// communication messages from vcmiclient
if (activeAccounts.count(connection))
{
if(json["type"].String() == "sendChatMessage")
return receiveSendChatMessage(connection, json);
if(json["type"].String() == "joinGameRoom")
return receiveJoinGameRoom(connection, json);
if(json["type"].String() == "openGameRoom")
return receiveOpenGameRoom(connection, json);
if(json["type"].String() == "joinGameRoom")
return receiveJoinGameRoom(connection, json);
if(json["type"].String() == "sendInvite")
return receiveSendInvite(connection, json);
if(json["type"].String() == "declineInvite")
return receiveDeclineInvite(connection, json);
return;
}
// communication messages from vcmiserver
if (activeGameRooms.count(connection))
{
if(json["type"].String() == "leaveGameRoom")
return receiveLeaveGameRoom(connection, json);
return;
}
// unauthorized connections - permit only login or register attempts
if(json["type"].String() == "clientLogin")
return receiveClientLogin(connection, json);
if(json["type"].String() == "clientRegister")
return receiveClientRegister(connection, json);
if(json["type"].String() == "serverLogin")
return receiveServerLogin(connection, json);
if(json["type"].String() == "clientProxyLogin")
return receiveClientProxyLogin(connection, json);
if(json["type"].String() == "serverProxyLogin")
return receiveServerProxyLogin(connection, json);
// TODO: add logging of suspicious connections.
networkServer->closeConnection(connection);
}
void LobbyServer::receiveSendChatMessage(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
void LobbyServer::receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json)
{
if(activeAccounts.count(connection) == 0)
return; // unauthenticated
std::string senderName = activeAccounts[connection].accountName;
std::string senderName = activeAccounts[connection].accountID;
std::string messageText = json["messageText"].String();
std::string messageTextClean = sanitizeChatMessage(messageText);
database->insertChatMessage(senderName, "general", "english", messageText);
if (messageTextClean.empty())
return;
JsonNode reply;
reply["type"].String() = "chatMessage";
reply["messageText"].String() = messageText;
reply["senderName"].String() = senderName;
database->insertChatMessage(senderName, "global", "english", messageText);
for(const auto & connection : activeAccounts)
sendMessage(connection.first, reply);
sendChatMessage(connection.first, "global", "english", senderName, messageText);
}
void LobbyServer::receiveAuthentication(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
void LobbyServer::receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string accountName = json["accountName"].String();
std::string accountID = json["accountID"].String();
std::string displayName = json["displayName"].String();
std::string language = json["language"].String();
// TODO: account cookie check
// TODO: account password check
// TODO: protocol version number
// TODO: client/server mode flag
// TODO: client language
if (database->isAccountExists(accountID))
return sendLoginFailed(connection, "Account name already in use");
activeAccounts[connection].accountName = accountName;
if (isAccountNameValid(accountID))
return sendLoginFailed(connection, "Illegal account name");
std::string accountCookie = boost::uuids::to_string(boost::uuids::random_generator()());
database->insertAccount(accountID, displayName);
database->insertAccessCookie(accountID, accountCookie);
sendAccountCreated(connection, accountID, accountCookie);
}
void LobbyServer::receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string accountID = json["accountID"].String();
std::string accountCookie = json["accountCookie"].String();
std::string language = json["language"].String();
std::string version = json["version"].String();
if (!database->isAccountExists(accountID))
return sendLoginFailed(connection, "Account not found");
auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
if (clientCookieStatus == LobbyCookieStatus::INVALID)
return sendLoginFailed(connection, "Authentification failure");
// prolong existing cookie
database->updateAccessCookie(accountID, accountCookie);
database->updateAccountLoginTime(accountID);
std::string displayName = database->getAccountDisplayName(accountID);
activeAccounts[connection].accountID = accountID;
activeAccounts[connection].displayName = displayName;
activeAccounts[connection].version = version;
activeAccounts[connection].language = language;
sendLoginSuccess(connection, accountCookie);
sendChatHistory(connection, database->getRecentMessageHistory());
// send active accounts list to new account
// and update acount list to everybody else
broadcastActiveAccounts();
}
void LobbyServer::receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string gameRoomID = json["gameRoomID"].String();
std::string accountID = json["accountID"].String();
std::string accountCookie = json["accountCookie"].String();
std::string version = json["version"].String();
auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
if (clientCookieStatus == LobbyCookieStatus::INVALID)
{
JsonNode reply;
reply["type"].String() = "authentication";
sendMessage(connection, reply);
sendLoginFailed(connection, "Invalid credentials");
}
auto history = database->getRecentMessageHistory();
else
{
JsonNode reply;
reply["type"].String() = "chatHistory";
database->insertGameRoom(gameRoomID, accountID);
activeGameRooms[connection].roomID = gameRoomID;
sendLoginSuccess(connection, accountCookie);
}
}
for(const auto & message : boost::adaptors::reverse(history))
void LobbyServer::receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string gameRoomID = json["gameRoomID"].String();
std::string accountID = json["accountID"].String();
std::string accountCookie = json["accountCookie"].String();
auto clientCookieStatus = database->getAccountCookieStatus(accountID, accountCookie, accountCookieLifetime);
if (clientCookieStatus != LobbyCookieStatus::INVALID)
{
for (auto & proxyEntry : awaitingProxies)
{
JsonNode jsonEntry;
if (proxyEntry.accountID != accountID)
continue;
if (proxyEntry.roomID != gameRoomID)
continue;
jsonEntry["messageText"].String() = message.messageText;
jsonEntry["senderName"].String() = message.sender;
jsonEntry["ageSeconds"].Integer() = message.age.count();
proxyEntry.accountConnection = connection;
reply["messages"].Vector().push_back(jsonEntry);
auto gameRoomConnection = proxyEntry.roomConnection.lock();
if (gameRoomConnection)
{
activeProxies[gameRoomConnection] = connection;
activeProxies[connection] = gameRoomConnection;
}
return;
}
sendMessage(connection, reply);
}
networkServer->closeConnection(connection);
}
void LobbyServer::receiveJoinGameRoom(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json)
void LobbyServer::receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json)
{
if(activeAccounts.count(connection) == 0)
return; // unauthenticated
std::string gameRoomID = json["gameRoomID"].String();
std::string guestAccountID = json["guestAccountID"].String();
std::string hostCookie = json["hostCookie"].String();
std::string senderName = activeAccounts[connection].accountName;
auto clientCookieStatus = database->getGameRoomCookieStatus(gameRoomID, hostCookie, accountCookieLifetime);
if(database->isPlayerInGameRoom(senderName))
if (clientCookieStatus != LobbyCookieStatus::INVALID)
{
NetworkConnectionPtr targetAccount = findAccount(guestAccountID);
if (targetAccount == nullptr)
return; // unknown / disconnected account
sendJoinRoomSuccess(targetAccount, gameRoomID);
AwaitingProxyState proxy;
proxy.accountID = guestAccountID;
proxy.roomID = gameRoomID;
proxy.roomConnection = connection;
awaitingProxies.push_back(proxy);
return;
}
networkServer->closeConnection(connection);
}
void LobbyServer::receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string hostAccountID = json["hostAccountID"].String();
std::string accountID = activeAccounts[connection].accountID;
if(database->isPlayerInGameRoom(accountID))
return; // only 1 room per player allowed
// TODO: roomType: private, public
// TODO: additional flags, e.g. allowCheats
// TODO: connection mode: direct or proxy
std::string gameRoomID = database->getIdleGameRoom(hostAccountID);
if (gameRoomID.empty())
return;
std::string roomType = json["roomType"].String();
if (roomType == "public")
database->setGameRoomStatus(gameRoomID, LobbyRoomState::PUBLIC);
if (roomType == "private")
database->setGameRoomStatus(gameRoomID, LobbyRoomState::PRIVATE);
// TODO: additional flags / initial settings, e.g. allowCheats
// TODO: connection mode: direct or proxy. For now direct is assumed
broadcastActiveGameRooms();
sendJoinRoomSuccess(connection, gameRoomID);
}
void LobbyServer::receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string gameRoomID = json["gameRoomID"].String();
std::string accountID = activeAccounts[connection].accountID;
if(database->isPlayerInGameRoom(accountID))
return; // only 1 room per player allowed
NetworkConnectionPtr targetRoom = findGameRoom(gameRoomID);
if (targetRoom == nullptr)
return; // unknown / disconnected room
auto roomStatus = database->getGameRoomStatus(gameRoomID);
if (roomStatus == LobbyRoomState::PRIVATE)
{
if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
return;
}
if (database->getGameRoomFreeSlots(gameRoomID) == 0)
return;
sendAccountJoinsRoom(targetRoom, accountID);
//No reply to client - will be sent once match server establishes proxy connection with lobby
broadcastActiveGameRooms();
}
void LobbyServer::receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string gameRoomID = json["gameRoomID"].String();
std::string senderName = activeAccounts[connection].accountID;
if(!database->isPlayerInGameRoom(senderName, gameRoomID))
return;
broadcastActiveGameRooms();
}
void LobbyServer::receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string senderName = activeAccounts[connection].accountID;
std::string accountID = json["accountID"].String();
std::string gameRoomID = database->getAccountGameRoom(senderName);
auto targetAccount = findAccount(accountID);
if (!targetAccount)
return; // target player does not exists or offline
if(!database->isPlayerInGameRoom(senderName))
return; // current player is not in room
if(database->isPlayerInGameRoom(accountID))
return; // target player is busy
if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::NOT_INVITED)
return; // already has invite
database->insertGameRoomInvite(accountID, gameRoomID);
sendInviteReceived(targetAccount, senderName, gameRoomID);
}
void LobbyServer::receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json)
{
std::string accountID = activeAccounts[connection].accountID;
std::string gameRoomID = json["gameRoomID"].String();
if (database->getAccountInviteStatus(accountID, gameRoomID) != LobbyInviteStatus::INVITED)
return; // already has invite
database->deleteGameRoomInvite(accountID, gameRoomID);
}
LobbyServer::~LobbyServer() = default;
LobbyServer::LobbyServer(const std::string & databasePath)
LobbyServer::LobbyServer(const boost::filesystem::path & databasePath)
: database(new LobbyDatabase(databasePath))
, networkServer(new NetworkServer(*this))
{

View File

@ -9,6 +9,7 @@
*/
#pragma once
#include "LobbyDefines.h"
#include "../lib/network/NetworkListener.h"
VCMI_LIB_NAMESPACE_BEGIN
@ -21,29 +22,78 @@ class LobbyServer : public INetworkServerListener
{
struct AccountState
{
std::string accountName;
std::string accountID;
std::string displayName;
std::string version;
std::string language;
};
struct GameRoomState
{
std::string roomID;
};
struct AwaitingProxyState
{
std::string accountID;
std::string roomID;
std::weak_ptr<NetworkConnection> accountConnection;
std::weak_ptr<NetworkConnection> roomConnection;
};
std::map<std::shared_ptr<NetworkConnection>, AccountState> activeAccounts;
/// list of connected proxies. All messages received from (key) will be redirected to (value) connection
std::map<NetworkConnectionPtr, std::weak_ptr<NetworkConnection>> activeProxies;
/// list of half-established proxies from server that are still waiting for client to connect
std::vector<AwaitingProxyState> awaitingProxies;
/// list of logged in accounts (vcmiclient's)
std::map<NetworkConnectionPtr, AccountState> activeAccounts;
/// list of currently logged in game rooms (vcmiserver's)
std::map<NetworkConnectionPtr, GameRoomState> activeGameRooms;
std::unique_ptr<LobbyDatabase> database;
std::unique_ptr<NetworkServer> networkServer;
std::string sanitizeChatMessage(const std::string & inputString) const;
bool isAccountNameValid(const std::string & accountName);
void onNewConnection(const std::shared_ptr<NetworkConnection> &) override;
void onDisconnected(const std::shared_ptr<NetworkConnection> &) override;
void onPacketReceived(const std::shared_ptr<NetworkConnection> &, const std::vector<uint8_t> & message) override;
NetworkConnectionPtr findAccount(const std::string & accountID);
NetworkConnectionPtr findGameRoom(const std::string & gameRoomID);
void onNewConnection(const NetworkConnectionPtr & connection) override;
void onDisconnected(const NetworkConnectionPtr & connection) override;
void onPacketReceived(const NetworkConnectionPtr & connection, const std::vector<uint8_t> & message) override;
void onTimer() override;
void sendMessage(const std::shared_ptr<NetworkConnection> & target, const JsonNode & json);
void sendMessage(const NetworkConnectionPtr & target, const JsonNode & json);
void receiveSendChatMessage(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json);
void receiveAuthentication(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json);
void receiveJoinGameRoom(const std::shared_ptr<NetworkConnection> & connection, const JsonNode & json);
void broadcastActiveAccounts();
void broadcastActiveGameRooms();
void sendChatMessage(const NetworkConnectionPtr & target, const std::string & roomMode, const std::string & roomName, const std::string & senderName, const std::string & messageText);
void sendAccountCreated(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & accountCookie);
void sendLoginFailed(const NetworkConnectionPtr & target, const std::string & reason);
void sendLoginSuccess(const NetworkConnectionPtr & target, const std::string & accountCookie);
void sendChatHistory(const NetworkConnectionPtr & target, const std::vector<LobbyChatMessage> &);
void sendAccountJoinsRoom(const NetworkConnectionPtr & target, const std::string & accountID);
void sendJoinRoomSuccess(const NetworkConnectionPtr & target, const std::string & gameRoomID);
void sendInviteReceived(const NetworkConnectionPtr & target, const std::string & accountID, const std::string & gameRoomID);
void receiveClientRegister(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveClientLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveServerLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveClientProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveServerProxyLogin(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveSendChatMessage(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveOpenGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveJoinGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveLeaveGameRoom(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveSendInvite(const NetworkConnectionPtr & connection, const JsonNode & json);
void receiveDeclineInvite(const NetworkConnectionPtr & connection, const JsonNode & json);
public:
LobbyServer(const std::string & databasePath);
explicit LobbyServer(const boost::filesystem::path & databasePath);
~LobbyServer();
void start(uint16_t port);

View File

@ -70,6 +70,12 @@ void SQLiteStatement::setBindSingle(size_t index, const double & value)
checkSQLiteError(m_instance.m_connection, result);
}
void SQLiteStatement::setBindSingle(size_t index, const bool & value)
{
int result = sqlite3_bind_int(m_statement, static_cast<int>(value), value);
checkSQLiteError(m_instance.m_connection, result);
}
void SQLiteStatement::setBindSingle(size_t index, const uint8_t & value)
{
int result = sqlite3_bind_int(m_statement, static_cast<int>(index), value);
@ -116,6 +122,11 @@ void SQLiteStatement::getColumnSingle(size_t index, double & value)
value = sqlite3_column_double(m_statement, static_cast<int>(index));
}
void SQLiteStatement::getColumnSingle(size_t index, bool & value)
{
value = sqlite3_column_int(m_statement, static_cast<int>(index)) != 0;
}
void SQLiteStatement::getColumnSingle(size_t index, uint8_t & value)
{
value = static_cast<uint8_t>(sqlite3_column_int(m_statement, static_cast<int>(index)));
@ -147,7 +158,7 @@ void SQLiteStatement::getColumnSingle(size_t index, std::string & value)
value = reinterpret_cast<const char *>(value_raw);
}
SQLiteInstancePtr SQLiteInstance::open(const std::string & db_path, bool allow_write)
SQLiteInstancePtr SQLiteInstance::open(const boost::filesystem::path & db_path, bool allow_write)
{
int flags = allow_write ? (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE) : SQLITE_OPEN_READONLY;

View File

@ -29,6 +29,14 @@ public:
~SQLiteStatement();
template<typename... Args>
void executeOnce(const Args &... args)
{
setBinds(args...);
execute();
reset();
}
template<typename... Args>
void setBinds(const Args &... args)
{
@ -43,6 +51,7 @@ public:
private:
void setBindSingle(size_t index, const double & value);
void setBindSingle(size_t index, const bool & value);
void setBindSingle(size_t index, const uint8_t & value);
void setBindSingle(size_t index, const uint16_t & value);
void setBindSingle(size_t index, const uint32_t & value);
@ -52,6 +61,7 @@ private:
void setBindSingle(size_t index, const char * value);
void getColumnSingle(size_t index, double & value);
void getColumnSingle(size_t index, bool & value);
void getColumnSingle(size_t index, uint8_t & value);
void getColumnSingle(size_t index, uint16_t & value);
void getColumnSingle(size_t index, uint32_t & value);
@ -92,7 +102,7 @@ class SQLiteInstance : boost::noncopyable
public:
friend class SQLiteStatement;
static SQLiteInstancePtr open(const std::string & db_path, bool allow_write);
static SQLiteInstancePtr open(const boost::filesystem::path & db_path, bool allow_write);
~SQLiteInstance();