26#include <boost/filesystem/operations.hpp>
41 std::function<
void(std::string
const&)>
const&
apply,
42 std::function<
void(
Path const& path)>
const& applyFile,
43 std::function<
QueryResult(std::string
const&)>
const& retrieve) :
44 _sourceDirectory(
std::make_unique<
Path>(sourceDirectory)), _apply(
apply), _applyFile(applyFile),
57 for (
auto const& entry : directories)
65 static uint32 const MAX_DEPTH = 10;
66 static directory_iterator
const end;
68 for (directory_iterator itr(path); itr != end; ++itr)
70 if (is_directory(itr->path()))
72 if (depth < MAX_DEPTH)
75 else if (itr->path().extension() ==
".sql")
77 TC_LOG_TRACE(
"sql.updates",
"Added locale file \"{}\".", itr->path().filename().generic_string());
83 if (storage.find(entry) != storage.end())
85 TC_LOG_FATAL(
"sql.updates",
"Duplicate filename \"{}\" occurred. Because updates are ordered " \
86 "by their filenames, every name needs to be unique!", itr->path().generic_string());
91 storage.insert(entry);
106 Field* fields = result->Fetch();
108 std::string path = fields[0].
GetString();
109 if (path.substr(0, 1) ==
"$")
114 if (!is_directory(p))
116 TC_LOG_WARN(
"sql.updates",
"DBUpdater: Given update include directory \"{}\" does not exist, skipped!", p.generic_string());
121 directories.push_back(entry);
123 TC_LOG_TRACE(
"sql.updates",
"Added applied file \"{}\" from remote.", p.filename().generic_string());
125 }
while (result->NextRow());
134 QueryResult result =
_retrieve(
"SELECT `name`, `hash`, `state`, UNIX_TIMESTAMP(`timestamp`) FROM `updates` ORDER BY `name` ASC");
140 Field* fields = result->Fetch();
145 map.insert(std::make_pair(entry.
name, entry));
147 while (result->NextRow());
154 std::ifstream in(file.c_str());
157 TC_LOG_FATAL(
"sql.updates",
"Failed to open the sql update \"{}\" for reading! "
158 "Stopping the server to keep the database integrity, "
159 "try to identify and solve the issue or disable the database updater.",
160 file.generic_string());
165 auto update = [&in] {
166 std::ostringstream ss;
176 bool const allowRehash,
177 bool const archivedRedundancy,
178 int32 const cleanDeadReferencesMaxCount)
const
183 size_t countRecentUpdates = 0;
184 size_t countArchivedUpdates = 0;
187 for (
auto const& entry : applied)
189 ++countRecentUpdates;
191 ++countArchivedUpdates;
195 for (
auto entry : applied)
196 hashToName.insert(std::make_pair(entry.second.hash, entry.first));
198 size_t importedUpdates = 0;
200 for (
auto const& availableQuery : available)
202 TC_LOG_DEBUG(
"sql.updates",
"Checking update \"{}\"...", availableQuery.first.filename().generic_string());
204 AppliedFileStorage::const_iterator iter = applied.find(availableQuery.first.filename().string());
205 if (iter != applied.end())
208 if (!redundancyChecks)
210 TC_LOG_DEBUG(
"sql.updates",
">> Update is already applied, skipping redundancy checks.");
216 if (!archivedRedundancy && (iter->second.state ==
ARCHIVED) && (availableQuery.second ==
ARCHIVED))
218 TC_LOG_DEBUG(
"sql.updates",
">> Update is archived and marked as archived in database, skipping redundancy checks.");
230 if (iter == applied.end())
233 HashToFileNameStorage::const_iterator
const hashIter = hashToName.find(hash);
234 if (hashIter != hashToName.end())
237 LocaleFileStorage::const_iterator localeIter;
239 for (localeIter = available.begin(); (localeIter != available.end()) &&
240 (localeIter->first.filename().string() != hashIter->second); ++localeIter);
243 if (localeIter != available.end())
245 TC_LOG_WARN(
"sql.updates",
">> It seems like the update \"{}\" \'{}\' was renamed, but the old file is still there! " \
246 "Treating it as a new file! (It is probably an unmodified copy of the file \"{}\")",
247 availableQuery.first.filename().string(), hash.substr(0, 7),
248 localeIter->first.filename().string());
253 TC_LOG_INFO(
"sql.updates",
">> Renaming update \"{}\" to \"{}\" \'{}\'.",
254 hashIter->second, availableQuery.first.filename().string(), hash.substr(0, 7));
256 RenameEntry(hashIter->second, availableQuery.first.filename().string());
257 applied.erase(hashIter->second);
264 TC_LOG_INFO(
"sql.updates",
">> Applying update \"{}\" \'{}\'...",
265 availableQuery.first.filename().string(), hash.substr(0, 7));
269 else if (allowRehash && iter->second.hash.empty())
273 TC_LOG_INFO(
"sql.updates",
">> Re-hashing update \"{}\" \'{}\'...", availableQuery.first.filename().string(),
279 if (iter->second.hash != hash)
281 TC_LOG_INFO(
"sql.updates",
">> Reapplying update \"{}\" \'{}\' -> \'{}\' (it changed)...", availableQuery.first.filename().string(),
282 iter->second.hash.substr(0, 7), hash.substr(0, 7));
287 if (iter->second.state != availableQuery.second)
289 TC_LOG_DEBUG(
"sql.updates",
">> Updating the state of \"{}\" to \'{}\'...",
292 UpdateState(availableQuery.first.filename().string(), availableQuery.second);
295 TC_LOG_DEBUG(
"sql.updates",
">> Update is already applied and matches the hash \'{}\'.", hash.substr(0, 7));
303 AppliedFileEntry const file = { availableQuery.first.filename().string(), hash, availableQuery.second, 0 };
308 speed =
Apply(availableQuery.first);
315 if (iter != applied.end())
323 if (!applied.empty())
325 bool const doCleanup = (cleanDeadReferencesMaxCount < 0) || (applied.size() <=
static_cast<size_t>(cleanDeadReferencesMaxCount));
327 for (
auto const& entry : applied)
329 TC_LOG_WARN(
"sql.updates",
">> The file \'{}\' was applied to the database, but is missing in" \
330 " your update directory now!", entry.first);
333 TC_LOG_INFO(
"sql.updates",
"Deleting orphaned entry \'{}\'...", entry.first);
340 TC_LOG_ERROR(
"sql.updates",
"Cleanup is disabled! There were {} dirty files applied to your database, " \
341 "but they are now missing in your source directory!", applied.size());
345 return UpdateResult(importedUpdates, countRecentUpdates, countArchivedUpdates);
350 using Time = std::chrono::high_resolution_clock;
353 auto const begin = Time::now();
359 return uint32(std::chrono::duration_cast<std::chrono::milliseconds>(Time::now() - begin).count());
364 std::string
const update =
"REPLACE INTO `updates` (`name`, `hash`, `state`, `speed`) VALUES (\"" +
375 std::string
const update =
"DELETE FROM `updates` WHERE `name`=\"" + to +
"\"";
383 std::string
const update =
"UPDATE `updates` SET `name`=\"" + to +
"\" WHERE `name`=\"" + from +
"\"";
395 std::stringstream update;
396 size_t remaining = storage.size();
398 update <<
"DELETE FROM `updates` WHERE `name` IN(";
400 for (
auto const& entry : storage)
402 update <<
"\"" << entry.first <<
"\"";
403 if ((--remaining) > 0)
423 return left.first.filename().string() < right.first.filename().string();
std::shared_ptr< ResultSet > QueryResult
#define TC_LOG_WARN(filterType__,...)
#define TC_LOG_DEBUG(filterType__,...)
#define TC_LOG_TRACE(filterType__,...)
#define TC_LOG_ERROR(filterType__,...)
#define TC_LOG_INFO(filterType__,...)
#define TC_LOG_FATAL(filterType__,...)
std::string ByteArrayToHexStr(Container const &c, bool reverse=false)
Class used to access individual fields of database query result.
std::string GetString() const
static Digest GetDigestOf(uint8 const *data, size_t len)
uint32 Apply(Path const &path) const
std::function< void(std::string const &)> const _apply
std::unordered_map< std::string, AppliedFileEntry > AppliedFileStorage
std::unique_ptr< Path > const _sourceDirectory
LocaleFileStorage GetFileList() const
void UpdateState(std::string const &name, State const state) const
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)
std::set< LocaleFileEntry, PathCompare > LocaleFileStorage
std::string ReadSQLUpdate(Path const &file) const
boost::filesystem::path Path
std::function< void(Path const &path)> const _applyFile
UpdateResult Update(bool const redundancyChecks, bool const allowRehash, bool const archivedRedundancy, int32 const cleanDeadReferencesMaxCount) const
void UpdateEntry(AppliedFileEntry const &entry, uint32 const speed=0) const
void FillFileListRecursively(Path const &path, LocaleFileStorage &storage, State const state, uint32 const depth) const
std::vector< UpdateFetcher::DirectoryEntry > DirectoryStorage
void RenameEntry(std::string const &from, std::string const &to) const
std::unordered_map< std::string, std::string > HashToFileNameStorage
AppliedFileStorage ReceiveAppliedFiles() const
void CleanUp(AppliedFileStorage const &storage) const
std::pair< Path, State > LocaleFileEntry
std::function< QueryResult(std::string const &)> const _retrieve
DirectoryStorage ReceiveIncludedDirectories() const
std::string GetStateAsString() const
static State StateConvert(std::string const &state)
DirectoryEntry(Path const &path_, State state_)
bool operator()(LocaleFileEntry const &left, LocaleFileEntry const &right) const