diff --git a/src/game/Server/DBCStores.cpp b/src/game/Server/DBCStores.cpp index 9e1f595fb2..f56fc9302a 100644 --- a/src/game/Server/DBCStores.cpp +++ b/src/game/Server/DBCStores.cpp @@ -289,6 +289,18 @@ std::string AcceptableClientBuildsListStr() return data.str(); } +static uint32 sDBCLoadedBuild = 0; ///< Client build of the DBC files loaded at startup + +/** + * @brief Returns the client build of the DBC files loaded at startup. + * + * @return uint32 The loaded DBC build, or 0 before LoadDBCStores has run. + */ +uint32 GetDBCLoadedBuild() +{ + return sDBCLoadedBuild; +} + static bool ReadDBCBuildFileText(const std::string& dbc_path, char const* localeName, std::string& text) { std::string filename = dbc_path + "component.wow-" + localeName + ".txt"; @@ -1019,6 +1031,10 @@ void LoadDBCStores(const std::string& dataPath) exit(1); } + // Remember the validated build for consumers like the character + // database cleaner (see GetDBCLoadedBuild). + sDBCLoadedBuild = build; + sLog.outString(); sLog.outString(">> Initialized %d data stores", DBCFilesCount); } diff --git a/src/game/Server/DBCStores.h b/src/game/Server/DBCStores.h index 8ca214e813..d0e481e806 100644 --- a/src/game/Server/DBCStores.h +++ b/src/game/Server/DBCStores.h @@ -41,6 +41,12 @@ bool IsAcceptableClientBuild(uint32 build); */ std::string AcceptableClientBuildsListStr(); +/** + * Returns the client build of the DBC files loaded at startup, + * or 0 before LoadDBCStores has run. + */ +uint32 GetDBCLoadedBuild(); + /** * This function checks to see if there is a valid locale file (component.wow-.txt) * and returns an index to the locale or -1 if not found diff --git a/src/game/Tools/CharacterDatabaseCleaner.cpp b/src/game/Tools/CharacterDatabaseCleaner.cpp index aa4c23d42f..d7a1cf50c7 100644 --- a/src/game/Tools/CharacterDatabaseCleaner.cpp +++ b/src/game/Tools/CharacterDatabaseCleaner.cpp @@ -36,7 +36,8 @@ * * The cleaner uses flags stored in the `saved_variables` table to determine * which cleanup operations are needed. This allows incremental cleaning - * across server restarts. + * across server restarts. A change of the loaded DBC client build against + * `saved_variables`.`last_cleaned_dbc_build` schedules a full cleanup. * * @see CharacterDatabaseCleaner for the cleaner interface * @see CLEANING_FLAG_* constants for available operations @@ -55,8 +56,10 @@ * Performs database cleaning based on configuration and stored flags: * 1. Checks if cleaning is enabled in config * 2. Reads cleaning flags from saved_variables table - * 3. Executes appropriate cleanup routines - * 4. Resets cleaning flags when complete + * 3. Schedules a full cleanup when the loaded DBC build differs from + * saved_variables.last_cleaned_dbc_build (DBC data was swapped) + * 4. Executes appropriate cleanup routines + * 5. Resets cleaning flags and records the DBC build when complete * * @note Can be disabled via CONFIG_BOOL_CLEAN_CHARACTER_DB */ @@ -68,8 +71,6 @@ void CharacterDatabaseCleaner::CleanDatabase() return; } - sLog.outString("Cleaning character database..."); - // Check which cleanups are needed QueryResult* result = CharacterDatabase.PQuery("SELECT `cleaning_flags` FROM `saved_variables`"); if (!result) @@ -79,6 +80,35 @@ void CharacterDatabaseCleaner::CleanDatabase() uint32 flags = (*result)[0].GetUInt32(); delete result; + // Orphaned rows appear when the extracted DBC data is swapped for + // another client build, so a build change schedules a full cleanup. + bool hasBuildColumn = false; + uint32 const dbcBuild = GetDBCLoadedBuild(); + result = CharacterDatabase.PQuery("SELECT `last_cleaned_dbc_build` FROM `saved_variables`"); + if (result) + { + hasBuildColumn = true; + uint32 lastCleanedBuild = (*result)[0].GetUInt32(); + delete result; + + if (dbcBuild && lastCleanedBuild != dbcBuild) + { + sLog.outString("DBC build changed (%u -> %u), scheduling character database cleanup.", lastCleanedBuild, dbcBuild); + flags |= CLEANING_FLAG_ALL; + } + } + else + { + sLog.outErrorDb("saved_variables.last_cleaned_dbc_build is missing, DBC build-change cleanup disabled. Please apply the character database updates."); + } + + if (!flags) + { + return; + } + + sLog.outString("Cleaning character database..."); + // clean up if (flags & CLEANING_FLAG_ACHIEVEMENT_PROGRESS) { @@ -98,7 +128,17 @@ void CharacterDatabaseCleaner::CleanDatabase() { CleanCharacterTalent(); } - CharacterDatabase.Execute("UPDATE `saved_variables` SET `cleaning_flags` = 0"); + + // Only reset the flags and record the build after a completed run so + // an interrupted cleanup is retried on the next startup. + if (hasBuildColumn) + { + CharacterDatabase.PExecute("UPDATE `saved_variables` SET `cleaning_flags` = 0, `last_cleaned_dbc_build` = %u", dbcBuild); + } + else + { + CharacterDatabase.Execute("UPDATE `saved_variables` SET `cleaning_flags` = 0"); + } } /** diff --git a/src/game/Tools/CharacterDatabaseCleaner.h b/src/game/Tools/CharacterDatabaseCleaner.h index e24430d62c..b337c784c6 100644 --- a/src/game/Tools/CharacterDatabaseCleaner.h +++ b/src/game/Tools/CharacterDatabaseCleaner.h @@ -35,7 +35,8 @@ namespace CharacterDatabaseCleaner CLEANING_FLAG_ACHIEVEMENT_PROGRESS = 0x1, ///< Clean Achievements CLEANING_FLAG_SKILLS = 0x2, ///< Clean skills CLEANING_FLAG_SPELLS = 0x4, ///< Clean spells - CLEANING_FLAG_TALENTS = 0x8 ///< Clean Talents + CLEANING_FLAG_TALENTS = 0x8, ///< Clean Talents + CLEANING_FLAG_ALL = 0xF ///< All cleanup categories }; /** diff --git a/src/mangosd/mangosd.conf.dist.in b/src/mangosd/mangosd.conf.dist.in index decc072ded..aba0369d46 100644 --- a/src/mangosd/mangosd.conf.dist.in +++ b/src/mangosd/mangosd.conf.dist.in @@ -208,6 +208,8 @@ BindIP = "0.0.0.0" # # CleanCharacterDB # Perform character db cleanups on start up +# Cleanups run when the loaded DBC client build changes or when +# cleaning_flags is set manually in the saved_variables table # Default: 1 (Enable) # 0 (Disabled) # diff --git a/src/shared/revision_data.h.in b/src/shared/revision_data.h.in index c166a916fe..0a917f1a4a 100644 --- a/src/shared/revision_data.h.in +++ b/src/shared/revision_data.h.in @@ -50,8 +50,8 @@ #define CHAR_DB_VERSION_NR "22" #define CHAR_DB_STRUCTURE_NR "3" - #define CHAR_DB_CONTENT_NR "2" - #define CHAR_DB_UPDATE_DESCRIPT "Add_Quest_Tracker_Table" + #define CHAR_DB_CONTENT_NR "3" + #define CHAR_DB_UPDATE_DESCRIPT "Add_Last_Cleaned_DBC_Build" #define WORLD_DB_VERSION_NR "22" #define WORLD_DB_STRUCTURE_NR "5"