aboutsummaryrefslogtreecommitdiff
path: root/src/server/shared
diff options
context:
space:
mode:
authorRat <gmstreetrat@gmail.com>2015-03-24 20:01:02 +0100
committerRat <gmstreetrat@gmail.com>2015-03-24 20:01:02 +0100
commiteebc468e628acc801a6426e03815150c8cfd9172 (patch)
tree2f269f7b97bd7fe83b8bfeb3240820521110f9aa /src/server/shared
parentbc5ebe3d0599a08e93fd6d0a8c63c7e7cc43c35f (diff)
parent3ad7776d5061308d3e2b4ff9e3cbf67d48bffdd6 (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')
-rw-r--r--src/server/shared/CMakeLists.txt5
-rw-r--r--src/server/shared/Database/DatabaseLoader.cpp193
-rw-r--r--src/server/shared/Database/DatabaseLoader.h72
-rw-r--r--src/server/shared/Database/DatabaseWorkerPool.h91
-rw-r--r--src/server/shared/Database/MySQLConnection.cpp8
-rw-r--r--src/server/shared/Database/MySQLConnection.h5
-rw-r--r--src/server/shared/Debugging/WheatyExceptionReport.cpp14
-rw-r--r--src/server/shared/Debugging/WheatyExceptionReport.h2
-rw-r--r--src/server/shared/Logging/Appender.cpp34
-rw-r--r--src/server/shared/Logging/Appender.h15
-rw-r--r--src/server/shared/Logging/AppenderConsole.cpp10
-rw-r--r--src/server/shared/Logging/AppenderConsole.h2
-rw-r--r--src/server/shared/Logging/AppenderDB.cpp12
-rw-r--r--src/server/shared/Logging/AppenderDB.h2
-rw-r--r--src/server/shared/Logging/AppenderFile.cpp14
-rw-r--r--src/server/shared/Logging/AppenderFile.h2
-rw-r--r--src/server/shared/Logging/Log.cpp49
-rw-r--r--src/server/shared/Logging/Log.h69
-rw-r--r--src/server/shared/Logging/LogOperation.cpp8
-rw-r--r--src/server/shared/Logging/LogOperation.h10
-rw-r--r--src/server/shared/Logging/Logger.cpp4
-rw-r--r--src/server/shared/Logging/Logger.h2
-rw-r--r--src/server/shared/Networking/SocketMgr.h2
-rw-r--r--src/server/shared/Updater/DBUpdater.cpp414
-rw-r--r--src/server/shared/Updater/DBUpdater.h79
-rw-r--r--src/server/shared/Updater/UpdateFetcher.cpp401
-rw-r--r--src/server/shared/Updater/UpdateFetcher.h132
-rw-r--r--src/server/shared/Utilities/ServiceWin32.cpp2
-rw-r--r--src/server/shared/Utilities/StringFormat.h34
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