25#include <boost/filesystem/directory.hpp>
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),
55 for (
auto const& entry : directories)
63 static uint32 const MAX_DEPTH = 10;
64 static directory_iterator
const end;
66 for (directory_iterator itr(path); itr != end; ++itr)
68 if (is_directory(itr->path()))
70 if (depth < MAX_DEPTH)
73 else if (itr->path().extension() ==
".sql")
75 TC_LOG_TRACE(
"sql.updates",
"Added locale file \"{}\".", itr->path().filename().generic_string());
81 if (storage.contains(entry))
83 TC_LOG_FATAL(
"sql.updates",
"Duplicate filename \"{}\" occurred. Because updates are ordered " \
84 "by their filenames, every name needs to be unique!", itr->path().generic_string());
89 storage.insert(entry);
104 Field* fields = result->Fetch();
106 std::string path = fields[0].
GetString();
107 if (path.starts_with(
"$"))
112 if (!is_directory(p))
114 TC_LOG_WARN(
"sql.updates",
"DBUpdater: Given update include directory \"{}\" does not exist, skipped!", p.generic_string());
119 directories.push_back(entry);
121 TC_LOG_TRACE(
"sql.updates",
"Added applied file \"{}\" from remote.", p.filename().generic_string());
123 }
while (result->NextRow());
132 QueryResult result =
_retrieve(
"SELECT `name`, `hash`, `state`, UNIX_TIMESTAMP(`timestamp`) FROM `updates` ORDER BY `name` ASC");
138 Field* fields = result->Fetch();
143 map.insert(std::make_pair(entry.
name, entry));
145 while (result->NextRow());
152 std::ifstream in(file.c_str());
155 TC_LOG_FATAL(
"sql.updates",
"Failed to open the sql update \"{}\" for reading! "
156 "Stopping the server to keep the database integrity, "
157 "try to identify and solve the issue or disable the database updater.",
158 file.generic_string());
163 auto update = [&in] {
164 std::ostringstream ss;
174 bool const allowRehash,
175 bool const archivedRedundancy,
176 int32 const cleanDeadReferencesMaxCount)
const
181 size_t countRecentUpdates = 0;
182 size_t countArchivedUpdates = 0;
185 for (
auto const& [_, appliedFile] : applied)
187 ++countRecentUpdates;
189 ++countArchivedUpdates;
193 for (
auto const& [name, appliedFile] : applied)
194 hashToName.try_emplace(appliedFile.hash, name);
196 size_t importedUpdates = 0;
198 for (
auto const& availableQuery : available)
200 std::string availableQueryFilename = availableQuery.first.filename().string();
202 TC_LOG_DEBUG(
"sql.updates",
"Checking update \"{}\"...", availableQueryFilename);
204 AppliedFileStorage::const_iterator iter = applied.find(availableQueryFilename);
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 = available.find(hashIter->second);
240 if (localeIter != available.end())
242 TC_LOG_WARN(
"sql.updates",
">> It seems like the update \"{}\" \'{}\' was renamed, but the old file is still there! "
243 "Treating it as a new file! (It is probably an unmodified copy of the file \"{}\")",
244 availableQueryFilename, hash.substr(0, 7),
245 localeIter->first.filename().string());
250 TC_LOG_INFO(
"sql.updates",
">> Renaming update \"{}\" to \"{}\" \'{}\'.",
251 hashIter->second, availableQueryFilename, hash.substr(0, 7));
253 RenameEntry(hashIter->second, availableQueryFilename);
254 applied.erase(hashIter->second);
261 TC_LOG_INFO(
"sql.updates",
">> Applying update \"{}\" \'{}\'...",
262 availableQueryFilename, hash.substr(0, 7));
266 else if (allowRehash && iter->second.hash.empty())
270 TC_LOG_INFO(
"sql.updates",
">> Re-hashing update \"{}\" \'{}\'...", availableQueryFilename,
276 if (iter->second.hash != hash)
278 TC_LOG_INFO(
"sql.updates",
">> Reapplying update \"{}\" \'{}\' -> \'{}\' (it changed)...", availableQueryFilename,
279 iter->second.hash.substr(0, 7), hash.substr(0, 7));
284 if (iter->second.state != availableQuery.second)
286 TC_LOG_DEBUG(
"sql.updates",
">> Updating the state of \"{}\" to \'{}\'...",
289 UpdateState(availableQueryFilename, availableQuery.second);
292 TC_LOG_DEBUG(
"sql.updates",
">> Update is already applied and matches the hash \'{}\'.", hash.substr(0, 7));
300 AppliedFileEntry const file = { availableQueryFilename, hash, availableQuery.second, 0 };
305 speed =
Apply(availableQuery.first);
312 if (iter != applied.end())
320 if (!applied.empty())
322 bool const doCleanup = (cleanDeadReferencesMaxCount < 0) || (applied.size() <=
static_cast<size_t>(cleanDeadReferencesMaxCount));
324 for (
auto const& entry : applied)
326 TC_LOG_WARN(
"sql.updates",
">> The file \'{}\' was applied to the database, but is missing in" \
327 " your update directory now!", entry.first);
330 TC_LOG_INFO(
"sql.updates",
"Deleting orphaned entry \'{}\'...", entry.first);
337 TC_LOG_ERROR(
"sql.updates",
"Cleanup is disabled! There were {} dirty files applied to your database, " \
338 "but they are now missing in your source directory!", applied.size());
342 return UpdateResult(importedUpdates, countRecentUpdates, countArchivedUpdates);
347 using Time = std::chrono::high_resolution_clock;
350 auto const begin = Time::now();
356 return uint32(std::chrono::duration_cast<std::chrono::milliseconds>(Time::now() - begin).count());
361 std::string
const update =
Trinity::StringFormat(R
"(REPLACE INTO `updates` (`name`, `hash`, `state`, `speed`) VALUES ("{}", "{}", '{}', {}))",
372 std::string
const update =
"DELETE FROM `updates` WHERE `name`=\"" + to +
"\"";
380 std::string
const update =
"UPDATE `updates` SET `name`=\"" + to +
"\" WHERE `name`=\"" + from +
"\"";
392 std::stringstream update;
393 size_t remaining = storage.size();
395 update <<
"DELETE FROM `updates` WHERE `name` IN(";
397 for (
auto const& entry : storage)
399 update <<
"\"" << entry.first <<
"\"";
400 if ((--remaining) > 0)
420 return arg.first.filename().string();
std::shared_ptr< ResultSet > QueryResult
#define TC_LOG_DEBUG(filterType__, message__,...)
#define TC_LOG_ERROR(filterType__, message__,...)
#define TC_LOG_FATAL(filterType__, message__,...)
#define TC_LOG_INFO(filterType__, message__,...)
#define TC_LOG_WARN(filterType__, message__,...)
#define TC_LOG_TRACE(filterType__, message__,...)
std::string ByteArrayToHexStr(Container const &c, bool reverse=false)
Class used to access individual fields of database query result.
uint64 GetUInt64() const noexcept
std::string GetString() const noexcept
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 StringFormat(FormatString< Args... > fmt, Args &&... args) noexcept
Default TC string format function.
std::string_view GetStateAsString() const
static State StateConvert(std::string_view const &state)
DirectoryEntry(Path const &path_, State state_)
static std::string MakeComparisonObject(LocaleFileEntry const &arg)