diff options
| author | Rat <gmstreetrat@gmail.com> | 2015-03-24 20:01:02 +0100 |
|---|---|---|
| committer | Rat <gmstreetrat@gmail.com> | 2015-03-24 20:01:02 +0100 |
| commit | eebc468e628acc801a6426e03815150c8cfd9172 (patch) | |
| tree | 2f269f7b97bd7fe83b8bfeb3240820521110f9aa /src/server/shared | |
| parent | bc5ebe3d0599a08e93fd6d0a8c63c7e7cc43c35f (diff) | |
| parent | 3ad7776d5061308d3e2b4ff9e3cbf67d48bffdd6 (diff) | |
Merge branch '6.x' of https://github.com/TrinityCore/TrinityCore into 6.x
Conflicts:
src/server/collision/Management/MMapManager.cpp
src/server/game/Conditions/ConditionMgr.cpp
src/server/game/Conditions/ConditionMgr.h
Diffstat (limited to 'src/server/shared')
29 files changed, 1533 insertions, 154 deletions
diff --git a/src/server/shared/CMakeLists.txt b/src/server/shared/CMakeLists.txt index 24902a7d91e..657b3fde349 100644 --- a/src/server/shared/CMakeLists.txt +++ b/src/server/shared/CMakeLists.txt @@ -22,6 +22,7 @@ file(GLOB_RECURSE sources_Networking Networking/*.cpp Networking/*.h) file(GLOB_RECURSE sources_Packets Packets/*.cpp Packets/*.h) file(GLOB_RECURSE sources_Realm Realm/*.cpp Realm/*.h) file(GLOB_RECURSE sources_Threading Threading/*.cpp Threading/*.h) +file(GLOB_RECURSE sources_Updater Updater/*.cpp Updater/*.h) file(GLOB_RECURSE sources_Utilities Utilities/*.cpp Utilities/*.h) file(GLOB sources_localdir *.cpp *.h) @@ -53,6 +54,7 @@ set(shared_STAT_SRCS ${sources_Packets} ${sources_Realm} ${sources_Threading} + ${sources_Updater} ${sources_Utilities} ${sources_localdir} ) @@ -61,7 +63,9 @@ include_directories( ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/dep/recastnavigation/Detour ${CMAKE_SOURCE_DIR}/dep/SFMT + ${CMAKE_SOURCE_DIR}/dep/cppformat ${CMAKE_SOURCE_DIR}/dep/utf8cpp + ${CMAKE_SOURCE_DIR}/dep/process ${CMAKE_SOURCE_DIR}/src/server ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/Configuration @@ -76,6 +80,7 @@ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/Realm ${CMAKE_CURRENT_SOURCE_DIR}/Threading ${CMAKE_CURRENT_SOURCE_DIR}/Utilities + ${CMAKE_CURRENT_SOURCE_DIR}/Updater ${CMAKE_SOURCE_DIR}/src/server/game/Entities/Object ${MYSQL_INCLUDE_DIR} ${OPENSSL_INCLUDE_DIR} diff --git a/src/server/shared/Database/DatabaseLoader.cpp b/src/server/shared/Database/DatabaseLoader.cpp new file mode 100644 index 00000000000..25c400fdfa8 --- /dev/null +++ b/src/server/shared/Database/DatabaseLoader.cpp @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "DatabaseLoader.h" +#include "DBUpdater.h" +#include "Config.h" + +#include <mysqld_error.h> + +DatabaseLoader::DatabaseLoader(std::string const& logger, uint32 const defaultUpdateMask) + : _logger(logger), _autoSetup(sConfigMgr->GetBoolDefault("Updates.AutoSetup", true)), + _updateFlags(sConfigMgr->GetIntDefault("Updates.EnableDatabases", defaultUpdateMask)) +{ +} + +template <class T> +DatabaseLoader& DatabaseLoader::AddDatabase(DatabaseWorkerPool<T>& pool, std::string const& name) +{ + bool const updatesEnabledForThis = DBUpdater<T>::IsEnabled(_updateFlags); + + _open.push(std::make_pair([this, name, updatesEnabledForThis, &pool]() -> bool + { + std::string const dbString = sConfigMgr->GetStringDefault(name + "DatabaseInfo", ""); + if (dbString.empty()) + { + TC_LOG_ERROR(_logger.c_str(), "Database %s not specified in configuration file!", name.c_str()); + return false; + } + + uint8 const asyncThreads = uint8(sConfigMgr->GetIntDefault(name + "Database.WorkerThreads", 1)); + if (asyncThreads < 1 || asyncThreads > 32) + { + TC_LOG_ERROR(_logger.c_str(), "%s database: invalid number of worker threads specified. " + "Please pick a value between 1 and 32.", name.c_str()); + return false; + } + + uint8 const synchThreads = uint8(sConfigMgr->GetIntDefault(name + "Database.SynchThreads", 1)); + + pool.SetConnectionInfo(dbString, asyncThreads, synchThreads); + if (uint32 error = pool.Open()) + { + // Database does not exist + if ((error == ER_BAD_DB_ERROR) && updatesEnabledForThis && _autoSetup) + { + // Try to create the database and connect again if auto setup is enabled + if (DBUpdater<T>::Create(pool) && (!pool.Open())) + error = 0; + } + + // If the error wasn't handled quit + if (error) + { + TC_LOG_ERROR("sql.driver", "\nDatabasePool %s NOT opened. There were errors opening the MySQL connections. Check your SQLDriverLogFile " + "for specific errors. Read wiki at http://collab.kpsn.org/display/tc/TrinityCore+Home", name.c_str()); + + return false; + } + } + return true; + }, + [&pool]() + { + pool.Close(); + })); + + // Populate and update only if updates are enabled for this pool + if (updatesEnabledForThis) + { + _populate.push([this, name, &pool]() -> bool + { + if (!DBUpdater<T>::Populate(pool)) + { + TC_LOG_ERROR(_logger.c_str(), "Could not populate the %s database, see log for details.", name.c_str()); + return false; + } + return true; + }); + + _update.push([this, name, &pool]() -> bool + { + if (!DBUpdater<T>::Update(pool)) + { + TC_LOG_ERROR(_logger.c_str(), "Could not update the %s database, see log for details.", name.c_str()); + return false; + } + return true; + }); + } + + _prepare.push([this, name, &pool]() -> bool + { + if (!pool.PrepareStatements()) + { + TC_LOG_ERROR(_logger.c_str(), "Could not prepare statements of the %s database, see log for details.", name.c_str()); + return false; + } + return true; + }); + + return *this; +} + +bool DatabaseLoader::Load() +{ + if (!OpenDatabases()) + return false; + + if (!PopulateDatabases()) + return false; + + if (!UpdateDatabases()) + return false; + + if (!PrepareStatements()) + return false; + + return true; +} + +bool DatabaseLoader::OpenDatabases() +{ + while (!_open.empty()) + { + std::pair<Predicate, std::function<void()>> const load = _open.top(); + if (load.first()) + _close.push(load.second); + else + { + // Close all loaded databases + while (!_close.empty()) + { + _close.top()(); + _close.pop(); + } + return false; + } + + _open.pop(); + } + return true; +} + +// Processes the elements of the given stack until a predicate returned false. +bool DatabaseLoader::Process(std::stack<Predicate>& stack) +{ + while (!stack.empty()) + { + if (!stack.top()()) + return false; + + stack.pop(); + } + return true; +} + +bool DatabaseLoader::PopulateDatabases() +{ + return Process(_populate); +} + +bool DatabaseLoader::UpdateDatabases() +{ + return Process(_update); +} + +bool DatabaseLoader::PrepareStatements() +{ + return Process(_prepare); +} + +template +DatabaseLoader& DatabaseLoader::AddDatabase<LoginDatabaseConnection>(DatabaseWorkerPool<LoginDatabaseConnection>& pool, std::string const& name); +template +DatabaseLoader& DatabaseLoader::AddDatabase<WorldDatabaseConnection>(DatabaseWorkerPool<WorldDatabaseConnection>& pool, std::string const& name); +template +DatabaseLoader& DatabaseLoader::AddDatabase<CharacterDatabaseConnection>(DatabaseWorkerPool<CharacterDatabaseConnection>& pool, std::string const& name); +template +DatabaseLoader& DatabaseLoader::AddDatabase<HotfixDatabaseConnection>(DatabaseWorkerPool<HotfixDatabaseConnection>& pool, std::string const& name); diff --git a/src/server/shared/Database/DatabaseLoader.h b/src/server/shared/Database/DatabaseLoader.h new file mode 100644 index 00000000000..3bbf7e75771 --- /dev/null +++ b/src/server/shared/Database/DatabaseLoader.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DatabaseLoader_h__ +#define DatabaseLoader_h__ + +#include "DatabaseWorkerPool.h" +#include "DatabaseEnv.h" + +#include <stack> +#include <functional> + +// A helper class to initiate all database worker pools, +// handles updating, delays preparing of statements and cleans up on failure. +class DatabaseLoader +{ +public: + DatabaseLoader(std::string const& logger, uint32 const defaultUpdateMask); + + // Register a database to the loader (lazy implemented) + template <class T> + DatabaseLoader& AddDatabase(DatabaseWorkerPool<T>& pool, std::string const& name); + + // Load all databases + bool Load(); + + enum DatabaseTypeFlags + { + DATABASE_NONE = 0, + + DATABASE_LOGIN = 1, + DATABASE_CHARACTER = 2, + DATABASE_WORLD = 4, + DATABASE_HOTFIX = 8, + + DATABASE_MASK_ALL = DATABASE_LOGIN | DATABASE_CHARACTER | DATABASE_WORLD | DATABASE_HOTFIX + }; + +private: + bool OpenDatabases(); + bool PopulateDatabases(); + bool UpdateDatabases(); + bool PrepareStatements(); + + using Predicate = std::function<bool()>; + + static bool Process(std::stack<Predicate>& stack); + + std::string const _logger; + bool const _autoSetup; + uint32 const _updateFlags; + + std::stack<std::pair<Predicate, std::function<void()>>> _open; + std::stack<std::function<void()>> _close; + std::stack<Predicate> _populate, _update, _prepare; +}; + +#endif // DatabaseLoader_h__ diff --git a/src/server/shared/Database/DatabaseWorkerPool.h b/src/server/shared/Database/DatabaseWorkerPool.h index f1c6a7acbf5..6210986ff8b 100644 --- a/src/server/shared/Database/DatabaseWorkerPool.h +++ b/src/server/shared/Database/DatabaseWorkerPool.h @@ -30,6 +30,7 @@ #include "AdhocStatement.h" #include <mysqld_error.h> +#include <memory> #define MIN_MYSQL_SERVER_VERSION 50100u #define MIN_MYSQL_CLIENT_VERSION 50100u @@ -57,9 +58,9 @@ class DatabaseWorkerPool public: /* Activity state */ - DatabaseWorkerPool() : _connectionInfo(NULL) + DatabaseWorkerPool() : _queue(new ProducerConsumerQueue<SQLOperation*>()), + _async_threads(0), _synch_threads(0) { - _queue = new ProducerConsumerQueue<SQLOperation*>(); memset(_connectionCount, 0, sizeof(_connectionCount)); _connections.resize(IDX_SIZE); @@ -70,31 +71,37 @@ class DatabaseWorkerPool ~DatabaseWorkerPool() { _queue->Cancel(); + } - delete _queue; + void SetConnectionInfo(std::string const& infoString, uint8 const asyncThreads, uint8 const synchThreads) + { + _connectionInfo.reset(new MySQLConnectionInfo(infoString)); - delete _connectionInfo; + _async_threads = asyncThreads; + _synch_threads = synchThreads; } - bool Open(const std::string& infoString, uint8 async_threads, uint8 synch_threads) + uint32 Open() { - _connectionInfo = new MySQLConnectionInfo(infoString); + WPFatal(_connectionInfo.get(), "Connection info was not set!"); TC_LOG_INFO("sql.driver", "Opening DatabasePool '%s'. Asynchronous connections: %u, synchronous connections: %u.", - GetDatabaseName(), async_threads, synch_threads); + GetDatabaseName(), _async_threads, _synch_threads); - bool res = OpenConnections(IDX_ASYNC, async_threads); + uint32 error = OpenConnections(IDX_ASYNC, _async_threads); - if (!res) - return res; + if (error) + return error; - res = OpenConnections(IDX_SYNCH, synch_threads); + error = OpenConnections(IDX_SYNCH, _synch_threads); - if (res) + if (!error) + { TC_LOG_INFO("sql.driver", "DatabasePool '%s' opened successfully. %u total connections running.", GetDatabaseName(), (_connectionCount[IDX_SYNCH] + _connectionCount[IDX_ASYNC])); + } - return res; + return error; } void Close() @@ -120,6 +127,32 @@ class DatabaseWorkerPool TC_LOG_INFO("sql.driver", "All connections on DatabasePool '%s' closed.", GetDatabaseName()); } + //! Prepares all prepared statements + bool PrepareStatements() + { + for (uint8 i = 0; i < IDX_SIZE; ++i) + for (uint32 c = 0; c < _connectionCount[i]; ++c) + { + T* t = _connections[i][c]; + t->LockIfReady(); + if (!t->PrepareStatements()) + { + t->Unlock(); + Close(); + return false; + } + else + t->Unlock(); + } + + return true; + } + + inline MySQLConnectionInfo const* GetConnectionInfo() const + { + return _connectionInfo.get(); + } + /** Delayed one-way statement methods. */ @@ -461,7 +494,7 @@ class DatabaseWorkerPool } private: - bool OpenConnections(InternalIndex type, uint8 numConnections) + uint32 OpenConnections(InternalIndex type, uint8 numConnections) { _connections[type].resize(numConnections); for (uint8 i = 0; i < numConnections; ++i) @@ -469,7 +502,7 @@ class DatabaseWorkerPool T* t; if (type == IDX_ASYNC) - t = new T(_queue, *_connectionInfo); + t = new T(_queue.get(), *_connectionInfo); else if (type == IDX_SYNCH) t = new T(*_connectionInfo); else @@ -478,35 +511,32 @@ class DatabaseWorkerPool _connections[type][i] = t; ++_connectionCount[type]; - bool res = t->Open(); + uint32 error = t->Open(); - if (res) + if (!error) { if (mysql_get_server_version(t->GetHandle()) < MIN_MYSQL_SERVER_VERSION) { TC_LOG_ERROR("sql.driver", "TrinityCore does not support MySQL versions below 5.1"); - res = false; + error = 1; } } // Failed to open a connection or invalid version, abort and cleanup - if (!res) + if (error) { - TC_LOG_ERROR("sql.driver", "DatabasePool %s NOT opened. There were errors opening the MySQL connections. Check your SQLDriverLogFile " - "for specific errors. Read wiki at http://collab.kpsn.org/display/tc/TrinityCore+Home", GetDatabaseName()); - while (_connectionCount[type] != 0) { T* t = _connections[type][i--]; delete t; --_connectionCount[type]; } - - return false; + return error; } } - return true; + // Everything is fine + return 0; } unsigned long EscapeString(char *to, const char *from, unsigned long length) @@ -546,10 +576,13 @@ class DatabaseWorkerPool return _connectionInfo->database.c_str(); } - ProducerConsumerQueue<SQLOperation*>* _queue; //! Queue shared by async worker threads. - std::vector< std::vector<T*> > _connections; - uint32 _connectionCount[2]; //! Counter of MySQL connections; - MySQLConnectionInfo* _connectionInfo; + //! Queue shared by async worker threads. + std::unique_ptr<ProducerConsumerQueue<SQLOperation*>> _queue; + std::vector<std::vector<T*>> _connections; + //! Counter of MySQL connections; + uint32 _connectionCount[IDX_SIZE]; + std::unique_ptr<MySQLConnectionInfo> _connectionInfo; + uint8 _async_threads, _synch_threads; }; #endif diff --git a/src/server/shared/Database/MySQLConnection.cpp b/src/server/shared/Database/MySQLConnection.cpp index 1a9f973d47b..1fa3f01a5e1 100644 --- a/src/server/shared/Database/MySQLConnection.cpp +++ b/src/server/shared/Database/MySQLConnection.cpp @@ -72,7 +72,7 @@ void MySQLConnection::Close() delete this; } -bool MySQLConnection::Open() +uint32 MySQLConnection::Open() { MYSQL *mysqlInit; mysqlInit = mysql_init(NULL); @@ -137,13 +137,13 @@ bool MySQLConnection::Open() // set connection properties to UTF8 to properly handle locales for different // server configs - core sends data in UTF8, so MySQL must expect UTF8 too mysql_set_character_set(m_Mysql, "utf8"); - return PrepareStatements(); + return 0; } else { - TC_LOG_ERROR("sql.sql", "Could not connect to MySQL database at %s: %s\n", m_connectionInfo.host.c_str(), mysql_error(mysqlInit)); + TC_LOG_ERROR("sql.sql", "Could not connect to MySQL database at %s: %s", m_connectionInfo.host.c_str(), mysql_error(mysqlInit)); mysql_close(mysqlInit); - return false; + return mysql_errno(mysqlInit); } } diff --git a/src/server/shared/Database/MySQLConnection.h b/src/server/shared/Database/MySQLConnection.h index d486f5b4679..78d8d2fb5dd 100644 --- a/src/server/shared/Database/MySQLConnection.h +++ b/src/server/shared/Database/MySQLConnection.h @@ -72,9 +72,11 @@ class MySQLConnection MySQLConnection(ProducerConsumerQueue<SQLOperation*>* queue, MySQLConnectionInfo& connInfo); //! Constructor for asynchronous connections. virtual ~MySQLConnection(); - virtual bool Open(); + virtual uint32 Open(); void Close(); + bool PrepareStatements(); + public: bool Execute(const char* sql); bool Execute(PreparedStatement* stmt); @@ -111,7 +113,6 @@ class MySQLConnection MySQLPreparedStatement* GetPreparedStatement(uint32 index); void PrepareStatement(uint32 index, const char* sql, ConnectionFlags flags); - bool PrepareStatements(); virtual void DoPrepareStatements() = 0; protected: diff --git a/src/server/shared/Debugging/WheatyExceptionReport.cpp b/src/server/shared/Debugging/WheatyExceptionReport.cpp index e9f4f9ca9ac..f8f641a9ea7 100644 --- a/src/server/shared/Debugging/WheatyExceptionReport.cpp +++ b/src/server/shared/Debugging/WheatyExceptionReport.cpp @@ -1068,7 +1068,7 @@ bool logChildren) { case btChar: case btStdString: - FormatOutputValue(buffer, basicType, length, (PVOID)offset, sizeof(buffer)); + FormatOutputValue(buffer, basicType, length, (PVOID)offset, sizeof(buffer), elementsCount); symbolDetails.top().Value = buffer; break; default: @@ -1196,7 +1196,8 @@ void WheatyExceptionReport::FormatOutputValue(char * pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress, -size_t bufferSize) +size_t bufferSize, +size_t countOverride) { __try { @@ -1204,10 +1205,15 @@ size_t bufferSize) { case btChar: { - if (strlen((char*)pAddress) > bufferSize - 6) + // Special case handling for char[] type + if (countOverride != 0) + length = countOverride; + else + length = strlen((char*)pAddress); + if (length > bufferSize - 6) pszCurrBuffer += sprintf(pszCurrBuffer, "\"%.*s...\"", bufferSize - 6, (char*)pAddress); else - pszCurrBuffer += sprintf(pszCurrBuffer, "\"%s\"", (char*)pAddress); + pszCurrBuffer += sprintf(pszCurrBuffer, "\"%.*s\"", length, (char*)pAddress); break; } case btStdString: diff --git a/src/server/shared/Debugging/WheatyExceptionReport.h b/src/server/shared/Debugging/WheatyExceptionReport.h index 9137b91aac9..b7731daaa2b 100644 --- a/src/server/shared/Debugging/WheatyExceptionReport.h +++ b/src/server/shared/Debugging/WheatyExceptionReport.h @@ -172,7 +172,7 @@ class WheatyExceptionReport static char * DumpTypeIndex(char *, DWORD64, DWORD, unsigned, DWORD_PTR, bool &, const char*, char*, bool, bool); - static void FormatOutputValue(char * pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress, size_t bufferSize); + static void FormatOutputValue(char * pszCurrBuffer, BasicType basicType, DWORD64 length, PVOID pAddress, size_t bufferSize, size_t countOverride = 0); static BasicType GetBasicType(DWORD typeIndex, DWORD64 modBase); static DWORD_PTR DereferenceUnsafePointer(DWORD_PTR address); diff --git a/src/server/shared/Logging/Appender.cpp b/src/server/shared/Logging/Appender.cpp index dff931e3da8..ca40a857419 100644 --- a/src/server/shared/Logging/Appender.cpp +++ b/src/server/shared/Logging/Appender.cpp @@ -18,6 +18,10 @@ #include "Appender.h" #include "Common.h" #include "Util.h" +#include "StringFormat.h" + +#include <utility> +#include <sstream> std::string LogMessage::getTimeStr(time_t time) { @@ -68,38 +72,36 @@ void Appender::setLogLevel(LogLevel _level) level = _level; } -void Appender::write(LogMessage& message) +void Appender::write(LogMessage* message) { - if (!level || level > message.level) + if (!level || level > message->level) return; - message.prefix.clear(); + std::ostringstream ss; + if (flags & APPENDER_FLAGS_PREFIX_TIMESTAMP) - message.prefix.append(message.getTimeStr()); + ss << message->getTimeStr(); if (flags & APPENDER_FLAGS_PREFIX_LOGLEVEL) { - if (!message.prefix.empty()) - message.prefix.push_back(' '); + if (ss.rdbuf()->in_avail() == 0) + ss << ' '; - char text[MAX_QUERY_LEN]; - snprintf(text, MAX_QUERY_LEN, "%-5s", Appender::getLogLevelString(message.level)); - message.prefix.append(text); + ss << Trinity::StringFormat("%-5s", Appender::getLogLevelString(message->level)); } if (flags & APPENDER_FLAGS_PREFIX_LOGFILTERTYPE) { - if (!message.prefix.empty()) - message.prefix.push_back(' '); + if (ss.rdbuf()->in_avail() == 0) + ss << ' '; - message.prefix.push_back('['); - message.prefix.append(message.type); - message.prefix.push_back(']'); + ss << '[' << message->type << ']'; } - if (!message.prefix.empty()) - message.prefix.push_back(' '); + if (ss.rdbuf()->in_avail() == 0) + ss << ' '; + message->prefix = std::move(ss.str()); _write(message); } diff --git a/src/server/shared/Logging/Appender.h b/src/server/shared/Logging/Appender.h index 6f38eb6aaf7..38c45b3bcf1 100644 --- a/src/server/shared/Logging/Appender.h +++ b/src/server/shared/Logging/Appender.h @@ -21,6 +21,7 @@ #include <unordered_map> #include <string> #include <time.h> +#include <type_traits> #include "Define.h" // Values assigned have their equivalent in enum ACE_Log_Priority @@ -57,16 +58,16 @@ enum AppenderFlags struct LogMessage { - LogMessage(LogLevel _level, std::string const& _type, std::string const& _text) - : level(_level), type(_type), text(_text), mtime(time(NULL)) + LogMessage(LogLevel _level, std::string const& _type, std::string&& _text) + : level(_level), type(_type), text(std::forward<std::string>(_text)), mtime(time(NULL)) { } static std::string getTimeStr(time_t time); std::string getTimeStr(); - LogLevel level; - std::string type; - std::string text; + LogLevel const level; + std::string const type; + std::string const text; std::string prefix; std::string param1; time_t mtime; @@ -91,11 +92,11 @@ class Appender AppenderFlags getFlags() const; void setLogLevel(LogLevel); - void write(LogMessage& message); + void write(LogMessage* message); static const char* getLogLevelString(LogLevel level); private: - virtual void _write(LogMessage const& /*message*/) = 0; + virtual void _write(LogMessage const* /*message*/) = 0; uint8 id; std::string name; diff --git a/src/server/shared/Logging/AppenderConsole.cpp b/src/server/shared/Logging/AppenderConsole.cpp index ae27337fb9a..2efa4db4d2e 100644 --- a/src/server/shared/Logging/AppenderConsole.cpp +++ b/src/server/shared/Logging/AppenderConsole.cpp @@ -158,14 +158,14 @@ void AppenderConsole::ResetColor(bool stdout_stream) #endif } -void AppenderConsole::_write(LogMessage const& message) +void AppenderConsole::_write(LogMessage const* message) { - bool stdout_stream = !(message.level == LOG_LEVEL_ERROR || message.level == LOG_LEVEL_FATAL); + bool stdout_stream = !(message->level == LOG_LEVEL_ERROR || message->level == LOG_LEVEL_FATAL); if (_colored) { uint8 index; - switch (message.level) + switch (message->level) { case LOG_LEVEL_TRACE: index = 5; @@ -189,9 +189,9 @@ void AppenderConsole::_write(LogMessage const& message) } SetColor(stdout_stream, _colors[index]); - utf8printf(stdout_stream ? stdout : stderr, "%s%s", message.prefix.c_str(), message.text.c_str()); + utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str()); ResetColor(stdout_stream); } else - utf8printf(stdout_stream ? stdout : stderr, "%s%s", message.prefix.c_str(), message.text.c_str()); + utf8printf(stdout_stream ? stdout : stderr, "%s%s\n", message->prefix.c_str(), message->text.c_str()); } diff --git a/src/server/shared/Logging/AppenderConsole.h b/src/server/shared/Logging/AppenderConsole.h index 0f9536b3111..0acf7636e35 100644 --- a/src/server/shared/Logging/AppenderConsole.h +++ b/src/server/shared/Logging/AppenderConsole.h @@ -51,7 +51,7 @@ class AppenderConsole: public Appender private: void SetColor(bool stdout_stream, ColorTypes color); void ResetColor(bool stdout_stream); - void _write(LogMessage const& message) override; + void _write(LogMessage const* message) override; bool _colored; ColorTypes _colors[MaxLogLevels]; }; diff --git a/src/server/shared/Logging/AppenderDB.cpp b/src/server/shared/Logging/AppenderDB.cpp index 99ae822af34..8a329ea3a0f 100644 --- a/src/server/shared/Logging/AppenderDB.cpp +++ b/src/server/shared/Logging/AppenderDB.cpp @@ -23,18 +23,18 @@ AppenderDB::AppenderDB(uint8 id, std::string const& name, LogLevel level) AppenderDB::~AppenderDB() { } -void AppenderDB::_write(LogMessage const& message) +void AppenderDB::_write(LogMessage const* message) { // Avoid infinite loop, PExecute triggers Logging with "sql.sql" type - if (!enabled || (message.type.find("sql") != std::string::npos)) + if (!enabled || (message->type.find("sql") != std::string::npos)) return; PreparedStatement* stmt = LoginDatabase.GetPreparedStatement(LOGIN_INS_LOG); - stmt->setUInt64(0, message.mtime); + stmt->setUInt64(0, message->mtime); stmt->setUInt32(1, realmId); - stmt->setString(2, message.type); - stmt->setUInt8(3, uint8(message.level)); - stmt->setString(4, message.text); + stmt->setString(2, message->type); + stmt->setUInt8(3, uint8(message->level)); + stmt->setString(4, message->text); LoginDatabase.Execute(stmt); } diff --git a/src/server/shared/Logging/AppenderDB.h b/src/server/shared/Logging/AppenderDB.h index e20ceaf77b4..09affdb46f1 100644 --- a/src/server/shared/Logging/AppenderDB.h +++ b/src/server/shared/Logging/AppenderDB.h @@ -31,7 +31,7 @@ class AppenderDB: public Appender private: uint32 realmId; bool enabled; - void _write(LogMessage const& message) override; + void _write(LogMessage const* message) override; }; #endif diff --git a/src/server/shared/Logging/AppenderFile.cpp b/src/server/shared/Logging/AppenderFile.cpp index 07a88a367ae..3892adbe3be 100644 --- a/src/server/shared/Logging/AppenderFile.cpp +++ b/src/server/shared/Logging/AppenderFile.cpp @@ -43,21 +43,21 @@ AppenderFile::~AppenderFile() CloseFile(); } -void AppenderFile::_write(LogMessage const& message) +void AppenderFile::_write(LogMessage const* message) { - bool exceedMaxSize = maxFileSize > 0 && (fileSize.load() + message.Size()) > maxFileSize; + bool exceedMaxSize = maxFileSize > 0 && (fileSize.load() + message->Size()) > maxFileSize; if (dynamicName) { char namebuf[TRINITY_PATH_MAX]; - snprintf(namebuf, TRINITY_PATH_MAX, filename.c_str(), message.param1.c_str()); + snprintf(namebuf, TRINITY_PATH_MAX, filename.c_str(), message->param1.c_str()); // always use "a" with dynamic name otherwise it could delete the log we wrote in last _write() call FILE* file = OpenFile(namebuf, "a", backup || exceedMaxSize); if (!file) return; - fprintf(file, "%s%s", message.prefix.c_str(), message.text.c_str()); + fprintf(file, "%s%s", message->prefix.c_str(), message->text.c_str()); fflush(file); - fileSize += uint64(message.Size()); + fileSize += uint64(message->Size()); fclose(file); return; } @@ -67,9 +67,9 @@ void AppenderFile::_write(LogMessage const& message) if (!logfile) return; - fprintf(logfile, "%s%s", message.prefix.c_str(), message.text.c_str()); + fprintf(logfile, "%s%s\n", message->prefix.c_str(), message->text.c_str()); fflush(logfile); - fileSize += uint64(message.Size()); + fileSize += uint64(message->Size()); } FILE* AppenderFile::OpenFile(std::string const &filename, std::string const &mode, bool backup) diff --git a/src/server/shared/Logging/AppenderFile.h b/src/server/shared/Logging/AppenderFile.h index 23651fc1129..36afdd23ad1 100644 --- a/src/server/shared/Logging/AppenderFile.h +++ b/src/server/shared/Logging/AppenderFile.h @@ -30,7 +30,7 @@ class AppenderFile: public Appender private: void CloseFile(); - void _write(LogMessage const& message) override; + void _write(LogMessage const* message) override; FILE* logfile; std::string filename; std::string logDir; diff --git a/src/server/shared/Logging/Log.cpp b/src/server/shared/Logging/Log.cpp index aa432128171..c9a4432039f 100644 --- a/src/server/shared/Logging/Log.cpp +++ b/src/server/shared/Logging/Log.cpp @@ -199,6 +199,9 @@ void Log::CreateLoggerFromConfig(std::string const& appenderName) return; } + if (level < lowestLogLevel) + lowestLogLevel = level; + logger.Create(name, level); //fprintf(stdout, "Log::CreateLoggerFromConfig: Created Logger %s, Level %u\n", name.c_str(), level); @@ -261,30 +264,18 @@ void Log::ReadLoggersFromConfig() } } -void Log::vlog(std::string const& filter, LogLevel level, char const* str, va_list argptr) -{ - char text[MAX_QUERY_LEN]; - vsnprintf(text, MAX_QUERY_LEN, str, argptr); - write(new LogMessage(level, filter, text)); -} - -void Log::write(LogMessage* msg) const +void Log::write(std::unique_ptr<LogMessage>&& msg) const { Logger const* logger = GetLoggerByType(msg->type); - msg->text.append("\n"); if (_ioService) { - auto logOperation = std::shared_ptr<LogOperation>(new LogOperation(logger, msg)); + auto logOperation = std::shared_ptr<LogOperation>(new LogOperation(logger, std::forward<std::unique_ptr<LogMessage>>(msg))); _ioService->post(_strand->wrap([logOperation](){ logOperation->call(); })); - } else - { - logger->write(*msg); - delete msg; - } + logger->write(msg.get()); } std::string Log::GetTimestampStr() @@ -321,6 +312,9 @@ bool Log::SetLogLevel(std::string const& name, const char* newLevelc, bool isLog return false; it->second.setLogLevel(newLevel); + + if (newLevel != LOG_LEVEL_DISABLED && newLevel < lowestLogLevel) + lowestLogLevel = newLevel; } else { @@ -343,33 +337,13 @@ void Log::outCharDump(char const* str, uint32 accountId, uint64 guid, char const ss << "== START DUMP == (account: " << accountId << " guid: " << guid << " name: " << name << ")\n" << str << "\n== END DUMP ==\n"; - LogMessage* msg = new LogMessage(LOG_LEVEL_INFO, "entities.player.dump", ss.str()); + std::unique_ptr<LogMessage> msg(new LogMessage(LOG_LEVEL_INFO, "entities.player.dump", ss.str())); std::ostringstream param; param << guid << '_' << name; msg->param1 = param.str(); - write(msg); -} - -void Log::outCommand(uint32 account, const char * str, ...) -{ - if (!str || !ShouldLog("commands.gm", LOG_LEVEL_INFO)) - return; - - va_list ap; - va_start(ap, str); - char text[MAX_QUERY_LEN]; - vsnprintf(text, MAX_QUERY_LEN, str, ap); - va_end(ap); - - LogMessage* msg = new LogMessage(LOG_LEVEL_INFO, "commands.gm", text); - - std::ostringstream ss; - ss << account; - msg->param1 = ss.str(); - - write(msg); + write(std::move(msg)); } void Log::SetRealmId(uint32 id) @@ -394,6 +368,7 @@ void Log::LoadFromConfig() { Close(); + lowestLogLevel = LOG_LEVEL_FATAL; AppenderId = 0; m_logsDir = sConfigMgr->GetStringDefault("LogsDir", ""); if (!m_logsDir.empty()) diff --git a/src/server/shared/Logging/Log.h b/src/server/shared/Logging/Log.h index 1d67ff87f76..20d83d2dcf0 100644 --- a/src/server/shared/Logging/Log.h +++ b/src/server/shared/Logging/Log.h @@ -22,12 +22,14 @@ #include "Define.h" #include "Appender.h" #include "Logger.h" -#include <stdarg.h> +#include "StringFormat.h" #include <boost/asio/io_service.hpp> #include <boost/asio/strand.hpp> +#include <stdarg.h> #include <unordered_map> #include <string> +#include <memory> #define LOGGER_ROOT "root" @@ -59,17 +61,34 @@ class Log bool ShouldLog(std::string const& type, LogLevel level) const; bool SetLogLevel(std::string const& name, char const* level, bool isLogger = true); - void outMessage(std::string const& f, LogLevel level, char const* str, ...) ATTR_PRINTF(4, 5); + template<typename... Args> + inline void outMessage(std::string const& filter, LogLevel const level, const char* fmt, Args const&... args) + { + write(std::move(std::unique_ptr<LogMessage>(new LogMessage(level, filter, std::move(Trinity::StringFormat(fmt, args...)))))); + } + + template<typename... Args> + void outCommand(uint32 account, const char* fmt, Args const&... args) + { + if (!ShouldLog("commands.gm", LOG_LEVEL_INFO)) + return; + + std::unique_ptr<LogMessage> msg(new LogMessage(LOG_LEVEL_INFO, "commands.gm", std::move(Trinity::StringFormat(fmt, args...)))); + + std::ostringstream ss; + ss << account; + msg->param1 = ss.str(); + + write(std::move(msg)); + } - void outCommand(uint32 account, const char * str, ...) ATTR_PRINTF(3, 4); void outCharDump(char const* str, uint32 account_id, uint64 guid, char const* name); void SetRealmId(uint32 id); private: static std::string GetTimestampStr(); - void vlog(std::string const& f, LogLevel level, char const* str, va_list argptr); - void write(LogMessage* msg) const; + void write(std::unique_ptr<LogMessage>&& msg) const; Logger const* GetLoggerByType(std::string const& type) const; Appender* GetAppenderByName(std::string const& name); @@ -82,6 +101,7 @@ class Log AppenderMap appenders; LoggerMap loggers; uint8 AppenderId; + LogLevel lowestLogLevel; std::string m_logsDir; std::string m_logsTimestamp; @@ -113,6 +133,10 @@ inline bool Log::ShouldLog(std::string const& type, LogLevel level) const // Speed up in cases where requesting "Type.sub1.sub2" but only configured // Logger "Type" + // Don't even look for a logger if the LogLevel is lower than lowest log levels across all loggers + if (level < lowestLogLevel) + return false; + Logger const* logger = GetLoggerByType(type); if (!logger) return false; @@ -121,23 +145,34 @@ inline bool Log::ShouldLog(std::string const& type, LogLevel level) const return logLevel != LOG_LEVEL_DISABLED && logLevel <= level; } -inline void Log::outMessage(std::string const& filter, LogLevel level, const char * str, ...) -{ - va_list ap; - va_start(ap, str); - - vlog(filter, level, str, ap); - - va_end(ap); -} - #define sLog Log::instance() +#define LOG_EXCEPTION_FREE(filterType__, level__, ...) \ + { \ + try \ + { \ + sLog->outMessage(filterType__, level__, __VA_ARGS__); \ + } \ + catch (std::exception& e) \ + { \ + sLog->outMessage("server", LOG_LEVEL_ERROR, "Wrong format occurred (%s) at %s:%u.", \ + e.what(), __FILE__, __LINE__); \ + } \ + } + #if PLATFORM != PLATFORM_WINDOWS +void check_args(const char* format, ...) ATTR_PRINTF(1, 2); + +// This will catch format errors on build time #define TC_LOG_MESSAGE_BODY(filterType__, level__, ...) \ do { \ if (sLog->ShouldLog(filterType__, level__)) \ - sLog->outMessage(filterType__, level__, __VA_ARGS__); \ + { \ + if (false) \ + check_args(__VA_ARGS__); \ + \ + LOG_EXCEPTION_FREE(filterType__, level__, __VA_ARGS__); \ + } \ } while (0) #else #define TC_LOG_MESSAGE_BODY(filterType__, level__, ...) \ @@ -145,7 +180,7 @@ inline void Log::outMessage(std::string const& filter, LogLevel level, const cha __pragma(warning(disable:4127)) \ do { \ if (sLog->ShouldLog(filterType__, level__)) \ - sLog->outMessage(filterType__, level__, __VA_ARGS__); \ + LOG_EXCEPTION_FREE(filterType__, level__, __VA_ARGS__); \ } while (0) \ __pragma(warning(pop)) #endif diff --git a/src/server/shared/Logging/LogOperation.cpp b/src/server/shared/Logging/LogOperation.cpp index 9afb28a49c2..bcd923c705e 100644 --- a/src/server/shared/Logging/LogOperation.cpp +++ b/src/server/shared/Logging/LogOperation.cpp @@ -18,14 +18,8 @@ #include "LogOperation.h" #include "Logger.h" -LogOperation::~LogOperation() -{ - delete msg; -} - int LogOperation::call() { - if (logger && msg) - logger->write(*msg); + logger->write(msg.get()); return 0; } diff --git a/src/server/shared/Logging/LogOperation.h b/src/server/shared/Logging/LogOperation.h index b8655413273..ffdd35c3c09 100644 --- a/src/server/shared/Logging/LogOperation.h +++ b/src/server/shared/Logging/LogOperation.h @@ -18,23 +18,25 @@ #ifndef LOGOPERATION_H #define LOGOPERATION_H +#include <memory> + class Logger; struct LogMessage; class LogOperation { public: - LogOperation(Logger const* _logger, LogMessage* _msg) - : logger(_logger), msg(_msg) + LogOperation(Logger const* _logger, std::unique_ptr<LogMessage>&& _msg) + : logger(_logger), msg(std::forward<std::unique_ptr<LogMessage>>(_msg)) { } - ~LogOperation(); + ~LogOperation() { } int call(); protected: Logger const* logger; - LogMessage* msg; + std::unique_ptr<LogMessage> msg; }; #endif diff --git a/src/server/shared/Logging/Logger.cpp b/src/server/shared/Logging/Logger.cpp index 615732deb30..3b02eb47575 100644 --- a/src/server/shared/Logging/Logger.cpp +++ b/src/server/shared/Logging/Logger.cpp @@ -50,9 +50,9 @@ void Logger::setLogLevel(LogLevel _level) level = _level; } -void Logger::write(LogMessage& message) const +void Logger::write(LogMessage* message) const { - if (!level || level > message.level || message.text.empty()) + if (!level || level > message->level || message->text.empty()) { //fprintf(stderr, "Logger::write: Logger %s, Level %u. Msg %s Level %u WRONG LEVEL MASK OR EMPTY MSG\n", getName().c_str(), getLogLevel(), message.text.c_str(), message.level); return; diff --git a/src/server/shared/Logging/Logger.h b/src/server/shared/Logging/Logger.h index a81ee8d7bd2..1aee75c5d72 100644 --- a/src/server/shared/Logging/Logger.h +++ b/src/server/shared/Logging/Logger.h @@ -32,7 +32,7 @@ class Logger std::string const& getName() const; LogLevel getLogLevel() const; void setLogLevel(LogLevel level); - void write(LogMessage& message) const; + void write(LogMessage* message) const; private: std::string name; diff --git a/src/server/shared/Networking/SocketMgr.h b/src/server/shared/Networking/SocketMgr.h index 0c1940b0e33..e18a2077288 100644 --- a/src/server/shared/Networking/SocketMgr.h +++ b/src/server/shared/Networking/SocketMgr.h @@ -100,7 +100,7 @@ public: } catch (boost::system::system_error const& err) { - TC_LOG_INFO("network", "Failed to retrieve client's remote address %s", err.what()); + TC_LOG_WARN("network", "Failed to retrieve client's remote address %s", err.what()); } } diff --git a/src/server/shared/Updater/DBUpdater.cpp b/src/server/shared/Updater/DBUpdater.cpp new file mode 100644 index 00000000000..10119e08deb --- /dev/null +++ b/src/server/shared/Updater/DBUpdater.cpp @@ -0,0 +1,414 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "DBUpdater.h" +#include "Log.h" +#include "revision.h" +#include "UpdateFetcher.h" +#include "DatabaseLoader.h" +#include "Config.h" + +#include <fstream> +#include <iostream> +#include <unordered_map> +#include <boost/process.hpp> +#include <boost/process/mitigate.hpp> +#include <boost/iostreams/device/file_descriptor.hpp> +#include <boost/system/system_error.hpp> + +using namespace boost::process; +using namespace boost::process::initializers; +using namespace boost::iostreams; + +template<class T> +std::string DBUpdater<T>::GetSourceDirectory() +{ + std::string const entry = sConfigMgr->GetStringDefault("Updates.SourcePath", ""); + if (!entry.empty()) + return entry; + else + return _SOURCE_DIRECTORY; +} + +template<class T> +std::string DBUpdater<T>::GetMySqlCli() +{ + std::string const entry = sConfigMgr->GetStringDefault("Updates.MySqlCLIPath", ""); + if (!entry.empty()) + return entry; + else + return _MYSQL_EXECUTABLE; +} + +// Auth Database +template<> +std::string DBUpdater<LoginDatabaseConnection>::GetConfigEntry() +{ + return "Updates.Auth"; +} + +template<> +std::string DBUpdater<LoginDatabaseConnection>::GetTableName() +{ + return "Auth"; +} + +template<> +std::string DBUpdater<LoginDatabaseConnection>::GetBaseFile() +{ + return DBUpdater<LoginDatabaseConnection>::GetSourceDirectory() + "/sql/base/auth_database.sql"; +} + +template<> +bool DBUpdater<LoginDatabaseConnection>::IsEnabled(uint32 const updateMask) +{ + // This way silences warnings under msvc + return (updateMask & DatabaseLoader::DATABASE_LOGIN) ? true : false; +} + +// World Database +template<> +std::string DBUpdater<WorldDatabaseConnection>::GetConfigEntry() +{ + return "Updates.World"; +} + +template<> +std::string DBUpdater<WorldDatabaseConnection>::GetTableName() +{ + return "World"; +} + +template<> +std::string DBUpdater<WorldDatabaseConnection>::GetBaseFile() +{ + return _FULL_DATABASE; +} + +template<> +bool DBUpdater<WorldDatabaseConnection>::IsEnabled(uint32 const updateMask) +{ + // This way silences warnings under msvc + return (updateMask & DatabaseLoader::DATABASE_WORLD) ? true : false; +} + +template<> +BaseLocation DBUpdater<WorldDatabaseConnection>::GetBaseLocationType() +{ + return LOCATION_DOWNLOAD; +} + +// Character Database +template<> +std::string DBUpdater<CharacterDatabaseConnection>::GetConfigEntry() +{ + return "Updates.Character"; +} + +template<> +std::string DBUpdater<CharacterDatabaseConnection>::GetTableName() +{ + return "Character"; +} + +template<> +std::string DBUpdater<CharacterDatabaseConnection>::GetBaseFile() +{ + return DBUpdater<CharacterDatabaseConnection>::GetSourceDirectory() + "/sql/base/characters_database.sql"; +} + +template<> +bool DBUpdater<CharacterDatabaseConnection>::IsEnabled(uint32 const updateMask) +{ + // This way silences warnings under msvc + return (updateMask & DatabaseLoader::DATABASE_CHARACTER) ? true : false; +} + +// Hotfix Database +template<> +std::string DBUpdater<HotfixDatabaseConnection>::GetConfigEntry() +{ + return "Updates.Hotfix"; +} + +template<> +std::string DBUpdater<HotfixDatabaseConnection>::GetTableName() +{ + return "Hotfixes"; +} + +template<> +std::string DBUpdater<HotfixDatabaseConnection>::GetBaseFile() +{ + return _HOTFIXES_DATABASE; +} + +template<> +bool DBUpdater<HotfixDatabaseConnection>::IsEnabled(uint32 const updateMask) +{ + // This way silences warnings under msvc + return (updateMask & DatabaseLoader::DATABASE_HOTFIX) ? true : false; +} + +template<> +BaseLocation DBUpdater<HotfixDatabaseConnection>::GetBaseLocationType() +{ + return LOCATION_DOWNLOAD; +} + +// All +template<class T> +BaseLocation DBUpdater<T>::GetBaseLocationType() +{ + return LOCATION_REPOSITORY; +} + +template<class T> +bool DBUpdater<T>::CheckExecutable() +{ + DBUpdater<T>::Path const exe(DBUpdater<T>::GetMySqlCli()); + if (!exists(exe)) + { + // Check for mysql in path + std::vector<std::string> args = {"--version"}; + uint32 ret; + try + { + child c = execute(run_exe("mysql"), set_args(args), throw_on_error(), close_stdout()); + ret = wait_for_exit(c); + } + catch (boost::system::system_error&) + { + ret = EXIT_FAILURE; + } + + if (ret == EXIT_FAILURE) + { + TC_LOG_FATAL("sql.updates", "Didn't find executeable mysql binary at \'%s\', correct the path in the *.conf (\"Updates.MySqlCLIPath\").", + absolute(exe).generic_string().c_str()); + + return false; + } + } + return true; +} + +template<class T> +bool DBUpdater<T>::Create(DatabaseWorkerPool<T>& pool) +{ + TC_LOG_INFO("sql.updates", "Database \"%s\" does not exist, do you want to create it? [yes (default) / no]: ", + pool.GetConnectionInfo()->database.c_str()); + + std::string answer; + std::getline(std::cin, answer); + if (!answer.empty() && !(answer.substr(0, 1) == "y")) + return false; + + TC_LOG_INFO("sql.updates", "Creating database \"%s\"...", pool.GetConnectionInfo()->database.c_str()); + + // Path of temp file + static Path const temp("create_table.sql"); + + // Create temporary query to use external mysql cli + std::ofstream file(temp.generic_string()); + if (!file.is_open()) + { + TC_LOG_FATAL("sql.updates", "Failed to create temporary query file \"%s\"!", temp.generic_string().c_str()); + return false; + } + + file << "CREATE DATABASE `" << pool.GetConnectionInfo()->database << "` DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci\n\n"; + + file.close(); + + try + { + DBUpdater<T>::ApplyFile(pool, pool.GetConnectionInfo()->host, pool.GetConnectionInfo()->user, pool.GetConnectionInfo()->password, + pool.GetConnectionInfo()->port_or_socket, "", temp); + } + catch (UpdateException&) + { + TC_LOG_FATAL("sql.updates", "Failed to create database %s! Has the user `CREATE` priviliges?", pool.GetConnectionInfo()->database.c_str()); + boost::filesystem::remove(temp); + return false; + } + + TC_LOG_INFO("sql.updates", "Done."); + boost::filesystem::remove(temp); + return true; +} + +template<class T> +bool DBUpdater<T>::Update(DatabaseWorkerPool<T>& pool) +{ + if (!DBUpdater<T>::CheckExecutable()) + return false; + + TC_LOG_INFO("sql.updates", "Updating %s database...", DBUpdater<T>::GetTableName().c_str()); + + Path const sourceDirectory(GetSourceDirectory()); + + if (!is_directory(sourceDirectory)) + { + TC_LOG_ERROR("sql.updates", "DBUpdater: Given source directory %s does not exist, skipped!", sourceDirectory.generic_string().c_str()); + return false; + } + + UpdateFetcher updateFetcher(sourceDirectory, [&](std::string const& query) { DBUpdater<T>::Apply(pool, query); }, + [&](Path const& file) { DBUpdater<T>::ApplyFile(pool, file); }, + [&](std::string const& query) -> QueryResult { return DBUpdater<T>::Retrieve(pool, query); }); + + uint32 const count = updateFetcher.Update( + sConfigMgr->GetBoolDefault("Updates.Redundancy", true), + sConfigMgr->GetBoolDefault("Updates.AllowRehash", true), + sConfigMgr->GetBoolDefault("Updates.ArchivedRedundancy", false), + sConfigMgr->GetIntDefault("Updates.CleanDeadRefMaxCount", 3)); + + if (!count) + TC_LOG_INFO("sql.updates", ">> %s database is up-to-date!", DBUpdater<T>::GetTableName().c_str()); + else + TC_LOG_INFO("sql.updates", ">> Applied %d %s.", count, count == 1 ? "query" : "queries"); + + return true; +} + +template<class T> +bool DBUpdater<T>::Populate(DatabaseWorkerPool<T>& pool) +{ + { + QueryResult const result = Retrieve(pool, "SHOW TABLES"); + if (result && (result->GetRowCount() > 0)) + return true; + } + + if (!DBUpdater<T>::CheckExecutable()) + return false; + + TC_LOG_INFO("sql.updates", "Database %s is empty, auto populating it...", DBUpdater<T>::GetTableName().c_str()); + + std::string const p = DBUpdater<T>::GetBaseFile(); + if (p.empty()) + { + TC_LOG_INFO("sql.updates", ">> No base file provided, skipped!"); + return true; + } + + Path const base(p); + if (!exists(base)) + { + switch (DBUpdater<T>::GetBaseLocationType()) + { + case LOCATION_REPOSITORY: + { + TC_LOG_ERROR("sql.updates", ">> Base file \"%s\" is missing, try to clone the source again.", + base.generic_string().c_str()); + + break; + } + case LOCATION_DOWNLOAD: + { + TC_LOG_ERROR("sql.updates", ">> File \"%s\" is missing, download it from \"http://www.trinitycore.org/f/files/category/1-database/\"" \ + " and place it in your server directory.", base.filename().generic_string().c_str()); + break; + } + } + return false; + } + + // Update database + TC_LOG_INFO("sql.updates", ">> Applying \'%s\'...", base.generic_string().c_str()); + ApplyFile(pool, base); + + TC_LOG_INFO("sql.updates", ">> Done!"); + return true; +} + +template<class T> +QueryResult DBUpdater<T>::Retrieve(DatabaseWorkerPool<T>& pool, std::string const& query) +{ + return pool.PQuery(query.c_str()); +} + +template<class T> +void DBUpdater<T>::Apply(DatabaseWorkerPool<T>& pool, std::string const& query) +{ + pool.DirectExecute(query.c_str()); +} + +template<class T> +void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, Path const& path) +{ + DBUpdater<T>::ApplyFile(pool, pool.GetConnectionInfo()->host, pool.GetConnectionInfo()->user, pool.GetConnectionInfo()->password, + pool.GetConnectionInfo()->port_or_socket, pool.GetConnectionInfo()->database, path); +} + +template<class T> +void DBUpdater<T>::ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& host, std::string const& user, + std::string const& password, std::string const& port_or_socket, std::string const& database, Path const& path) +{ + std::vector<std::string> args; + args.reserve(7); + + // CLI Client connection info + args.push_back("-h" + host); + args.push_back("-u" + user); + args.push_back("-p" + password); + args.push_back("-P" + port_or_socket); + + // Set the default charset to utf8 + args.push_back("--default-character-set=utf8"); + + // Set max allowed packet to 1 GB + args.push_back("--max-allowed-packet=1GB"); + + // Database + if (!database.empty()) + args.push_back(database); + + // ToDo: use the existing query in memory as virtual file if possible + file_descriptor_source source(path); + + uint32 ret; + try + { + child c = execute(run_exe(DBUpdater<T>::GetMySqlCli().empty() ? "mysql" : + boost::filesystem::absolute(DBUpdater<T>::GetMySqlCli()).generic_string()), + set_args(args), bind_stdin(source), throw_on_error()); + + ret = wait_for_exit(c); + } + catch (boost::system::system_error&) + { + ret = EXIT_FAILURE; + } + + source.close(); + + if (ret != EXIT_SUCCESS) + { + TC_LOG_FATAL("sql.updates", "Applying of file \'%s\' to database \'%s\' failed!" \ + " If you are an user pull the latest revision from the repository. If you are a developer fix your sql query.", + path.generic_string().c_str(), pool.GetConnectionInfo()->database.c_str()); + + throw UpdateException("update failed"); + } +} + +template class DBUpdater<LoginDatabaseConnection>; +template class DBUpdater<WorldDatabaseConnection>; +template class DBUpdater<CharacterDatabaseConnection>; +template class DBUpdater<HotfixDatabaseConnection>; diff --git a/src/server/shared/Updater/DBUpdater.h b/src/server/shared/Updater/DBUpdater.h new file mode 100644 index 00000000000..0caf8a438fb --- /dev/null +++ b/src/server/shared/Updater/DBUpdater.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef DBUpdater_h__ +#define DBUpdater_h__ + +#include "DatabaseEnv.h" + +#include <string> +#include <boost/filesystem.hpp> + +class UpdateException : public std::exception +{ +public: + UpdateException(std::string const& msg) : _msg(msg) { } + ~UpdateException() throw() { } + + char const* what() const throw() override { return _msg.c_str(); } + +private: + std::string const _msg; +}; + +enum BaseLocation +{ + LOCATION_REPOSITORY, + LOCATION_DOWNLOAD +}; + +template <class T> +class DBUpdater +{ +public: + using Path = boost::filesystem::path; + + static std::string GetSourceDirectory(); + + static inline std::string GetConfigEntry(); + + static inline std::string GetTableName(); + + static std::string GetBaseFile(); + + static bool IsEnabled(uint32 const updateMask); + + static BaseLocation GetBaseLocationType(); + + static bool Create(DatabaseWorkerPool<T>& pool); + + static bool Update(DatabaseWorkerPool<T>& pool); + + static bool Populate(DatabaseWorkerPool<T>& pool); + +private: + static std::string GetMySqlCli(); + static bool CheckExecutable(); + + static QueryResult Retrieve(DatabaseWorkerPool<T>& pool, std::string const& query); + static void Apply(DatabaseWorkerPool<T>& pool, std::string const& query); + static void ApplyFile(DatabaseWorkerPool<T>& pool, Path const& path); + static void ApplyFile(DatabaseWorkerPool<T>& pool, std::string const& host, std::string const& user, + std::string const& password, std::string const& port_or_socket, std::string const& database, Path const& path); +}; + +#endif // DBUpdater_h__ diff --git a/src/server/shared/Updater/UpdateFetcher.cpp b/src/server/shared/Updater/UpdateFetcher.cpp new file mode 100644 index 00000000000..b93ca614729 --- /dev/null +++ b/src/server/shared/Updater/UpdateFetcher.cpp @@ -0,0 +1,401 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "UpdateFetcher.h" +#include "Log.h" +#include "Util.h" + +#include <fstream> +#include <chrono> +#include <vector> +#include <sstream> +#include <exception> +#include <unordered_map> +#include <openssl/sha.h> + +using namespace boost::filesystem; + +UpdateFetcher::UpdateFetcher(Path const& sourceDirectory, + std::function<void(std::string const&)> const& apply, + std::function<void(Path const& path)> const& applyFile, + std::function<QueryResult(std::string const&)> const& retrieve) : + _sourceDirectory(sourceDirectory), _apply(apply), _applyFile(applyFile), + _retrieve(retrieve) +{ +} + +UpdateFetcher::LocaleFileStorage UpdateFetcher::GetFileList() const +{ + LocaleFileStorage files; + DirectoryStorage directories = ReceiveIncludedDirectories(); + for (auto const& entry : directories) + FillFileListRecursively(entry.path, files, entry.state, 1); + + return files; +} + +void UpdateFetcher::FillFileListRecursively(Path const& path, LocaleFileStorage& storage, State const state, uint32 const depth) const +{ + static uint32 const MAX_DEPTH = 10; + static directory_iterator const end; + + for (directory_iterator itr(path); itr != end; ++itr) + { + if (is_directory(itr->path())) + { + if (depth < MAX_DEPTH) + FillFileListRecursively(itr->path(), storage, state, depth + 1); + } + else if (itr->path().extension() == ".sql") + { + TC_LOG_TRACE("sql.updates", "Added locale file \"%s\".", itr->path().filename().generic_string().c_str()); + + LocaleFileEntry const entry = { itr->path(), state }; + + // Check for doubled filenames + // Since elements are only compared through their filenames this is ok + if (storage.find(entry) != storage.end()) + { + TC_LOG_FATAL("sql.updates", "Duplicated filename occurred \"%s\", since updates are ordered " \ + "through its filename every name needs to be unique!", itr->path().generic_string().c_str()); + + throw UpdateException("Updating failed, see the log for details."); + } + + storage.insert(entry); + } + } +} + +UpdateFetcher::DirectoryStorage UpdateFetcher::ReceiveIncludedDirectories() const +{ + DirectoryStorage directories; + + QueryResult const result = _retrieve("SELECT `path`, `state` FROM `updates_include`"); + if (!result) + return directories; + + do + { + Field* fields = result->Fetch(); + + std::string path = fields[0].GetString(); + if (path.substr(0, 1) == "$") + path = _sourceDirectory.generic_string() + path.substr(1); + + Path const p(path); + + if (!is_directory(p)) + { + TC_LOG_WARN("sql.updates", "DBUpdater: Given update include directory \"%s\" isn't existing, skipped!", p.generic_string().c_str()); + continue; + } + + DirectoryEntry const entry = { p, AppliedFileEntry::StateConvert(fields[1].GetString()) }; + directories.push_back(entry); + + TC_LOG_TRACE("sql.updates", "Added applied file \"%s\" from remote.", p.filename().generic_string().c_str()); + + } while (result->NextRow()); + + return directories; +} + +UpdateFetcher::AppliedFileStorage UpdateFetcher::ReceiveAppliedFiles() const +{ + AppliedFileStorage map; + + QueryResult result = _retrieve("SELECT `name`, `hash`, `state`, UNIX_TIMESTAMP(`timestamp`) FROM `updates` ORDER BY `name` ASC"); + if (!result) + return map; + + do + { + Field* fields = result->Fetch(); + + AppliedFileEntry const entry = { fields[0].GetString(), fields[1].GetString(), + AppliedFileEntry::StateConvert(fields[2].GetString()), fields[3].GetUInt64() }; + + map.insert(std::make_pair(entry.name, entry)); + } + while (result->NextRow()); + + return map; +} + +UpdateFetcher::SQLUpdate UpdateFetcher::ReadSQLUpdate(boost::filesystem::path const& file) const +{ + std::ifstream in(file.c_str()); + WPFatal(in.is_open(), "Could not read an update file."); + + auto const start_pos = in.tellg(); + in.ignore(std::numeric_limits<std::streamsize>::max()); + auto const char_count = in.gcount(); + in.seekg(start_pos); + + SQLUpdate const update(new std::string(char_count, char{})); + + in.read(&(*update)[0], update->size()); + in.close(); + return update; +} + +uint32 UpdateFetcher::Update(bool const redundancyChecks, bool const allowRehash, bool const archivedRedundancy, int32 const cleanDeadReferencesMaxCount) const +{ + LocaleFileStorage const available = GetFileList(); + AppliedFileStorage applied = ReceiveAppliedFiles(); + + // Fill hash to name cache + HashToFileNameStorage hashToName; + for (auto entry : applied) + hashToName.insert(std::make_pair(entry.second.hash, entry.first)); + + uint32 importedUpdates = 0; + + for (auto const& availableQuery : available) + { + TC_LOG_DEBUG("sql.updates", "Checking update \"%s\"...", availableQuery.first.filename().generic_string().c_str()); + + AppliedFileStorage::const_iterator iter = applied.find(availableQuery.first.filename().string()); + if (iter != applied.end()) + { + // If redundancy is disabled skip it since the update is already applied. + if (!redundancyChecks) + { + TC_LOG_DEBUG("sql.updates", ">> Update is already applied, skipping redundancy checks."); + applied.erase(iter); + continue; + } + + // If the update is in an archived directory and is marked as archived in our database skip redundancy checks (archived updates never change). + if (!archivedRedundancy && (iter->second.state == ARCHIVED) && (availableQuery.second == ARCHIVED)) + { + TC_LOG_DEBUG("sql.updates", ">> Update is archived and marked as archived in database, skipping redundancy checks."); + applied.erase(iter); + continue; + } + } + + // Read update from file + SQLUpdate const update = ReadSQLUpdate(availableQuery.first); + + // Calculate hash + std::string const hash = CalculateHash(update); + + UpdateMode mode = MODE_APPLY; + + // Update is not in our applied list + if (iter == applied.end()) + { + // Catch renames (different filename but same hash) + HashToFileNameStorage::const_iterator const hashIter = hashToName.find(hash); + if (hashIter != hashToName.end()) + { + // Check if the original file was removed if not we've got a problem. + LocaleFileStorage::const_iterator localeIter; + // Push localeIter forward + for (localeIter = available.begin(); (localeIter != available.end()) && + (localeIter->first.filename().string() != hashIter->second); ++localeIter); + + // Conflict! + if (localeIter != available.end()) + { + TC_LOG_WARN("sql.updates", ">> Seems like update \"%s\" \'%s\' was renamed, but the old file is still there! " \ + "Trade it as a new file! (Probably its an unmodified copy of file \"%s\")", + availableQuery.first.filename().string().c_str(), hash.substr(0, 7).c_str(), + localeIter->first.filename().string().c_str()); + } + // Its save to trade the file as renamed here + else + { + TC_LOG_INFO("sql.updates", ">> Renaming update \"%s\" to \"%s\" \'%s\'.", + hashIter->second.c_str(), availableQuery.first.filename().string().c_str(), hash.substr(0, 7).c_str()); + + RenameEntry(hashIter->second, availableQuery.first.filename().string()); + applied.erase(hashIter->second); + continue; + } + } + // Apply the update if it was never seen before. + else + { + TC_LOG_INFO("sql.updates", ">> Applying update \"%s\" \'%s\'...", + availableQuery.first.filename().string().c_str(), hash.substr(0, 7).c_str()); + } + } + // Rehash the update entry if it is contained in our database but with an empty hash. + else if (allowRehash && iter->second.hash.empty()) + { + mode = MODE_REHASH; + + TC_LOG_INFO("sql.updates", ">> Re-hashing update \"%s\" \'%s\'...", availableQuery.first.filename().string().c_str(), + hash.substr(0, 7).c_str()); + } + else + { + // If the hash of the files differs from the one stored in our database reapply the update (because it was changed). + if (iter->second.hash != hash) + { + TC_LOG_INFO("sql.updates", ">> Reapplying update \"%s\" \'%s\' -> \'%s\' (it changed)...", availableQuery.first.filename().string().c_str(), + iter->second.hash.substr(0, 7).c_str(), hash.substr(0, 7).c_str()); + } + else + { + // If the file wasn't changed and just moved update its state if necessary. + if (iter->second.state != availableQuery.second) + { + TC_LOG_DEBUG("sql.updates", ">> Updating state of \"%s\" to \'%s\'...", + availableQuery.first.filename().string().c_str(), AppliedFileEntry::StateConvert(availableQuery.second).c_str()); + + UpdateState(availableQuery.first.filename().string(), availableQuery.second); + } + + TC_LOG_DEBUG("sql.updates", ">> Update is already applied and is matching hash \'%s\'.", hash.substr(0, 7).c_str()); + + applied.erase(iter); + continue; + } + } + + uint32 speed = 0; + AppliedFileEntry const file = { availableQuery.first.filename().string(), hash, availableQuery.second, 0 }; + + switch (mode) + { + case MODE_APPLY: + speed = Apply(availableQuery.first); + /*no break*/ + case MODE_REHASH: + UpdateEntry(file, speed); + break; + } + + if (iter != applied.end()) + applied.erase(iter); + + if (mode == MODE_APPLY) + ++importedUpdates; + } + + // Cleanup up orphaned entries if enabled + if (!applied.empty()) + { + bool const doCleanup = (cleanDeadReferencesMaxCount < 0) || (applied.size() <= static_cast<size_t>(cleanDeadReferencesMaxCount)); + + for (auto const& entry : applied) + { + TC_LOG_WARN("sql.updates", ">> File \'%s\' was applied to the database but is missing in" \ + " your update directory now!", entry.first.c_str()); + + if (doCleanup) + TC_LOG_INFO("sql.updates", "Deleting orphaned entry \'%s\'...", entry.first.c_str()); + } + + if (doCleanup) + CleanUp(applied); + else + { + TC_LOG_ERROR("sql.updates", "Cleanup is disabled! There are %zu dirty files that were applied to your database " \ + "but are now missing in your source directory!", applied.size()); + } + } + + return importedUpdates; +} + +std::string UpdateFetcher::CalculateHash(SQLUpdate const& query) const +{ + // Calculate a Sha1 hash based on query content. + unsigned char digest[SHA_DIGEST_LENGTH]; + SHA1((unsigned char*)query->c_str(), query->length(), (unsigned char*)&digest); + + return ByteArrayToHexStr(digest, SHA_DIGEST_LENGTH); +} + +uint32 UpdateFetcher::Apply(Path const& path) const +{ + using Time = std::chrono::high_resolution_clock; + using ms = std::chrono::milliseconds; + + // Benchmark query speed + auto const begin = Time::now(); + + // Update database + _applyFile(path); + + // Return time the query took to apply + return std::chrono::duration_cast<ms>(Time::now() - begin).count(); +} + +void UpdateFetcher::UpdateEntry(AppliedFileEntry const& entry, uint32 const speed) const +{ + std::string const update = "REPLACE INTO `updates` (`name`, `hash`, `state`, `speed`) VALUES (\"" + + entry.name + "\", \"" + entry.hash + "\", \'" + entry.GetStateAsString() + "\', " + std::to_string(speed) + ")"; + + // Update database + _apply(update); +} + +void UpdateFetcher::RenameEntry(std::string const& from, std::string const& to) const +{ + // Delete target if it exists + { + std::string const update = "DELETE FROM `updates` WHERE `name`=\"" + to + "\""; + + // Update database + _apply(update); + } + + // Rename + { + std::string const update = "UPDATE `updates` SET `name`=\"" + to + "\" WHERE `name`=\"" + from + "\""; + + // Update database + _apply(update); + } +} + +void UpdateFetcher::CleanUp(AppliedFileStorage const& storage) const +{ + if (storage.empty()) + return; + + std::stringstream update; + size_t remaining = storage.size(); + + update << "DELETE FROM `updates` WHERE `name` IN("; + + for (auto const& entry : storage) + { + update << "\"" << entry.first << "\""; + if ((--remaining) > 0) + update << ", "; + } + + update << ")"; + + // Update database + _apply(update.str()); +} + +void UpdateFetcher::UpdateState(std::string const& name, State const state) const +{ + std::string const update = "UPDATE `updates` SET `state`=\'" + AppliedFileEntry::StateConvert(state) + "\' WHERE `name`=\"" + name + "\""; + + // Update database + _apply(update); +} diff --git a/src/server/shared/Updater/UpdateFetcher.h b/src/server/shared/Updater/UpdateFetcher.h new file mode 100644 index 00000000000..fa142547873 --- /dev/null +++ b/src/server/shared/Updater/UpdateFetcher.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef UpdateFetcher_h__ +#define UpdateFetcher_h__ + +#include <DBUpdater.h> + +#include <functional> +#include <string> +#include <memory> +#include <vector> + +class UpdateFetcher +{ + typedef boost::filesystem::path Path; + +public: + UpdateFetcher(Path const& updateDirectory, + std::function<void(std::string const&)> const& apply, + std::function<void(Path const& path)> const& applyFile, + std::function<QueryResult(std::string const&)> const& retrieve); + + uint32 Update(bool const redundancyChecks, bool const allowRehash, + bool const archivedRedundancy, int32 const cleanDeadReferencesMaxCount) const; + +private: + enum UpdateMode + { + MODE_APPLY, + MODE_REHASH + }; + + enum State + { + RELEASED, + ARCHIVED + }; + + struct AppliedFileEntry + { + AppliedFileEntry(std::string const& name_, std::string const& hash_, State state_, uint64 timestamp_) + : name(name_), hash(hash_), state(state_), timestamp(timestamp_) { } + + std::string const name; + + std::string const hash; + + State const state; + + uint64 const timestamp; + + static inline State StateConvert(std::string const& state) + { + return (state == "RELEASED") ? RELEASED : ARCHIVED; + } + + static inline std::string StateConvert(State const state) + { + return (state == RELEASED) ? "RELEASED" : "ARCHIVED"; + } + + std::string GetStateAsString() const + { + return StateConvert(state); + } + }; + + struct DirectoryEntry + { + DirectoryEntry(Path const& path_, State state_) : path(path_), state(state_) { } + + Path const path; + + State const state; + }; + + typedef std::pair<Path, State> LocaleFileEntry; + + struct PathCompare + { + inline bool operator() (LocaleFileEntry const& left, LocaleFileEntry const& right) const + { + return left.first.filename().string() < right.first.filename().string(); + } + }; + + typedef std::set<LocaleFileEntry, PathCompare> LocaleFileStorage; + typedef std::unordered_map<std::string, std::string> HashToFileNameStorage; + typedef std::unordered_map<std::string, AppliedFileEntry> AppliedFileStorage; + typedef std::vector<UpdateFetcher::DirectoryEntry> DirectoryStorage; + typedef std::shared_ptr<std::string> SQLUpdate; + + LocaleFileStorage GetFileList() const; + void FillFileListRecursively(Path const& path, LocaleFileStorage& storage, State const state, uint32 const depth) const; + + DirectoryStorage ReceiveIncludedDirectories() const; + AppliedFileStorage ReceiveAppliedFiles() const; + + SQLUpdate ReadSQLUpdate(Path const& file) const; + std::string CalculateHash(SQLUpdate const& query) const; + + uint32 Apply(Path const& path) const; + + void UpdateEntry(AppliedFileEntry const& entry, uint32 const speed = 0) const; + void RenameEntry(std::string const& from, std::string const& to) const; + void CleanUp(AppliedFileStorage const& storage) const; + + void UpdateState(std::string const& name, State const state) const; + + Path const _sourceDirectory; + + std::function<void(std::string const&)> const _apply; + std::function<void(Path const& path)> const _applyFile; + std::function<QueryResult(std::string const&)> const _retrieve; +}; + +#endif // UpdateFetcher_h__ diff --git a/src/server/shared/Utilities/ServiceWin32.cpp b/src/server/shared/Utilities/ServiceWin32.cpp index c73949fc6a3..3e5e416b1a3 100644 --- a/src/server/shared/Utilities/ServiceWin32.cpp +++ b/src/server/shared/Utilities/ServiceWin32.cpp @@ -255,7 +255,7 @@ bool WinServiceRun() if (!StartServiceCtrlDispatcher(serviceTable)) { - TC_LOG_ERROR("server.worldserver", "StartService Failed. Error [%u]", ::GetLastError()); + TC_LOG_ERROR("server.worldserver", "StartService Failed. Error [%u]", uint32(::GetLastError())); return false; } return true; diff --git a/src/server/shared/Utilities/StringFormat.h b/src/server/shared/Utilities/StringFormat.h new file mode 100644 index 00000000000..70d9aefb14d --- /dev/null +++ b/src/server/shared/Utilities/StringFormat.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2008-2015 TrinityCore <http://www.trinitycore.org/> + * Copyright (C) 2005-2009 MaNGOS <http://getmangos.com/> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef TRINITYCORE_STRING_FORMAT_H +#define TRINITYCORE_STRING_FORMAT_H + +#include <format.h> + +namespace Trinity +{ + //! Default TC string format function + template<typename... Args> + inline std::string StringFormat(const char* fmt, Args const&... args) + { + return fmt::sprintf(fmt, args...); + } +} + +#endif |
