diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 922e49a8..6cf382fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,3 +66,30 @@ If you're adding a new translation, LOOT's source code must be updated to recogn * In [archive.js](scripts/archive.js), add the language folder to the list on line 83. * In [installer.iss](scripts/installer.iss), add an entry for your language's translation file to the `[Files]` section. * In [LOOT Metadata Syntax.html](docs/LOOT%20Metadata%20Syntax.html), add a row for your language to the Language Codes table. + +## Code Style + +LOOT's JavaScript uses a slightly tweaked version of the Airbnb style, and can be automatically linted by ESLint, so isn't covered here. + +### C++ Code Style + +The [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html) is used as the base, with deviations as listed below. Note that the LOOT API is a C API, so the style guide doesn't apply to its header. + +#### C++ Features + +* Static variables may contain non-POD types. +* Reference arguments don't need to be `const` (ie. they can be used for output variables). +* Exceptions can be used. +* Unsigned integer types can be used. +* There's no restriction on which Boost libraries can be used. +* Specialising `std::hash` is allowed. + +#### Naming + +* Constant, enumerator and variable names should use `camelCase` or `underscore_separators`, but they should be consistent within the same scope. +* Function names should use `PascalCase` or `camelCase`, but they should be consistent within the same scope. + +#### Formatting + +* Line length doesn't matter. +* `public`, `protected` and `private` keywords should not be indented within a class declaration. diff --git a/include/loot/api.h b/include/loot/api.h index e1891811..bcf9de84 100644 --- a/include/loot/api.h +++ b/include/loot/api.h @@ -150,8 +150,8 @@ * calling loot_cleanup(). */ -#ifndef __LOOT_API_H__ -#define __LOOT_API_H__ +#ifndef LOOT_LOOT_API +#define LOOT_LOOT_API #include @@ -202,501 +202,501 @@ extern "C" * made for each game, though it should be kept in mind that the * API is not thread-safe. */ - typedef struct loot_db loot_db; + typedef struct loot_db loot_db; - /** - * @brief A structure that holds the type of a message and the message - * string itself. - */ - typedef struct { - /** - * @var type - * The type of the message, specified using one of the message - * type codes. - * @var message - * The message string, which may be formatted using - * [GitHub Flavored Markdown] - * (https://help.github.com/articles/github-flavored-markdown). - */ - unsigned int type; - const char * message; - } loot_message; + /** + * @brief A structure that holds the type of a message and the message + * string itself. + */ + typedef struct { + /** + * @var type + * The type of the message, specified using one of the message + * type codes. + * @var message + * The message string, which may be formatted using + * [GitHub Flavored Markdown] + * (https://help.github.com/articles/github-flavored-markdown). + */ + unsigned int type; + const char * message; + } loot_message; - /**********************************************************************//** - * @name Return Codes - * @brief Error codes signify an issue that caused a function to exit - * prematurely. If a function exits prematurely, a reversal of any - * changes made during its execution is attempted before it exits. - *************************************************************************/ - /**@{*/ + /**********************************************************************//** + * @name Return Codes + * @brief Error codes signify an issue that caused a function to exit + * prematurely. If a function exits prematurely, a reversal of any + * changes made during its execution is attempted before it exits. + *************************************************************************/ + /**@{*/ - LOOT_API extern const unsigned int loot_ok; /**< The function completed successfully. */ - LOOT_API extern const unsigned int loot_error_liblo_error; /**< There was an error in performing a load order operation. */ - LOOT_API extern const unsigned int loot_error_file_write_fail; /**< A file could not be written to. */ - LOOT_API extern const unsigned int loot_error_parse_fail; /**< There was an error parsing the file. */ - LOOT_API extern const unsigned int loot_error_condition_eval_fail; /**< There was an error evaluating the conditionals in a metadata file. */ - LOOT_API extern const unsigned int loot_error_regex_eval_fail; /**< There was an error evaluating the regular expressions in a metadata file. */ - LOOT_API extern const unsigned int loot_error_no_mem; /**< The API was unable to allocate the required memory. */ - LOOT_API extern const unsigned int loot_error_invalid_args; /**< Invalid arguments were given for the function. */ - LOOT_API extern const unsigned int loot_error_no_tag_map; /**< No Bash Tag map has been generated yet. */ - LOOT_API extern const unsigned int loot_error_path_not_found; /**< A file or folder path could not be found. */ - LOOT_API extern const unsigned int loot_error_no_game_detected; /**< The given game could not be found. */ - LOOT_API extern const unsigned int loot_error_git_error; /**< An error occurred while performing a git operation (updating or getting the masterlist version). */ - LOOT_API extern const unsigned int loot_error_windows_error; /**< An error occurred during a call to the Windows API. */ - LOOT_API extern const unsigned int loot_error_sorting_error; /**< An error occurred while sorting plugins. */ + LOOT_API extern const unsigned int loot_ok; /**< The function completed successfully. */ + LOOT_API extern const unsigned int loot_error_liblo_error; /**< There was an error in performing a load order operation. */ + LOOT_API extern const unsigned int loot_error_file_write_fail; /**< A file could not be written to. */ + LOOT_API extern const unsigned int loot_error_parse_fail; /**< There was an error parsing the file. */ + LOOT_API extern const unsigned int loot_error_condition_eval_fail; /**< There was an error evaluating the conditionals in a metadata file. */ + LOOT_API extern const unsigned int loot_error_regex_eval_fail; /**< There was an error evaluating the regular expressions in a metadata file. */ + LOOT_API extern const unsigned int loot_error_no_mem; /**< The API was unable to allocate the required memory. */ + LOOT_API extern const unsigned int loot_error_invalid_args; /**< Invalid arguments were given for the function. */ + LOOT_API extern const unsigned int loot_error_no_tag_map; /**< No Bash Tag map has been generated yet. */ + LOOT_API extern const unsigned int loot_error_path_not_found; /**< A file or folder path could not be found. */ + LOOT_API extern const unsigned int loot_error_no_game_detected; /**< The given game could not be found. */ + LOOT_API extern const unsigned int loot_error_git_error; /**< An error occurred while performing a git operation (updating or getting the masterlist version). */ + LOOT_API extern const unsigned int loot_error_windows_error; /**< An error occurred during a call to the Windows API. */ + LOOT_API extern const unsigned int loot_error_sorting_error; /**< An error occurred while sorting plugins. */ - /** - * @brief Matches the value of the highest-numbered return code. - * @details Provided in case clients wish to incorporate additional return - * codes in their implementation and desire some method of - * avoiding value conflicts. - */ - LOOT_API extern const unsigned int loot_return_max; + /** + * @brief Matches the value of the highest-numbered return code. + * @details Provided in case clients wish to incorporate additional return + * codes in their implementation and desire some method of + * avoiding value conflicts. + */ + LOOT_API extern const unsigned int loot_return_max; - /**@}*/ - /**********************************************************************//** - * @name Game Codes - * @brief Used with loot_create_db(). - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Game Codes + * @brief Used with loot_create_db(). + *************************************************************************/ + /**@{*/ - LOOT_API extern const unsigned int loot_game_tes4; /**< Game code for The Elder Scrolls IV: Oblivion. */ - LOOT_API extern const unsigned int loot_game_tes5; /**< Game code for The Elder Scrolls V: Skyrim. */ - LOOT_API extern const unsigned int loot_game_fo3; /**< Game code for Fallout 3. */ - LOOT_API extern const unsigned int loot_game_fonv; /**< Game code for Fallout: New Vegas. */ - LOOT_API extern const unsigned int loot_game_fo4; /**< Game code for Fallout: New Vegas. */ + LOOT_API extern const unsigned int loot_game_tes4; /**< Game code for The Elder Scrolls IV: Oblivion. */ + LOOT_API extern const unsigned int loot_game_tes5; /**< Game code for The Elder Scrolls V: Skyrim. */ + LOOT_API extern const unsigned int loot_game_fo3; /**< Game code for Fallout 3. */ + LOOT_API extern const unsigned int loot_game_fonv; /**< Game code for Fallout: New Vegas. */ + LOOT_API extern const unsigned int loot_game_fo4; /**< Game code for Fallout: New Vegas. */ - /**@}*/ - /**********************************************************************//** - * @name Message Type Codes - * @brief Used with the loot_message structure. - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Message Type Codes + * @brief Used with the loot_message structure. + *************************************************************************/ + /**@{*/ - LOOT_API extern const unsigned int loot_message_say; /**< Denotes a generic note-type message. */ - LOOT_API extern const unsigned int loot_message_warn; /**< Denotes a warning message. */ - LOOT_API extern const unsigned int loot_message_error; /**< Denotes an error message. */ + LOOT_API extern const unsigned int loot_message_say; /**< Denotes a generic note-type message. */ + LOOT_API extern const unsigned int loot_message_warn; /**< Denotes a warning message. */ + LOOT_API extern const unsigned int loot_message_error; /**< Denotes an error message. */ - /**@}*/ - /**********************************************************************//** - * @name Message Language Codes - * @brief Used with loot_eval_lists(). - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Message Language Codes + * @brief Used with loot_eval_lists(). + *************************************************************************/ + /**@{*/ - LOOT_API extern const unsigned int loot_lang_english; /**< Tells the API to preferentially select English messages. */ - LOOT_API extern const unsigned int loot_lang_spanish; /**< Tells the API to preferentially select Spanish messages */ - LOOT_API extern const unsigned int loot_lang_russian; /**< Tells the API to preferentially select Russian messages. */ - LOOT_API extern const unsigned int loot_lang_french; /**< Tells the API to preferentially select French messages. */ - LOOT_API extern const unsigned int loot_lang_chinese; /**< Tells the API to preferentially select Chinese messages. */ - LOOT_API extern const unsigned int loot_lang_polish; /**< Tells the API to preferentially select Polish messages. */ - LOOT_API extern const unsigned int loot_lang_brazilian_portuguese; /**< Tells the API to preferentially select Brazilian Portuguese messages. */ - LOOT_API extern const unsigned int loot_lang_finnish; /**< Tells the API to preferentially select Finnish messages. */ - LOOT_API extern const unsigned int loot_lang_german; /**< Tells the API to preferentially select German messages. */ - LOOT_API extern const unsigned int loot_lang_danish; /**< Tells the API to preferentially select Danish messages. */ - LOOT_API extern const unsigned int loot_lang_korean; /**< Tells the API to preferentially select Korean messages. */ + LOOT_API extern const unsigned int loot_lang_english; /**< Tells the API to preferentially select English messages. */ + LOOT_API extern const unsigned int loot_lang_spanish; /**< Tells the API to preferentially select Spanish messages */ + LOOT_API extern const unsigned int loot_lang_russian; /**< Tells the API to preferentially select Russian messages. */ + LOOT_API extern const unsigned int loot_lang_french; /**< Tells the API to preferentially select French messages. */ + LOOT_API extern const unsigned int loot_lang_chinese; /**< Tells the API to preferentially select Chinese messages. */ + LOOT_API extern const unsigned int loot_lang_polish; /**< Tells the API to preferentially select Polish messages. */ + LOOT_API extern const unsigned int loot_lang_brazilian_portuguese; /**< Tells the API to preferentially select Brazilian Portuguese messages. */ + LOOT_API extern const unsigned int loot_lang_finnish; /**< Tells the API to preferentially select Finnish messages. */ + LOOT_API extern const unsigned int loot_lang_german; /**< Tells the API to preferentially select German messages. */ + LOOT_API extern const unsigned int loot_lang_danish; /**< Tells the API to preferentially select Danish messages. */ + LOOT_API extern const unsigned int loot_lang_korean; /**< Tells the API to preferentially select Korean messages. */ - /**@}*/ - /**********************************************************************//** - * @name Plugin Cleanliness Codes - * @brief Used with loot_get_dirty_info(). - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Plugin Cleanliness Codes + * @brief Used with loot_get_dirty_info(). + *************************************************************************/ + /**@{*/ - LOOT_API extern const unsigned int loot_needs_cleaning_no; /**< Denotes that the plugin queried does not need cleaning. */ - LOOT_API extern const unsigned int loot_needs_cleaning_yes; /**< Denotes that the plugin queried needs cleaning. */ - LOOT_API extern const unsigned int loot_needs_cleaning_unknown; /**< Denotes that the API is unable to determine whether or not the plugin queried needs cleaning. */ + LOOT_API extern const unsigned int loot_needs_cleaning_no; /**< Denotes that the plugin queried does not need cleaning. */ + LOOT_API extern const unsigned int loot_needs_cleaning_yes; /**< Denotes that the plugin queried needs cleaning. */ + LOOT_API extern const unsigned int loot_needs_cleaning_unknown; /**< Denotes that the API is unable to determine whether or not the plugin queried needs cleaning. */ - /**@}*/ - /**********************************************************************//** - * @name Error Handling Functions - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Error Handling Functions + *************************************************************************/ + /**@{*/ - /** - * @brief Returns the message for the last error or warning encountered. - * @details Outputs a string giving the a message containing the details - * of the last error or warning encountered by a function. Each - * time this function is called, the memory for the previous - * message is freed, so only one error message is available at - * any one time. - * @param message - * A pointer to the error details string outputted by the function. - * @returns A return code. - */ - LOOT_API unsigned int loot_get_error_message(const char ** const message); + /** + * @brief Returns the message for the last error or warning encountered. + * @details Outputs a string giving the a message containing the details + * of the last error or warning encountered by a function. Each + * time this function is called, the memory for the previous + * message is freed, so only one error message is available at + * any one time. + * @param message + * A pointer to the error details string outputted by the function. + * @returns A return code. + */ + LOOT_API unsigned int loot_get_error_message(const char ** const message); - /**@}*/ - /**********************************************************************//** - * @name Version Functions - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Version Functions + *************************************************************************/ + /**@{*/ - /** - * @brief Checks for API compatibility. - * @details Checks whether the loaded API is compatible with the given - * version of the API, abstracting API stability policy away from - * clients. The version numbering used is major.minor.patch. - * @param versionMajor - * The major version number to check. - * @param versionMinor - * The minor version number to check. - * @param versionPatch - * The patch version number to check. - * @returns True if the API versions are compatible, false otherwise. - */ - LOOT_API bool loot_is_compatible(const unsigned int versionMajor, - const unsigned int versionMinor, - const unsigned int versionPatch); + /** + * @brief Checks for API compatibility. + * @details Checks whether the loaded API is compatible with the given + * version of the API, abstracting API stability policy away from + * clients. The version numbering used is major.minor.patch. + * @param versionMajor + * The major version number to check. + * @param versionMinor + * The minor version number to check. + * @param versionPatch + * The patch version number to check. + * @returns True if the API versions are compatible, false otherwise. + */ + LOOT_API bool loot_is_compatible(const unsigned int versionMajor, + const unsigned int versionMinor, + const unsigned int versionPatch); - /** - * @brief Gets the API version. - * @details Outputs the major, minor and patch version numbers for the - * loaded API. The version numbering used is major.minor.patch. - * @param versionMajor - * A pointer to the major version number. - * @param versionMinor - * A pointer to the minor version number. - * @param versionPatch - * A pointer to the patch version number. - * @returns A return code. - */ - LOOT_API unsigned int loot_get_version(unsigned int * const versionMajor, - unsigned int * const versionMinor, - unsigned int * const versionPatch); + /** + * @brief Gets the API version. + * @details Outputs the major, minor and patch version numbers for the + * loaded API. The version numbering used is major.minor.patch. + * @param versionMajor + * A pointer to the major version number. + * @param versionMinor + * A pointer to the minor version number. + * @param versionPatch + * A pointer to the patch version number. + * @returns A return code. + */ + LOOT_API unsigned int loot_get_version(unsigned int * const versionMajor, + unsigned int * const versionMinor, + unsigned int * const versionPatch); - /** - * @brief Get the Git revision of the code from which the binary was - * built. - * @param revision - * A pointer to the shortened Git revision ID string. - * @returns A return code. - */ - LOOT_API unsigned int loot_get_build_id(const char ** const revision); + /** + * @brief Get the Git revision of the code from which the binary was + * built. + * @param revision + * A pointer to the shortened Git revision ID string. + * @returns A return code. + */ + LOOT_API unsigned int loot_get_build_id(const char ** const revision); - /**@}*/ - /**********************************************************************//** - * @name Lifecycle Management Functions - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Lifecycle Management Functions + *************************************************************************/ + /**@{*/ - /** - * @brief Initialise a new database handle. - * @details Creates a handle for a database, which is then used by all - * database functions. - * @param db - * A pointer to the handle that is created by the function. - * @param clientGame - * A game code for which to create the handle. - * @param gamePath - * The relative or absolute path to the game folder, or `NULL`. - * If `NULL`, the API will attempt to detect the data path of the - * specified game. - * @param gameLocalPath - * The relative or absolute path to the game's folder in - * `%LOCALAPPDATA%`, or `NULL`. If `NULL`, the API will attempt to - * look up the path that `%LOCALAPPDATA%` corresponds to. This - * parameter is provided so that systems lacking that environmental - * variable (eg. Linux) can still use the API. - * @returns A return code. - */ - LOOT_API unsigned int loot_create_db(loot_db ** const db, - const unsigned int clientGame, - const char * const gamePath, - const char * const gameLocalPath); + /** + * @brief Initialise a new database handle. + * @details Creates a handle for a database, which is then used by all + * database functions. + * @param db + * A pointer to the handle that is created by the function. + * @param clientGame + * A game code for which to create the handle. + * @param gamePath + * The relative or absolute path to the game folder, or `NULL`. + * If `NULL`, the API will attempt to detect the data path of the + * specified game. + * @param gameLocalPath + * The relative or absolute path to the game's folder in + * `%LOCALAPPDATA%`, or `NULL`. If `NULL`, the API will attempt to + * look up the path that `%LOCALAPPDATA%` corresponds to. This + * parameter is provided so that systems lacking that environmental + * variable (eg. Linux) can still use the API. + * @returns A return code. + */ + LOOT_API unsigned int loot_create_db(loot_db ** const db, + const unsigned int clientGame, + const char * const gamePath, + const char * const gameLocalPath); - /** - * @brief Destroy an existing database handle. - * @details Destroys the given database handle, freeing up memory - * allocated during its use, excluding any memory allocated to - * error messages. - * @param db - * The database handle to destroy. - */ - LOOT_API void loot_destroy_db(loot_db * const db); + /** + * @brief Destroy an existing database handle. + * @details Destroys the given database handle, freeing up memory + * allocated during its use, excluding any memory allocated to + * error messages. + * @param db + * The database handle to destroy. + */ + LOOT_API void loot_destroy_db(loot_db * const db); - /**@}*/ - /**********************************************************************//** - * @name Database Loading Functions - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Database Loading Functions + *************************************************************************/ + /**@{*/ - /** - * @brief Loads the masterlist and userlist from the paths specified. - * @details Can be called multiple times, each time replacing the - * previously-loaded data. - * @param db - * The database the function acts on. - * @param masterlistPath - * A string containing the relative or absolute path to the masterlist - * file that should be loaded. - * @param userlistPath - * A string containing the relative or absolute path to the userlist - * file that should be loaded, or `NULL`. If `NULL`, no userlist will - * be loaded. - * @returns A return code. - */ - LOOT_API unsigned int loot_load_lists(loot_db * const db, - const char * const masterlistPath, - const char * const userlistPath); + /** + * @brief Loads the masterlist and userlist from the paths specified. + * @details Can be called multiple times, each time replacing the + * previously-loaded data. + * @param db + * The database the function acts on. + * @param masterlistPath + * A string containing the relative or absolute path to the masterlist + * file that should be loaded. + * @param userlistPath + * A string containing the relative or absolute path to the userlist + * file that should be loaded, or `NULL`. If `NULL`, no userlist will + * be loaded. + * @returns A return code. + */ + LOOT_API unsigned int loot_load_lists(loot_db * const db, + const char * const masterlistPath, + const char * const userlistPath); - /** - * @brief Evaluates all conditions and regular expression metadata - * entries. - * @details Repeated calls re-evaluate the metadata from scratch. This - * function affects the output of all the database access - * functions. - * @param db - * The database the function acts on. - * @param language - * The language code that is used for message language comparisons. - * @returns A return code. - */ - LOOT_API unsigned int loot_eval_lists(loot_db * const db, - const unsigned int language); + /** + * @brief Evaluates all conditions and regular expression metadata + * entries. + * @details Repeated calls re-evaluate the metadata from scratch. This + * function affects the output of all the database access + * functions. + * @param db + * The database the function acts on. + * @param language + * The language code that is used for message language comparisons. + * @returns A return code. + */ + LOOT_API unsigned int loot_eval_lists(loot_db * const db, + const unsigned int language); - /**********************************************************************//** - * @name LOOT Functionality Functions - *************************************************************************/ - /**@{*/ + /**********************************************************************//** + * @name LOOT Functionality Functions + *************************************************************************/ + /**@{*/ - /** - * @brief Calculates a new load order for the game's installed plugins - * (including inactive plugins) and outputs the sorted order. - * @details Pulls metadata from the masterlist and userlist if they are - * loaded, and reads the contents of each plugin. No changes are - * applied to the load order used by the game. This function does - * not load or evaluate the masterlist or userlist. - * @param db - * The database the function acts on. - * @param sortedPlugins - * A pointer to an array of plugin filenames in their sorted load - * order. - * @param numPlugins - * A pointer to the size of the outputted array. - * @returns A return code. - */ - LOOT_API unsigned int loot_sort_plugins(loot_db * const db, - const char * const ** const sortedPlugins, - size_t * const numPlugins); + /** + * @brief Calculates a new load order for the game's installed plugins + * (including inactive plugins) and outputs the sorted order. + * @details Pulls metadata from the masterlist and userlist if they are + * loaded, and reads the contents of each plugin. No changes are + * applied to the load order used by the game. This function does + * not load or evaluate the masterlist or userlist. + * @param db + * The database the function acts on. + * @param sortedPlugins + * A pointer to an array of plugin filenames in their sorted load + * order. + * @param numPlugins + * A pointer to the size of the outputted array. + * @returns A return code. + */ + LOOT_API unsigned int loot_sort_plugins(loot_db * const db, + const char * const ** const sortedPlugins, + size_t * const numPlugins); - /** - * @brief Applies the given load order. - * @param db - * The database the function acts on. - * @param loadOrder - * An array of plugin filenames in the load order to be set. - * @param numPlugins - * The size of the inputted array. - * @returns A return code. - */ - LOOT_API unsigned int loot_apply_load_order(loot_db * const db, - const char * const * const loadOrder, - const size_t numPlugins); + /** + * @brief Applies the given load order. + * @param db + * The database the function acts on. + * @param loadOrder + * An array of plugin filenames in the load order to be set. + * @param numPlugins + * The size of the inputted array. + * @returns A return code. + */ + LOOT_API unsigned int loot_apply_load_order(loot_db * const db, + const char * const * const loadOrder, + const size_t numPlugins); - /** - * @brief Update the given masterlist. - * @details Uses Git to update the given masterlist to a given remote. - * If the masterlist doesn't exist, this will create it. This - * function also initialises a Git repository in the given - * masterlist's parent folder. If the masterlist was not already - * up-to-date, it will be re-loaded, but not re-evaluated. - * - * If a Git repository is already present, it will be used to - * perform a diff-only update, but if for any reason a - * fast-forward merge update is not possible, the existing - * repository will be deleted and a new repository cloned from - * the given remote. - * @param db - * The database the function acts on. - * @param masterlistPath - * A string containing the relative or absolute path to the masterlist - * file that should be updated. The filename must match the filename - * of the masterlist file in the given remote repository, otherwise it - * will not be updated correctly. Although LOOT itself expects this - * filename to be "masterlist.yaml", the API does not check for any - * specific filename. - * @param remoteURL - * The URL of the remote from which to fetch updates. This can also be - * a relative or absolute path to a local repository. - * @param remoteBranch - * The branch of the remote from which to apply updates. LOOT's - * official masterlists are versioned using separate branches for each - * new version of the masterlist syntax, so if you're using them, - * check their repositories to see which is the latest release branch. - * @param updated - * `true` if the masterlist was updated. `false` if no update was - * necessary, ie. it was already up-to-date. If `true`, the masterlist - * will have been re-loaded, but will need to be re-evaluated - * separately. - * @returns A return code. - */ - LOOT_API unsigned int loot_update_masterlist(loot_db * const db, - const char * const masterlistPath, - const char * const remoteURL, - const char * const remoteBranch, - bool * const updated); + /** + * @brief Update the given masterlist. + * @details Uses Git to update the given masterlist to a given remote. + * If the masterlist doesn't exist, this will create it. This + * function also initialises a Git repository in the given + * masterlist's parent folder. If the masterlist was not already + * up-to-date, it will be re-loaded, but not re-evaluated. + * + * If a Git repository is already present, it will be used to + * perform a diff-only update, but if for any reason a + * fast-forward merge update is not possible, the existing + * repository will be deleted and a new repository cloned from + * the given remote. + * @param db + * The database the function acts on. + * @param masterlistPath + * A string containing the relative or absolute path to the masterlist + * file that should be updated. The filename must match the filename + * of the masterlist file in the given remote repository, otherwise it + * will not be updated correctly. Although LOOT itself expects this + * filename to be "masterlist.yaml", the API does not check for any + * specific filename. + * @param remoteURL + * The URL of the remote from which to fetch updates. This can also be + * a relative or absolute path to a local repository. + * @param remoteBranch + * The branch of the remote from which to apply updates. LOOT's + * official masterlists are versioned using separate branches for each + * new version of the masterlist syntax, so if you're using them, + * check their repositories to see which is the latest release branch. + * @param updated + * `true` if the masterlist was updated. `false` if no update was + * necessary, ie. it was already up-to-date. If `true`, the masterlist + * will have been re-loaded, but will need to be re-evaluated + * separately. + * @returns A return code. + */ + LOOT_API unsigned int loot_update_masterlist(loot_db * const db, + const char * const masterlistPath, + const char * const remoteURL, + const char * const remoteBranch, + bool * const updated); - /** - * @brief Get the given masterlist's revision. - * @details Getting a masterlist's revision is only possible if it is - * found inside a local Git repository. - * @param db - * The database the function acts on. - * @param masterlistPath - * A string containing the relative or absolute path to the masterlist - * file that should be queried. - * @param getShortID - * If `true`, the shortest unique hexadecimal revision hash that is at - * least 7 characters long will be outputted. Otherwise, the full 40 - * character hash will be outputted. - * @param revisionID - * A pointer to a string containing the outputted revision hash for - * the masterlist. If the masterlist doesn't exist, or there is no Git - * repository at its location, this will be `NULL`. - * @param revisionDate - * A pointer to a string containing the ISO 8601 formatted revision - * date, ie. YYYY-MM-DD. If the masterlist doesn't exist, or there is - * no Git repository at its location, this will be `NULL`. - * @param isModified - * A pointer to a boolean that is `true` if the masterlist has been - * edited since the outputted revision, or `false` if it is at exactly - * the revision given. - * @returns A return code. - */ - LOOT_API unsigned int loot_get_masterlist_revision(loot_db * const db, - const char * const masterlistPath, - const bool getShortID, - const char ** const revisionID, - const char ** const revisionDate, - bool * const isModified); + /** + * @brief Get the given masterlist's revision. + * @details Getting a masterlist's revision is only possible if it is + * found inside a local Git repository. + * @param db + * The database the function acts on. + * @param masterlistPath + * A string containing the relative or absolute path to the masterlist + * file that should be queried. + * @param getShortID + * If `true`, the shortest unique hexadecimal revision hash that is at + * least 7 characters long will be outputted. Otherwise, the full 40 + * character hash will be outputted. + * @param revisionID + * A pointer to a string containing the outputted revision hash for + * the masterlist. If the masterlist doesn't exist, or there is no Git + * repository at its location, this will be `NULL`. + * @param revisionDate + * A pointer to a string containing the ISO 8601 formatted revision + * date, ie. YYYY-MM-DD. If the masterlist doesn't exist, or there is + * no Git repository at its location, this will be `NULL`. + * @param isModified + * A pointer to a boolean that is `true` if the masterlist has been + * edited since the outputted revision, or `false` if it is at exactly + * the revision given. + * @returns A return code. + */ + LOOT_API unsigned int loot_get_masterlist_revision(loot_db * const db, + const char * const masterlistPath, + const bool getShortID, + const char ** const revisionID, + const char ** const revisionDate, + bool * const isModified); - /**@}*/ - /**********************************************************************//** - * @name Database Access Functions - *************************************************************************/ - /**@{*/ + /**@}*/ + /**********************************************************************//** + * @name Database Access Functions + *************************************************************************/ + /**@{*/ - /** - * @brief Outputs an array of the Bash Tags that are suggested in the - * database. - * @details This function must be called prior to calling - * loot_get_plugin_tags() to ensure that the latter can return - * the Tags using the correct array indicies. - * @param db - * The database the function acts on. - * @param tagMap - * A pointer to the outputted array of Bash Tags. The array functions - * as a map where the indicies are the keys, allowing the API to use - * them as UIDs for Bash Tags instead of passing their names as - * strings every time loot_get_plugin_tags() is called. If no Bash - * Tags are suggested, this will be `NULL`. - * @param numTags - * A pointer to the size of the outputted array. If no Bash Tags are - * suggested, this will be `0`. - * @returns A return code. - */ - LOOT_API unsigned int loot_get_tag_map(loot_db * const db, - const char * const ** const tagMap, - size_t * const numTags); + /** + * @brief Outputs an array of the Bash Tags that are suggested in the + * database. + * @details This function must be called prior to calling + * loot_get_plugin_tags() to ensure that the latter can return + * the Tags using the correct array indicies. + * @param db + * The database the function acts on. + * @param tagMap + * A pointer to the outputted array of Bash Tags. The array functions + * as a map where the indicies are the keys, allowing the API to use + * them as UIDs for Bash Tags instead of passing their names as + * strings every time loot_get_plugin_tags() is called. If no Bash + * Tags are suggested, this will be `NULL`. + * @param numTags + * A pointer to the size of the outputted array. If no Bash Tags are + * suggested, this will be `0`. + * @returns A return code. + */ + LOOT_API unsigned int loot_get_tag_map(loot_db * const db, + const char * const ** const tagMap, + size_t * const numTags); - /** - * @brief Outputs the Bash Tags suggested for addition and removal by the - * database for the given plugin. - * @details loot_get_tag_map() must be called before this to ensure that - * the Bash Tag UIDs outputted by this function can be matched up - * to name strings. - * @param db - * The database the function acts on. - * @param plugin - * The filename of the plugin to look up Bash Tag suggestions for. - * @param tags_added - * A pointer to the outputted array of UIDs of the Bash Tags suggested - * for addition to the specified plugin. `NULL` if no Bash Tag - * additions are suggested. - * @param numTags_added - * A pointer to the size of the tags_added array. `0` if `tags_added` - * is `NULL`. - * @param tags_removed - * A pointer to the outputted array of UIDs of the Bash Tags suggested - * for removal from the specified plugin. `NULL` if no Bash Tag - * removals are suggested. - * @param numTags_removed - * A pointer to the size of the `tags_removed` array. `0` if - * `tags_removed` is `null`. - * @param userlistModified - * `true` if the Bash Tag suggestions were modified by the data in the - * userlist, `false` otherwise. - * @returns A return code. - */ - LOOT_API unsigned int loot_get_plugin_tags(loot_db * const db, - const char * const plugin, - const unsigned int ** const tags_added, - size_t * const numTags_added, - const unsigned int ** const tags_removed, - size_t * const numTags_removed, - bool * const userlistModified); + /** + * @brief Outputs the Bash Tags suggested for addition and removal by the + * database for the given plugin. + * @details loot_get_tag_map() must be called before this to ensure that + * the Bash Tag UIDs outputted by this function can be matched up + * to name strings. + * @param db + * The database the function acts on. + * @param plugin + * The filename of the plugin to look up Bash Tag suggestions for. + * @param tags_added + * A pointer to the outputted array of UIDs of the Bash Tags suggested + * for addition to the specified plugin. `NULL` if no Bash Tag + * additions are suggested. + * @param numTags_added + * A pointer to the size of the tags_added array. `0` if `tags_added` + * is `NULL`. + * @param tags_removed + * A pointer to the outputted array of UIDs of the Bash Tags suggested + * for removal from the specified plugin. `NULL` if no Bash Tag + * removals are suggested. + * @param numTags_removed + * A pointer to the size of the `tags_removed` array. `0` if + * `tags_removed` is `null`. + * @param userlistModified + * `true` if the Bash Tag suggestions were modified by the data in the + * userlist, `false` otherwise. + * @returns A return code. + */ + LOOT_API unsigned int loot_get_plugin_tags(loot_db * const db, + const char * const plugin, + const unsigned int ** const tags_added, + size_t * const numTags_added, + const unsigned int ** const tags_removed, + size_t * const numTags_removed, + bool * const userlistModified); - /** - * @brief Outputs the messages associated with the given plugin in the - * database. - * @param db - * The database the function acts on. - * @param plugin - * The filename of the plugin to look up messages for. - * @param messages - * A pointer to the outputted array of messages associated with the - * specified plugin, given as loot_message structures. `NULL` if the - * plugin has no messages associated with it. - * @param numMessages - * A pointer to the size of the outputted array. If no messages are - * outputted, this will be `0`. - * @returns A return code. - */ - LOOT_API unsigned int loot_get_plugin_messages(loot_db * const db, - const char * const plugin, - const loot_message ** const messages, - size_t * const numMessages); + /** + * @brief Outputs the messages associated with the given plugin in the + * database. + * @param db + * The database the function acts on. + * @param plugin + * The filename of the plugin to look up messages for. + * @param messages + * A pointer to the outputted array of messages associated with the + * specified plugin, given as loot_message structures. `NULL` if the + * plugin has no messages associated with it. + * @param numMessages + * A pointer to the size of the outputted array. If no messages are + * outputted, this will be `0`. + * @returns A return code. + */ + LOOT_API unsigned int loot_get_plugin_messages(loot_db * const db, + const char * const plugin, + const loot_message ** const messages, + size_t * const numMessages); - /** - * @brief Determines the database's knowledge of a plugin's dirtiness. - * @details Outputs whether the plugin should be cleaned or not, or if - * no data is available. The mechanism used to determine that - * a plugin should not be cleaned is not very reliable, and is - * likely to fail if `loot_eval_lists()` was called with a - * language other than English. As such, some plugins that should - * not be cleaned may have the `loot_needs_cleaning_unknown` - * code outputted. - * @param db - * The database the function acts on. - * @param plugin - * The plugin to look up dirty status information for. - * @param needsCleaning - * A pointer to a plugin cleanliness code. - * @returns A return code. - */ - LOOT_API unsigned int loot_get_dirty_info(loot_db * const db, - const char * const plugin, - unsigned int * const needsCleaning); + /** + * @brief Determines the database's knowledge of a plugin's dirtiness. + * @details Outputs whether the plugin should be cleaned or not, or if + * no data is available. The mechanism used to determine that + * a plugin should not be cleaned is not very reliable, and is + * likely to fail if `loot_eval_lists()` was called with a + * language other than English. As such, some plugins that should + * not be cleaned may have the `loot_needs_cleaning_unknown` + * code outputted. + * @param db + * The database the function acts on. + * @param plugin + * The plugin to look up dirty status information for. + * @param needsCleaning + * A pointer to a plugin cleanliness code. + * @returns A return code. + */ + LOOT_API unsigned int loot_get_dirty_info(loot_db * const db, + const char * const plugin, + unsigned int * const needsCleaning); - /** - * @brief Writes a minimal metadata file that only contains plugins with - * Bash Tag suggestions and/or dirty info, plus the suggestions and - * info themselves. - * @param db - * The database the function acts on. - * @param outputFile - * The path to which the file shall be written. - * @param overwrite - * If `false` and `outputFile` already exists, no data will be - * written. Otherwise, data will be written. - * @returns A return code. - */ - LOOT_API unsigned int loot_write_minimal_list(loot_db * const db, - const char * const outputFile, - const bool overwrite); + /** + * @brief Writes a minimal metadata file that only contains plugins with + * Bash Tag suggestions and/or dirty info, plus the suggestions and + * info themselves. + * @param db + * The database the function acts on. + * @param outputFile + * The path to which the file shall be written. + * @param overwrite + * If `false` and `outputFile` already exists, no data will be + * written. Otherwise, data will be written. + * @returns A return code. + */ + LOOT_API unsigned int loot_write_minimal_list(loot_db * const db, + const char * const outputFile, + const bool overwrite); - /**@}*/ + /**@}*/ #ifdef __cplusplus } diff --git a/src/api/api.cpp b/src/api/api.cpp index db64eb5f..c351b0eb 100644 --- a/src/api/api.cpp +++ b/src/api/api.cpp @@ -23,13 +23,6 @@ */ #include "loot/api.h" -#include "loot_db.h" -#include "../backend/error.h" -#include "../backend/app/loot_paths.h" -#include "../backend/app/loot_version.h" -#include "../backend/plugin/plugin_sorter.h" - -#include #include #include @@ -40,9 +33,18 @@ #include #include #include +#include + +#include "api/loot_db.h" +#include "backend/error.h" +#include "backend/app/loot_paths.h" +#include "backend/app/loot_version.h" +#include "backend/plugin/plugin_sorter.h" using loot::Error; using loot::GameType; +using loot::Message; +using loot::Language; const unsigned int loot_ok = Error::asUnsignedInt(Error::Code::ok); const unsigned int loot_error_liblo_error = Error::asUnsignedInt(Error::Code::liblo_error); @@ -67,23 +69,22 @@ const unsigned int loot_game_fo3 = static_cast(GameType::fo3); const unsigned int loot_game_fonv = static_cast(GameType::fonv); const unsigned int loot_game_fo4 = static_cast(GameType::fo4); -// LOOT message types. -const unsigned int loot_message_say = static_cast(loot::Message::Type::say); -const unsigned int loot_message_warn = static_cast(loot::Message::Type::warn); -const unsigned int loot_message_error = static_cast(loot::Message::Type::error); +const unsigned int loot_message_say = static_cast(Message::Type::say); +const unsigned int loot_message_warn = static_cast(Message::Type::warn); +const unsigned int loot_message_error = static_cast(Message::Type::error); // LOOT message languages. -const unsigned int loot_lang_english = static_cast(loot::Language::Code::english); -const unsigned int loot_lang_spanish = static_cast(loot::Language::Code::spanish); -const unsigned int loot_lang_russian = static_cast(loot::Language::Code::russian); -const unsigned int loot_lang_french = static_cast(loot::Language::Code::french); -const unsigned int loot_lang_chinese = static_cast(loot::Language::Code::chinese); -const unsigned int loot_lang_polish = static_cast(loot::Language::Code::polish); -const unsigned int loot_lang_brazilian_portuguese = static_cast(loot::Language::Code::brazilian_portuguese); -const unsigned int loot_lang_finnish = static_cast(loot::Language::Code::finnish); -const unsigned int loot_lang_german = static_cast(loot::Language::Code::german); -const unsigned int loot_lang_danish = static_cast(loot::Language::Code::danish); -const unsigned int loot_lang_korean = static_cast(loot::Language::Code::korean); +const unsigned int loot_lang_english = static_cast(Language::Code::english); +const unsigned int loot_lang_spanish = static_cast(Language::Code::spanish); +const unsigned int loot_lang_russian = static_cast(Language::Code::russian); +const unsigned int loot_lang_french = static_cast(Language::Code::french); +const unsigned int loot_lang_chinese = static_cast(Language::Code::chinese); +const unsigned int loot_lang_polish = static_cast(Language::Code::polish); +const unsigned int loot_lang_brazilian_portuguese = static_cast(Language::Code::brazilian_portuguese); +const unsigned int loot_lang_finnish = static_cast(Language::Code::finnish); +const unsigned int loot_lang_german = static_cast(Language::Code::german); +const unsigned int loot_lang_danish = static_cast(Language::Code::danish); +const unsigned int loot_lang_korean = static_cast(Language::Code::korean); // LOOT cleanliness codes. const unsigned int loot_needs_cleaning_no = 0; @@ -93,12 +94,12 @@ const unsigned int loot_needs_cleaning_unknown = 2; std::string extMessageStr; unsigned int c_error(const Error& e) { - extMessageStr = e.what(); - return e.codeAsUnsignedInt(); + extMessageStr = e.what(); + return e.codeAsUnsignedInt(); } unsigned int c_error(const unsigned int code, const std::string& what) { - return c_error(Error(Error::Code(code), what.c_str())); + return c_error(Error(Error::Code(code), what.c_str())); } ////////////////////////////// @@ -109,12 +110,12 @@ unsigned int c_error(const unsigned int code, const std::string& what) { // warning return code was returned by a function. The string exists // until this function is called again or until CleanUpAPI is called. LOOT_API unsigned int loot_get_error_message(const char ** const message) { - if (message == nullptr) - return c_error(loot_error_invalid_args, "Null message pointer passed."); + if (message == nullptr) + return c_error(loot_error_invalid_args, "Null message pointer passed."); - *message = extMessageStr.c_str(); + *message = extMessageStr.c_str(); - return loot_ok; + return loot_ok; } ////////////////////////////// @@ -124,33 +125,33 @@ LOOT_API unsigned int loot_get_error_message(const char ** const message) { // Returns whether this version of LOOT supports the API from the given // LOOT version. Abstracts LOOT API stability policy away from clients. LOOT_API bool loot_is_compatible(const unsigned int versionMajor, const unsigned int versionMinor, const unsigned int versionPatch) { - if (versionMajor > 0) - return versionMajor == loot::LootVersion::major; - else - return versionMinor == loot::LootVersion::minor; + if (versionMajor > 0) + return versionMajor == loot::LootVersion::major; + else + return versionMinor == loot::LootVersion::minor; } // Returns the version string for this version of LOOT. // The string exists until this function is called again or until // CleanUpAPI is called. LOOT_API unsigned int loot_get_version(unsigned int * const versionMajor, unsigned int * const versionMinor, unsigned int * const versionPatch) { - if (versionMajor == nullptr || versionMinor == nullptr || versionPatch == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (versionMajor == nullptr || versionMinor == nullptr || versionPatch == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - *versionMajor = loot::LootVersion::major; - *versionMinor = loot::LootVersion::minor; - *versionPatch = loot::LootVersion::patch; + *versionMajor = loot::LootVersion::major; + *versionMinor = loot::LootVersion::minor; + *versionPatch = loot::LootVersion::patch; - return loot_ok; + return loot_ok; } LOOT_API unsigned int loot_get_build_id(const char ** const revision) { - if (revision == nullptr) - return c_error(loot_error_invalid_args, "Null message pointer passed."); + if (revision == nullptr) + return c_error(loot_error_invalid_args, "Null message pointer passed."); - *revision = loot::LootVersion::revision.c_str(); + *revision = loot::LootVersion::revision.c_str(); - return loot_ok; + return loot_ok; } //////////////////////////////////// @@ -168,57 +169,54 @@ LOOT_API unsigned int loot_create_db(loot_db ** const db, const unsigned int clientGame, const char * const gamePath, const char * const gameLocalPath) { - if (db == nullptr - || (clientGame != loot_game_tes4 - && clientGame != loot_game_tes5 - && clientGame != loot_game_fo3 - && clientGame != loot_game_fonv - && clientGame != loot_game_fo4)) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr + || (clientGame != loot_game_tes4 + && clientGame != loot_game_tes5 + && clientGame != loot_game_fo3 + && clientGame != loot_game_fonv + && clientGame != loot_game_fo4)) + return c_error(loot_error_invalid_args, "Null pointer passed."); - loot::LootPaths::initialise(); + loot::LootPaths::initialise(); - //Disable logging or else stdout will get overrun. - boost::log::core::get()->set_logging_enabled(false); + //Disable logging or else stdout will get overrun. + boost::log::core::get()->set_logging_enabled(false); - std::string game_path = ""; - if (gamePath != nullptr) - game_path = gamePath; + std::string game_path = ""; + if (gamePath != nullptr) + game_path = gamePath; - boost::filesystem::path game_local_path = ""; - if (gameLocalPath != nullptr) - game_local_path = gameLocalPath; + boost::filesystem::path game_local_path = ""; + if (gameLocalPath != nullptr) + game_local_path = gameLocalPath; #ifndef _WIN32 - else - return c_error(loot_error_invalid_args, "A local data path must be supplied on non-Windows platforms."); + else + return c_error(loot_error_invalid_args, "A local data path must be supplied on non-Windows platforms."); #endif - try { - // Check for valid paths. - if (gamePath != nullptr && !boost::filesystem::is_directory(gamePath)) - return c_error(loot_error_invalid_args, "Given game path \"" + std::string(gamePath) + "\" is not a valid directory."); + try { + // Check for valid paths. + if (gamePath != nullptr && !boost::filesystem::is_directory(gamePath)) + return c_error(loot_error_invalid_args, "Given game path \"" + std::string(gamePath) + "\" is not a valid directory."); - if (gameLocalPath != nullptr && !boost::filesystem::is_directory(gameLocalPath)) - return c_error(loot_error_invalid_args, "Given local data path \"" + std::string(gameLocalPath) + "\" is not a valid directory."); + if (gameLocalPath != nullptr && !boost::filesystem::is_directory(gameLocalPath)) + return c_error(loot_error_invalid_args, "Given local data path \"" + std::string(gameLocalPath) + "\" is not a valid directory."); - *db = new loot_db(clientGame, game_path, game_local_path); - } - catch (Error& e) { - return c_error(e); - } - catch (std::bad_alloc& e) { - return c_error(loot_error_no_mem, e.what()); - } - catch (std::exception& e) { - return c_error(loot_error_invalid_args, e.what()); - } + *db = new loot_db(clientGame, game_path, game_local_path); + } catch (Error& e) { + return c_error(e); + } catch (std::bad_alloc& e) { + return c_error(loot_error_no_mem, e.what()); + } catch (std::exception& e) { + return c_error(loot_error_invalid_args, e.what()); + } - return loot_ok; + return loot_ok; } // Destroys the given DB, freeing any memory allocated as part of its use. LOOT_API void loot_destroy_db(loot_db * const db) { - delete db; + delete db; } /////////////////////////////////// @@ -231,48 +229,44 @@ LOOT_API void loot_destroy_db(loot_db * const db) { // masterlistPath and userlistPath are files. LOOT_API unsigned int loot_load_lists(loot_db * const db, const char * const masterlistPath, const char * const userlistPath) { - if (db == nullptr || masterlistPath == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || masterlistPath == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - loot::Masterlist temp; - loot::MetadataList userTemp; + loot::Masterlist temp; + loot::MetadataList userTemp; - try { - if (boost::filesystem::exists(masterlistPath)) { - temp.Load(masterlistPath); - } - else { - return c_error(loot_error_path_not_found, std::string("The given masterlist path does not exist: ") + masterlistPath); - } + try { + if (boost::filesystem::exists(masterlistPath)) { + temp.Load(masterlistPath); + } else { + return c_error(loot_error_path_not_found, std::string("The given masterlist path does not exist: ") + masterlistPath); } - catch (std::exception& e) { - return c_error(loot_error_parse_fail, e.what()); + } catch (std::exception& e) { + return c_error(loot_error_parse_fail, e.what()); + } + + try { + if (userlistPath != nullptr) { + if (boost::filesystem::exists(userlistPath)) { + userTemp.Load(userlistPath); + } else { + return c_error(loot_error_path_not_found, std::string("The given userlist path does not exist: ") + userlistPath); + } } + } catch (YAML::Exception& e) { + return c_error(loot_error_parse_fail, e.what()); + } - try { - if (userlistPath != nullptr) { - if (boost::filesystem::exists(userlistPath)) { - userTemp.Load(userlistPath); - } - else { - return c_error(loot_error_path_not_found, std::string("The given userlist path does not exist: ") + userlistPath); - } - } - } - catch (YAML::Exception& e) { - return c_error(loot_error_parse_fail, e.what()); - } + //Also free memory. + db->clearBashTagMap(); + db->clearArrays(); - //Also free memory. - db->clearBashTagMap(); - db->clearArrays(); + db->GetMasterlist() = temp; + db->getUnevaluatedMasterlist() = temp; + db->GetUserlist() = userTemp; + db->getUnevaluatedUserlist() = userTemp; - db->GetMasterlist() = temp; - db->getUnevaluatedMasterlist() = temp; - db->GetUserlist() = userTemp; - db->getUnevaluatedUserlist() = userTemp; - - return loot_ok; + return loot_ok; } // Evaluates all conditional lines and regex mods the loaded masterlist. @@ -282,37 +276,36 @@ LOOT_API unsigned int loot_load_lists(loot_db * const db, const char * const mas // ignoring the results of any previous evaluations. Paths are case-sensitive // if the underlying filesystem is case-sensitive. LOOT_API unsigned int loot_eval_lists(loot_db * const db, const unsigned int language) { - if (db == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); - if (language != loot_lang_english - && language != loot_lang_spanish - && language != loot_lang_russian - && language != loot_lang_french - && language != loot_lang_chinese - && language != loot_lang_polish - && language != loot_lang_brazilian_portuguese - && language != loot_lang_finnish - && language != loot_lang_german - && language != loot_lang_danish) - return c_error(loot_error_invalid_args, "Invalid language code given."); + if (db == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); + if (language != loot_lang_english + && language != loot_lang_spanish + && language != loot_lang_russian + && language != loot_lang_french + && language != loot_lang_chinese + && language != loot_lang_polish + && language != loot_lang_brazilian_portuguese + && language != loot_lang_finnish + && language != loot_lang_german + && language != loot_lang_danish) + return c_error(loot_error_invalid_args, "Invalid language code given."); - // Clear caches before evaluating conditions. - db->ClearCachedConditions(); +// Clear caches before evaluating conditions. + db->ClearCachedConditions(); - loot::Masterlist temp = db->getUnevaluatedMasterlist(); - loot::MetadataList userTemp = db->getUnevaluatedUserlist(); - try { - // Refresh active plugins before evaluating conditions. - temp.EvalAllConditions(*db, loot::Language::Code(language)); - userTemp.EvalAllConditions(*db, loot::Language::Code(language)); - } - catch (Error& e) { - return c_error(e); - } - db->GetMasterlist() = temp; - db->GetUserlist() = userTemp; + loot::Masterlist temp = db->getUnevaluatedMasterlist(); + loot::MetadataList userTemp = db->getUnevaluatedUserlist(); + try { + // Refresh active plugins before evaluating conditions. + temp.EvalAllConditions(*db, Language(Language::Code(language)).GetCode()); + userTemp.EvalAllConditions(*db, Language(Language::Code(language)).GetCode()); + } catch (loot::Error& e) { + return c_error(e); + } + db->GetMasterlist() = temp; + db->GetUserlist() = userTemp; - return loot_ok; + return loot_ok; } //////////////////////////////////// @@ -322,52 +315,49 @@ LOOT_API unsigned int loot_eval_lists(loot_db * const db, const unsigned int lan LOOT_API unsigned int loot_sort_plugins(loot_db * const db, const char * const ** const sortedPlugins, size_t * const numPlugins) { - if (db == nullptr || sortedPlugins == nullptr || numPlugins == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || sortedPlugins == nullptr || numPlugins == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - //Initialise output. - *numPlugins = 0; - *sortedPlugins = nullptr; +//Initialise output. + *numPlugins = 0; + *sortedPlugins = nullptr; - try { - // Always reload all the plugins. - db->LoadPlugins(false); + try { + // Always reload all the plugins. + db->LoadPlugins(false); - //Sort plugins into their load order. - loot::PluginSorter sorter; + //Sort plugins into their load order. + loot::PluginSorter sorter; - db->setPluginNames(sorter.Sort(*db, loot::Language::Code::english)); - } - catch (Error &e) { - return c_error(e); - } - catch (std::bad_alloc& e) { - return c_error(loot_error_no_mem, e.what()); - } - - if (db->getPluginNames().empty()) - return loot_ok; - - *numPlugins = db->getPluginNames().size(); - *sortedPlugins = &db->getPluginNames()[0]; + db->setPluginNames(sorter.Sort(*db, loot::Language::Code::english)); + } catch (Error &e) { + return c_error(e); + } catch (std::bad_alloc& e) { + return c_error(loot_error_no_mem, e.what()); + } + if (db->getPluginNames().empty()) return loot_ok; + + *numPlugins = db->getPluginNames().size(); + *sortedPlugins = &db->getPluginNames()[0]; + + return loot_ok; } LOOT_API unsigned int loot_apply_load_order(loot_db * const db, const char * const * const loadOrder, const size_t numPlugins) { - if (db == nullptr || loadOrder == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || loadOrder == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - try { - db->SetLoadOrder(loadOrder, numPlugins); - } - catch (Error &e) { - return c_error(e); - } + try { + db->SetLoadOrder(loadOrder, numPlugins); + } catch (Error &e) { + return c_error(e); + } - return loot_ok; + return loot_ok; } LOOT_API unsigned int loot_update_masterlist(loot_db * const db, @@ -375,22 +365,21 @@ LOOT_API unsigned int loot_update_masterlist(loot_db * const db, const char * const remoteURL, const char * const remoteBranch, bool * const updated) { - if (db == nullptr || masterlistPath == nullptr || remoteURL == nullptr || remoteBranch == nullptr || updated == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); - if (!boost::filesystem::is_directory(boost::filesystem::path(masterlistPath).parent_path())) - return c_error(loot_error_invalid_args, "Given masterlist path \"" + std::string(masterlistPath) + "\" does not have a valid parent directory."); + if (db == nullptr || masterlistPath == nullptr || remoteURL == nullptr || remoteBranch == nullptr || updated == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); + if (!boost::filesystem::is_directory(boost::filesystem::path(masterlistPath).parent_path())) + return c_error(loot_error_invalid_args, "Given masterlist path \"" + std::string(masterlistPath) + "\" does not have a valid parent directory."); - *updated = false; + *updated = false; - try { - loot::Masterlist masterlist; - *updated = masterlist.Update(masterlistPath, remoteURL, remoteBranch); - } - catch (Error &e) { - return c_error(e); - } + try { + loot::Masterlist masterlist; + *updated = masterlist.Update(masterlistPath, remoteURL, remoteBranch); + } catch (Error &e) { + return c_error(e); + } - return loot_ok; + return loot_ok; } LOOT_API unsigned int loot_get_masterlist_revision(loot_db * const db, @@ -399,43 +388,41 @@ LOOT_API unsigned int loot_get_masterlist_revision(loot_db * const db, const char ** const revisionID, const char ** const revisionDate, bool * const isModified) { - if (db == nullptr || masterlistPath == nullptr || revisionID == nullptr || revisionDate == nullptr || isModified == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || masterlistPath == nullptr || revisionID == nullptr || revisionDate == nullptr || isModified == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - *revisionID = nullptr; - *revisionDate = nullptr; - *isModified = false; + *revisionID = nullptr; + *revisionDate = nullptr; + *isModified = false; - bool edited = false; - try { - loot::Masterlist::Info info = loot::Masterlist::GetInfo(masterlistPath, getShortID); - std::string id = info.revision; - std::string date = info.date; + bool edited = false; + try { + loot::Masterlist::Info info = loot::Masterlist::GetInfo(masterlistPath, getShortID); + std::string id = info.revision; + std::string date = info.date; - if (boost::ends_with(id, " (edited)")) { - id = id.substr(0, id.length() - 9); - date = date.substr(0, date.length() - 9); - edited = true; - } - - db->setRevisionIdString(id); - db->setRevisionDateString(date); - } - catch (Error &e) { - if (e.code() == Error::Code::ok) - return loot_ok; - else - return c_error(e); - } - catch (std::bad_alloc& e) { - return c_error(loot_error_no_mem, e.what()); + if (boost::ends_with(id, " (edited)")) { + id = id.substr(0, id.length() - 9); + date = date.substr(0, date.length() - 9); + edited = true; } - *revisionID = db->getRevisionIdString(); - *revisionDate = db->getRevisionDateString(); - *isModified = edited; + db->setRevisionIdString(id); + db->setRevisionDateString(date); + } catch (Error &e) { + if (e.code() == Error::Code::ok) + return loot_ok; + else + return c_error(e); + } catch (std::bad_alloc& e) { + return c_error(loot_error_no_mem, e.what()); + } - return loot_ok; + *revisionID = db->getRevisionIdString(); + *revisionDate = db->getRevisionDateString(); + *isModified = edited; + + return loot_ok; } ////////////////////////// @@ -446,43 +433,42 @@ LOOT_API unsigned int loot_get_masterlist_revision(loot_db * const db, // and userlist, and the number of tags in the returned array. The array and // its contents are static and should not be freed by the client. LOOT_API unsigned int loot_get_tag_map(loot_db * const db, const char * const ** const tagMap, size_t * const numTags) { - if (db == nullptr || tagMap == nullptr || numTags == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || tagMap == nullptr || numTags == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - //Clear existing array allocation. - db->clearBashTagMap(); +//Clear existing array allocation. + db->clearBashTagMap(); - //Initialise output. - *tagMap = nullptr; - *numTags = 0; + //Initialise output. + *tagMap = nullptr; + *numTags = 0; - std::set allTags; + std::set allTags; - for (const auto &plugin : db->GetMasterlist().Plugins()) { - for (const auto &tag : plugin.Tags()) { - allTags.insert(tag.Name()); - } + for (const auto &plugin : db->GetMasterlist().Plugins()) { + for (const auto &tag : plugin.Tags()) { + allTags.insert(tag.Name()); } - for (const auto &plugin : db->GetUserlist().Plugins()) { - for (const auto &tag : plugin.Tags()) { - allTags.insert(tag.Name()); - } + } + for (const auto &plugin : db->GetUserlist().Plugins()) { + for (const auto &tag : plugin.Tags()) { + allTags.insert(tag.Name()); } + } - if (allTags.empty()) - return loot_ok; - - try { - db->addBashTagsToMap(allTags); - } - catch (std::bad_alloc& e) { - return c_error(loot_error_no_mem, e.what()); - } - - *tagMap = &db->getBashTagMap()[0]; - *numTags = db->getBashTagMap().size(); - + if (allTags.empty()) return loot_ok; + + try { + db->addBashTagsToMap(allTags); + } catch (std::bad_alloc& e) { + return c_error(loot_error_no_mem, e.what()); + } + + *tagMap = &db->getBashTagMap()[0]; + *numTags = db->getBashTagMap().size(); + + return loot_ok; } // Returns arrays of Bash Tag UIDs for Bash Tags suggested for addition and removal @@ -498,62 +484,61 @@ LOOT_API unsigned int loot_get_plugin_tags(loot_db * const db, const char * cons const unsigned int ** const tagIds_removed, size_t * const numTags_removed, bool * const userlistModified) { - if (db == nullptr || plugin == nullptr || tagIds_added == nullptr || numTags_added == nullptr || tagIds_removed == nullptr || numTags_removed == nullptr || userlistModified == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || plugin == nullptr || tagIds_added == nullptr || numTags_added == nullptr || tagIds_removed == nullptr || numTags_removed == nullptr || userlistModified == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - if (db->getBashTagMap().empty()) { - return c_error(loot_error_no_tag_map, "No Bash Tag map has been previously generated."); - } + if (db->getBashTagMap().empty()) { + return c_error(loot_error_no_tag_map, "No Bash Tag map has been previously generated."); + } - //Initialise output. + //Initialise output. + *tagIds_added = nullptr; + *tagIds_removed = nullptr; + *userlistModified = false; + *numTags_added = 0; + *numTags_removed = 0; + + std::set tagsAdded, tagsRemoved; + loot::PluginMetadata p = db->GetMasterlist().FindPlugin(loot::PluginMetadata(plugin)); + for (const auto &tag : p.Tags()) { + if (tag.IsAddition()) + tagsAdded.insert(tag.Name()); + else + tagsRemoved.insert(tag.Name()); + } + + p = db->GetUserlist().FindPlugin(loot::PluginMetadata(plugin)); + *userlistModified = !p.Tags().empty(); + for (const auto &tag : p.Tags()) { + *userlistModified = true; + if (tag.IsAddition()) + tagsAdded.insert(tag.Name()); + else + tagsRemoved.insert(tag.Name()); + } + + try { + db->setAddedTags(tagsAdded); + db->setRemovedTags(tagsRemoved); + } catch (Error& e) { + return c_error(e); + } + + //Set outputs. + *numTags_added = db->getAddedTagIds().size(); + *numTags_removed = db->getRemovedTagIds().size(); + + if (db->getAddedTagIds().empty()) *tagIds_added = nullptr; + else + *tagIds_added = reinterpret_cast(&db->getAddedTagIds()[0]); + + if (db->getRemovedTagIds().empty()) *tagIds_removed = nullptr; - *userlistModified = false; - *numTags_added = 0; - *numTags_removed = 0; + else + *tagIds_removed = reinterpret_cast(&db->getRemovedTagIds()[0]); - std::set tagsAdded, tagsRemoved; - loot::PluginMetadata p = db->GetMasterlist().FindPlugin(loot::PluginMetadata(plugin)); - for (const auto &tag : p.Tags()) { - if (tag.IsAddition()) - tagsAdded.insert(tag.Name()); - else - tagsRemoved.insert(tag.Name()); - } - - p = db->GetUserlist().FindPlugin(loot::PluginMetadata(plugin)); - *userlistModified = !p.Tags().empty(); - for (const auto &tag : p.Tags()) { - *userlistModified = true; - if (tag.IsAddition()) - tagsAdded.insert(tag.Name()); - else - tagsRemoved.insert(tag.Name()); - } - - try { - db->setAddedTags(tagsAdded); - db->setRemovedTags(tagsRemoved); - } - catch (Error& e) { - return c_error(e); - } - - //Set outputs. - *numTags_added = db->getAddedTagIds().size(); - *numTags_removed = db->getRemovedTagIds().size(); - - if (db->getAddedTagIds().empty()) - *tagIds_added = nullptr; - else - *tagIds_added = reinterpret_cast(&db->getAddedTagIds()[0]); - - if (db->getRemovedTagIds().empty()) - *tagIds_removed = nullptr; - else - *tagIds_removed = reinterpret_cast(&db->getRemovedTagIds()[0]); - - return loot_ok; + return loot_ok; } // Returns the messages attached to the given plugin. Messages are valid until Load, @@ -562,60 +547,60 @@ LOOT_API unsigned int loot_get_plugin_tags(loot_db * const db, const char * cons LOOT_API unsigned int loot_get_plugin_messages(loot_db * const db, const char * const plugin, const loot_message ** const messages, size_t * const numMessages) { - if (db == nullptr || plugin == nullptr || messages == nullptr || numMessages == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || plugin == nullptr || messages == nullptr || numMessages == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - //Initialise output. - *messages = nullptr; - *numMessages = 0; +//Initialise output. + *messages = nullptr; + *numMessages = 0; - loot::PluginMetadata p = db->GetMasterlist().FindPlugin(loot::PluginMetadata(plugin)); - std::list pluginMessages(p.Messages()); + loot::PluginMetadata p = db->GetMasterlist().FindPlugin(loot::PluginMetadata(plugin)); + std::list pluginMessages(p.Messages()); - p = db->GetUserlist().FindPlugin(loot::PluginMetadata(plugin)); - std::list temp(p.Messages()); - pluginMessages.insert(pluginMessages.end(), temp.begin(), temp.end()); - - if (pluginMessages.empty()) - return loot_ok; - - db->setPluginMessages(pluginMessages); - - *messages = &db->getPluginMessages()[0]; - *numMessages = db->getPluginMessages().size(); + p = db->GetUserlist().FindPlugin(loot::PluginMetadata(plugin)); + std::list temp(p.Messages()); + pluginMessages.insert(pluginMessages.end(), temp.begin(), temp.end()); + if (pluginMessages.empty()) return loot_ok; + + db->setPluginMessages(pluginMessages); + + *messages = &db->getPluginMessages()[0]; + *numMessages = db->getPluginMessages().size(); + + return loot_ok; } LOOT_API unsigned int loot_get_dirty_info(loot_db * const db, const char * const plugin, unsigned int * const needsCleaning) { - if (db == nullptr || plugin == nullptr || needsCleaning == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || plugin == nullptr || needsCleaning == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - *needsCleaning = loot_needs_cleaning_unknown; + *needsCleaning = loot_needs_cleaning_unknown; - // Is there any dirty info? Testing for applicability happens in loot_eval_lists(). - if (!db->GetMasterlist().FindPlugin(loot::PluginMetadata(plugin)).DirtyInfo().empty() - || !db->GetUserlist().FindPlugin(loot::PluginMetadata(plugin)).DirtyInfo().empty()) { - *needsCleaning = loot_needs_cleaning_yes; + // Is there any dirty info? Testing for applicability happens in loot_eval_lists(). + if (!db->GetMasterlist().FindPlugin(loot::PluginMetadata(plugin)).DirtyInfo().empty() + || !db->GetUserlist().FindPlugin(loot::PluginMetadata(plugin)).DirtyInfo().empty()) { + *needsCleaning = loot_needs_cleaning_yes; + } + + // Is there a message beginning with the substring "Do not clean."? + // This isn't a very reliable system, because if the lists have been evaluated in some language + // other than English, the strings will be in different languages (and the API can't tell what they'd be) + // and the strings may be non-standard and begin with something other than "Do not clean." anyway. + std::list messages(db->GetMasterlist().FindPlugin(loot::PluginMetadata(plugin)).Messages()); + + std::list temp(db->GetUserlist().FindPlugin(loot::PluginMetadata(plugin)).Messages()); + messages.insert(messages.end(), temp.begin(), temp.end()); + + for (const auto& message : messages) { + if (boost::starts_with(message.ChooseContent(loot::Language::Code::english).GetText(), "Do not clean")) { + *needsCleaning = loot_needs_cleaning_no; + break; } + } - // Is there a message beginning with the substring "Do not clean."? - // This isn't a very reliable system, because if the lists have been evaluated in some language - // other than English, the strings will be in different languages (and the API can't tell what they'd be) - // and the strings may be non-standard and begin with something other than "Do not clean." anyway. - std::list messages(db->GetMasterlist().FindPlugin(loot::PluginMetadata(plugin)).Messages()); - - std::list temp(db->GetUserlist().FindPlugin(loot::PluginMetadata(plugin)).Messages()); - messages.insert(messages.end(), temp.begin(), temp.end()); - - for (const auto& message : messages) { - if (boost::starts_with(message.ChooseContent(loot::Language::Code::english).GetText(), "Do not clean")) { - *needsCleaning = loot_needs_cleaning_no; - break; - } - } - - return loot_ok; + return loot_ok; } // Writes a minimal masterlist that only contains mods that have Bash Tag suggestions, @@ -623,41 +608,40 @@ LOOT_API unsigned int loot_get_dirty_info(loot_db * const db, const char * const // conditions, in order to create the Wrye Bash taglist. outputFile is the path to use // for output. If outputFile already exists, it will only be overwritten if overwrite is true. LOOT_API unsigned int loot_write_minimal_list(loot_db * const db, const char * const outputFile, const bool overwrite) { - if (db == nullptr || outputFile == nullptr) - return c_error(loot_error_invalid_args, "Null pointer passed."); + if (db == nullptr || outputFile == nullptr) + return c_error(loot_error_invalid_args, "Null pointer passed."); - if (!boost::filesystem::exists(boost::filesystem::path(outputFile).parent_path())) - return c_error(loot_error_invalid_args, "Output directory does not exist."); + if (!boost::filesystem::exists(boost::filesystem::path(outputFile).parent_path())) + return c_error(loot_error_invalid_args, "Output directory does not exist."); - if (boost::filesystem::exists(outputFile) && !overwrite) - return c_error(loot_error_file_write_fail, "Output file exists but overwrite is not set to true."); + if (boost::filesystem::exists(outputFile) && !overwrite) + return c_error(loot_error_file_write_fail, "Output file exists but overwrite is not set to true."); - loot::Masterlist temp = db->GetMasterlist(); - std::unordered_set minimalPlugins; - for (const auto &plugin : temp.Plugins()) { - loot::PluginMetadata p(plugin.Name()); - p.Tags(plugin.Tags()); - p.DirtyInfo(plugin.DirtyInfo()); - minimalPlugins.insert(p); - } + loot::Masterlist temp = db->GetMasterlist(); + std::unordered_set minimalPlugins; + for (const auto &plugin : temp.Plugins()) { + loot::PluginMetadata p(plugin.Name()); + p.Tags(plugin.Tags()); + p.DirtyInfo(plugin.DirtyInfo()); + minimalPlugins.insert(p); + } - YAML::Emitter yout; - yout.SetIndent(2); - yout << YAML::BeginMap - << YAML::Key << "plugins" << YAML::Value << minimalPlugins - << YAML::EndMap; + YAML::Emitter yout; + yout.SetIndent(2); + yout << YAML::BeginMap + << YAML::Key << "plugins" << YAML::Value << minimalPlugins + << YAML::EndMap; - boost::filesystem::path p(outputFile); - try { - boost::filesystem::ofstream out(p); - if (out.fail()) - return c_error(loot_error_file_write_fail, "Couldn't open output file."); - out << yout.c_str(); - out.close(); - } - catch (std::exception& e) { - return c_error(loot_error_file_write_fail, e.what()); - } + boost::filesystem::path p(outputFile); + try { + boost::filesystem::ofstream out(p); + if (out.fail()) + return c_error(loot_error_file_write_fail, "Couldn't open output file."); + out << yout.c_str(); + out.close(); + } catch (std::exception& e) { + return c_error(loot_error_file_write_fail, e.what()); + } - return loot_ok; + return loot_ok; } diff --git a/src/api/loot_db.cpp b/src/api/loot_db.cpp index c9aff9ff..9ad6f17b 100644 --- a/src/api/loot_db.cpp +++ b/src/api/loot_db.cpp @@ -22,123 +22,123 @@ . */ -#include "loot_db.h" +#include "api/loot_db.h" -#include "../backend/error.h" +#include "backend/error.h" loot_db::loot_db(const unsigned int clientGame, const std::string& gamePath, const boost::filesystem::path& gameLocalDataPath) - : Game(loot::GameType(clientGame)) { - this->SetGamePath(gamePath); - this->Init(false, gameLocalDataPath); + : Game(loot::GameType(clientGame)) { + this->SetGamePath(gamePath); + this->Init(false, gameLocalDataPath); } loot::Masterlist& loot_db::getUnevaluatedMasterlist() { - return unevaluatedMasterlist_; + return unevaluatedMasterlist_; } loot::MetadataList& loot_db::getUnevaluatedUserlist() { - return unevaluatedUserlist_; + return unevaluatedUserlist_; } const char * loot_db::getRevisionIdString() const { - return revisionId.c_str(); + return revisionId.c_str(); } const char * loot_db::getRevisionDateString() const { - return revisionDate.c_str(); + return revisionDate.c_str(); } const std::vector& loot_db::getPluginNames() const { - return cPluginNames; + return cPluginNames; } const std::vector& loot_db::getBashTagMap() const { - return cBashTagMap; + return cBashTagMap; } unsigned int loot_db::getBashTagUid(const std::string& name) const { - auto it = bashTagMap.find(name); - if (it != end(bashTagMap)) - return it->second; + auto it = bashTagMap.find(name); + if (it != end(bashTagMap)) + return it->second; - throw loot::Error(loot::Error::Code::no_tag_map, "The Bash Tag \"" + name + "\" does not exist in the Bash Tag map."); + throw loot::Error(loot::Error::Code::no_tag_map, "The Bash Tag \"" + name + "\" does not exist in the Bash Tag map."); } const std::vector& loot_db::getAddedTagIds() const { - return addedTagIds; + return addedTagIds; } const std::vector& loot_db::getRemovedTagIds() const { - return removedTagIds; + return removedTagIds; } const std::vector& loot_db::getPluginMessages() const { - return cPluginMessages; + return cPluginMessages; } void loot_db::setRevisionIdString(const std::string& str) { - revisionId = str; + revisionId = str; } void loot_db::setRevisionDateString(const std::string& str) { - revisionDate = str; + revisionDate = str; } void loot_db::setAddedTags(const std::set& names) { - addedTagIds.clear(); - for (const auto& name : names) - addedTagIds.push_back(getBashTagUid(name)); + addedTagIds.clear(); + for (const auto& name : names) + addedTagIds.push_back(getBashTagUid(name)); } void loot_db::setRemovedTags(const std::set& names) { - removedTagIds.clear(); - for (const auto& name : names) - removedTagIds.push_back(getBashTagUid(name)); + removedTagIds.clear(); + for (const auto& name : names) + removedTagIds.push_back(getBashTagUid(name)); } void loot_db::setPluginMessages(const std::list& pluginMessages) { - cPluginMessages.resize(pluginMessages.size()); - pluginMessageStrings.resize(pluginMessages.size()); + cPluginMessages.resize(pluginMessages.size()); + pluginMessageStrings.resize(pluginMessages.size()); - size_t i = 0; - for (const auto& message : pluginMessages) { - pluginMessageStrings[i] = message.ChooseContent(loot::Language::Code::english).GetText(); + size_t i = 0; + for (const auto& message : pluginMessages) { + pluginMessageStrings[i] = message.ChooseContent(loot::Language::Code::english).GetText(); - cPluginMessages[i].type = static_cast(message.GetType()); - cPluginMessages[i].message = pluginMessageStrings[i].c_str(); + cPluginMessages[i].type = static_cast(message.GetType()); + cPluginMessages[i].message = pluginMessageStrings[i].c_str(); - ++i; - } + ++i; + } } void loot_db::addBashTagsToMap(std::set names) { - for (const auto& name : names) { - // Try adding the Bash Tag to the map assuming it's not already in - // there, then use the UID in the returned value, as that will be - // equal to the value in the map, even if the Bash Tag was already - // present. - unsigned int uid = bashTagMap.size(); - // If the tag already exists in the map, do - auto it = bashTagMap.emplace(name, uid).first; - if (it->second == cBashTagMap.size()) - cBashTagMap.push_back(it->first.c_str()); - else - cBashTagMap.at(it->second) = it->first.c_str(); - } + for (const auto& name : names) { + // Try adding the Bash Tag to the map assuming it's not already in + // there, then use the UID in the returned value, as that will be + // equal to the value in the map, even if the Bash Tag was already + // present. + unsigned int uid = bashTagMap.size(); + // If the tag already exists in the map, do + auto it = bashTagMap.emplace(name, uid).first; + if (it->second == cBashTagMap.size()) + cBashTagMap.push_back(it->first.c_str()); + else + cBashTagMap.at(it->second) = it->first.c_str(); + } } void loot_db::clearBashTagMap() { - bashTagMap.clear(); - cBashTagMap.clear(); + bashTagMap.clear(); + cBashTagMap.clear(); } void loot_db::clearArrays() { - pluginNames.clear(); - cPluginNames.clear(); + pluginNames.clear(); + cPluginNames.clear(); - addedTagIds.clear(); - removedTagIds.clear(); + addedTagIds.clear(); + removedTagIds.clear(); - cPluginMessages.clear(); - pluginMessageStrings.clear(); + cPluginMessages.clear(); + pluginMessageStrings.clear(); } diff --git a/src/api/loot_db.h b/src/api/loot_db.h index 52a9d675..67b3e580 100644 --- a/src/api/loot_db.h +++ b/src/api/loot_db.h @@ -22,92 +22,89 @@ . */ -#ifndef LOOT_API_LOOT_DB_INT_H -#define LOOT_API_LOOT_DB_INT_H - -#include "../backend/game/game.h" -#include "../include/loot/api.h" +#ifndef LOOT_API_LOOT_DB +#define LOOT_API_LOOT_DB #include #include +#include "backend/game/game.h" +#include "loot/api.h" + struct loot_db : public loot::Game { - loot_db(const unsigned int clientGame, - const std::string& gamePath, - const boost::filesystem::path& gameLocalDataPath); + loot_db(const unsigned int clientGame, + const std::string& gamePath, + const boost::filesystem::path& gameLocalDataPath); - loot::Masterlist& getUnevaluatedMasterlist(); - loot::MetadataList& getUnevaluatedUserlist(); + loot::Masterlist& getUnevaluatedMasterlist(); + loot::MetadataList& getUnevaluatedUserlist(); - loot::MetadataList rawUserMetadata; - loot::Masterlist rawMetadata; + const char * getRevisionIdString() const; + const char * getRevisionDateString() const; - const char * getRevisionIdString() const; - const char * getRevisionDateString() const; + const std::vector& getPluginNames() const; - const std::vector& getPluginNames() const; + const std::vector& getBashTagMap() const; + unsigned int getBashTagUid(const std::string& name) const; - const std::vector& getBashTagMap() const; - unsigned int getBashTagUid(const std::string& name) const; + const std::vector& getAddedTagIds() const; + const std::vector& getRemovedTagIds() const; - const std::vector& getAddedTagIds() const; - const std::vector& getRemovedTagIds() const; + const std::vector& getPluginMessages() const; - const std::vector& getPluginMessages() const; + void setRevisionIdString(const std::string& str); + void setRevisionDateString(const std::string& str); - void setRevisionIdString(const std::string& str); - void setRevisionDateString(const std::string& str); + template + void setPluginNames(const T& plugins) { + // First take copies of the C++ strings to store. + pluginNames.resize(plugins.size()); + std::transform(begin(plugins), + end(plugins), + begin(pluginNames), + [](const loot::PluginMetadata& plugin) { + return plugin.Name(); + }); - template - void setPluginNames(const T& plugins) { - // First take copies of the C++ strings to store. - pluginNames.resize(plugins.size()); - std::transform(begin(plugins), - end(plugins), - begin(pluginNames), - [](const loot::PluginMetadata& plugin) { - return plugin.Name(); - }); + // Now store their C strings. + cPluginNames.resize(pluginNames.size()); + std::transform(begin(pluginNames), + end(pluginNames), + begin(cPluginNames), + [](const std::string& pluginName) { + return pluginName.c_str(); + }); + } - // Now store their C strings. - cPluginNames.resize(pluginNames.size()); - std::transform(begin(pluginNames), - end(pluginNames), - begin(cPluginNames), - [](const std::string& pluginName) { - return pluginName.c_str(); - }); - } + void setAddedTags(const std::set& names); + void setRemovedTags(const std::set& names); - void setAddedTags(const std::set& names); - void setRemovedTags(const std::set& names); + void setPluginMessages(const std::list& pluginMessages); - void setPluginMessages(const std::list& pluginMessages); + void addBashTagsToMap(std::set names); - void addBashTagsToMap(std::set names); - - void clearBashTagMap(); - void clearArrays(); + void clearBashTagMap(); + void clearArrays(); private: - loot::Masterlist unevaluatedMasterlist_; - loot::MetadataList unevaluatedUserlist_; + loot::Masterlist unevaluatedMasterlist_; + loot::MetadataList unevaluatedUserlist_; - std::string revisionId; - std::string revisionDate; + std::string revisionId; + std::string revisionDate; - std::vector pluginNames; - std::vector cPluginNames; + std::vector pluginNames; + std::vector cPluginNames; - // For the Bash Tag map, a string is mapped to a UID that is also the - // index of the vector where the C string can be found. - std::unordered_map bashTagMap; - std::vector cBashTagMap; + // For the Bash Tag map, a string is mapped to a UID that is also the + // index of the vector where the C string can be found. + std::unordered_map bashTagMap; + std::vector cBashTagMap; - std::vector addedTagIds; - std::vector removedTagIds; + std::vector addedTagIds; + std::vector removedTagIds; - std::vector cPluginMessages; - std::vector pluginMessageStrings; + std::vector cPluginMessages; + std::vector pluginMessageStrings; }; #endif diff --git a/src/backend/app/loot_paths.cpp b/src/backend/app/loot_paths.cpp index a1d05a21..81c13646 100644 --- a/src/backend/app/loot_paths.cpp +++ b/src/backend/app/loot_paths.cpp @@ -23,12 +23,13 @@ Fallout: New Vegas. */ #include "loot_paths.h" -#include "../helpers/helpers.h" -#include "../error.h" + +#include #include -#include +#include "backend/error.h" +#include "backend/helpers/helpers.h" #ifdef _WIN32 # ifndef UNICODE @@ -42,69 +43,69 @@ Fallout: New Vegas. #endif namespace loot { - boost::filesystem::path LootPaths::getReadmePath() { - return lootAppPath / "docs" / "LOOT Readme.html"; - } +boost::filesystem::path LootPaths::getReadmePath() { + return lootAppPath_ / "docs" / "LOOT Readme.html"; +} - boost::filesystem::path LootPaths::getUIIndexPath() { - return lootAppPath / "resources" / "ui" / "index.html"; - } +boost::filesystem::path LootPaths::getUIIndexPath() { + return lootAppPath_ / "resources" / "ui" / "index.html"; +} - boost::filesystem::path LootPaths::getL10nPath() { - return lootAppPath / "resources" / "l10n"; - } +boost::filesystem::path LootPaths::getL10nPath() { + return lootAppPath_ / "resources" / "l10n"; +} - boost::filesystem::path LootPaths::getLootDataPath() { - return lootDataPath; - } +boost::filesystem::path LootPaths::getLootDataPath() { + return lootDataPath_; +} - boost::filesystem::path LootPaths::getSettingsPath() { - return lootDataPath / "settings.yaml"; - } +boost::filesystem::path LootPaths::getSettingsPath() { + return lootDataPath_ / "settings.yaml"; +} - boost::filesystem::path LootPaths::getLogPath() { - return lootDataPath / "LOOTDebugLog.txt"; - } +boost::filesystem::path LootPaths::getLogPath() { + return lootDataPath_ / "LOOTDebugLog.txt"; +} - void LootPaths::initialise() { - // Set the locale to get UTF-8 conversions working correctly. - std::locale::global(boost::locale::generator().generate("")); - boost::filesystem::path::imbue(std::locale()); +void LootPaths::initialise() { + // Set the locale to get UTF-8 conversions working correctly. + std::locale::global(boost::locale::generator().generate("")); + boost::filesystem::path::imbue(std::locale()); - lootAppPath = boost::filesystem::current_path(); - lootDataPath = getLocalAppDataPath() / "LOOT"; - } + lootAppPath_ = boost::filesystem::current_path(); + lootDataPath_ = getLocalAppDataPath() / "LOOT"; +} - boost::filesystem::path LootPaths::getLocalAppDataPath() { +boost::filesystem::path LootPaths::getLocalAppDataPath() { #ifdef _WIN32 - HWND owner = 0; - PWSTR path; + HWND owner = 0; + PWSTR path; - if (SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path) != S_OK) - throw Error(Error::Code::windows_error, boost::locale::translate("Failed to get %LOCALAPPDATA% path.")); + if (SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path) != S_OK) + throw Error(Error::Code::windows_error, boost::locale::translate("Failed to get %LOCALAPPDATA% path.")); - boost::filesystem::path localAppDataPath(FromWinWide(path)); - CoTaskMemFree(path); + boost::filesystem::path localAppDataPath(FromWinWide(path)); + CoTaskMemFree(path); - return localAppDataPath; + return localAppDataPath; #else // Use XDG_CONFIG_HOME environmental variable if it's available. - const char * xdgConfigHome = getenv("XDG_CONFIG_HOME"); + const char * xdgConfigHome = getenv("XDG_CONFIG_HOME"); - if (xdgConfigHome != nullptr) - return boost::filesystem::path(xdgConfigHome); + if (xdgConfigHome != nullptr) + return boost::filesystem::path(xdgConfigHome); - // Otherwise, use the HOME env. var. if it's available. - xdgConfigHome = getenv("HOME"); +// Otherwise, use the HOME env. var. if it's available. + xdgConfigHome = getenv("HOME"); - if (xdgConfigHome != nullptr) - return boost::filesystem::path(xdgConfigHome) / ".config"; + if (xdgConfigHome != nullptr) + return boost::filesystem::path(xdgConfigHome) / ".config"; - // If somehow both are missing, use the current path. - return boost::filesystem::current_path(); +// If somehow both are missing, use the current path. + return boost::filesystem::current_path(); #endif - } +} - boost::filesystem::path LootPaths::lootAppPath; - boost::filesystem::path LootPaths::lootDataPath; +boost::filesystem::path LootPaths::lootAppPath_; +boost::filesystem::path LootPaths::lootDataPath_; } diff --git a/src/backend/app/loot_paths.h b/src/backend/app/loot_paths.h index f9fd98c1..1006406f 100644 --- a/src/backend/app/loot_paths.h +++ b/src/backend/app/loot_paths.h @@ -22,31 +22,31 @@ along with LOOT. If not, see . */ -#ifndef LOOT_BACKEND_LOOT_PATHS -#define LOOT_BACKEND_LOOT_PATHS +#ifndef LOOT_BACKEND_APP_LOOT_PATHS +#define LOOT_BACKEND_APP_LOOT_PATHS #include namespace loot { - class LootPaths { - public: - static boost::filesystem::path getReadmePath(); - static boost::filesystem::path getUIIndexPath(); - static boost::filesystem::path getL10nPath(); - static boost::filesystem::path getLootDataPath(); - static boost::filesystem::path getSettingsPath(); - static boost::filesystem::path getLogPath(); +class LootPaths { +public: + static boost::filesystem::path getReadmePath(); + static boost::filesystem::path getUIIndexPath(); + static boost::filesystem::path getL10nPath(); + static boost::filesystem::path getLootDataPath(); + static boost::filesystem::path getSettingsPath(); + static boost::filesystem::path getLogPath(); - // Sets the app path to the current path, and the data path to the user - // local app data path / "LOOT". - static void initialise(); - private: - static boost::filesystem::path lootAppPath; - static boost::filesystem::path lootDataPath; + // Sets the app path to the current path, and the data path to the user + // local app data path / "LOOT". + static void initialise(); +private: + //Get the local application data path. + static boost::filesystem::path getLocalAppDataPath(); - //Get the local application data path. - static boost::filesystem::path getLocalAppDataPath(); - }; + static boost::filesystem::path lootAppPath_; + static boost::filesystem::path lootDataPath_; +}; } #endif diff --git a/src/backend/app/loot_settings.cpp b/src/backend/app/loot_settings.cpp index 268deda7..68f400c2 100644 --- a/src/backend/app/loot_settings.cpp +++ b/src/backend/app/loot_settings.cpp @@ -22,273 +22,275 @@ . */ -#include "loot_settings.h" -#include "backend/app/loot_version.h" +#include "backend/app/loot_settings.h" #include #include -using namespace std; +#include "backend/app/loot_version.h" + +using std::lock_guard; +using std::recursive_mutex; +using std::string; namespace loot { - LootSettings::WindowPosition::WindowPosition() : top(0), bottom(0), left(0), right(0) {} +LootSettings::WindowPosition::WindowPosition() : top(0), bottom(0), left(0), right(0) {} - LootSettings::LootSettings() : - gameSettings({ - GameSettings(GameType::tes4), - GameSettings(GameType::tes5), - GameSettings(GameType::fo3), - GameSettings(GameType::fonv), - GameSettings(GameType::fo4), - GameSettings(GameType::tes4, "Nehrim") - .SetName("Nehrim - At Fate's Edge") - .SetMaster("Nehrim.esm") - .SetRegistryKey("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Nehrim - At Fate's Edge_is1\\InstallLocation"), - }), - enableDebugLogging(false), - updateMasterlist(true), - game("auto"), - language(Language(Language::Code::english)), - lastGame("auto") {} +LootSettings::LootSettings() : + gameSettings_({ + GameSettings(GameType::tes4), + GameSettings(GameType::tes5), + GameSettings(GameType::fo3), + GameSettings(GameType::fonv), + GameSettings(GameType::fo4), + GameSettings(GameType::tes4, "Nehrim") + .SetName("Nehrim - At Fate's Edge") + .SetMaster("Nehrim.esm") + .SetRegistryKey("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Nehrim - At Fate's Edge_is1\\InstallLocation"), +}), +enableDebugLogging_(false), +updateMasterlist_(true), +game_("auto"), +language_(Language(Language::Code::english)), +lastGame_("auto") {} - void LootSettings::load(YAML::Node& settings) { - std::lock_guard guard(mutex); +void LootSettings::load(YAML::Node& settings) { + lock_guard guard(mutex_); - upgradeYaml(settings); + upgradeYaml(settings); - if (settings["enableDebugLogging"]) - enableDebugLogging = settings["enableDebugLogging"].as(); - if (settings["updateMasterlist"]) - updateMasterlist = settings["updateMasterlist"].as(); - if (settings["game"]) - game = settings["game"].as(); - if (settings["language"]) - language = Language(settings["language"].as()); - if (settings["lastGame"]) - lastGame = settings["lastGame"].as(); - if (settings["lastVersion"]) - lastVersion = settings["lastVersion"].as(); + if (settings["enableDebugLogging"]) + enableDebugLogging_ = settings["enableDebugLogging"].as(); + if (settings["updateMasterlist"]) + updateMasterlist_ = settings["updateMasterlist"].as(); + if (settings["game"]) + game_ = settings["game"].as(); + if (settings["language"]) + language_ = Language(settings["language"].as()); + if (settings["lastGame"]) + lastGame_ = settings["lastGame"].as(); + if (settings["lastVersion"]) + lastVersion_ = settings["lastVersion"].as(); - if (settings["window"] - && settings["window"]["top"] && settings["window"]["bottom"] - && settings["window"]["left"] && settings["window"]["right"]) { - windowPosition.top = settings["window"]["top"].as(); - windowPosition.bottom = settings["window"]["bottom"].as(); - windowPosition.left = settings["window"]["left"].as(); - windowPosition.right = settings["window"]["right"].as(); - } + if (settings["window"] + && settings["window"]["top"] && settings["window"]["bottom"] + && settings["window"]["left"] && settings["window"]["right"]) { + windowPosition_.top = settings["window"]["top"].as(); + windowPosition_.bottom = settings["window"]["bottom"].as(); + windowPosition_.left = settings["window"]["left"].as(); + windowPosition_.right = settings["window"]["right"].as(); + } - if (settings["games"]) { - gameSettings = settings["games"].as>(); + if (settings["games"]) { + gameSettings_ = settings["games"].as>(); - // If a base game isn't in the settings, add it. - if (find(begin(gameSettings), end(gameSettings), GameSettings(GameType::tes4)) == end(gameSettings)) - gameSettings.push_back(GameSettings(GameType::tes4)); + // If a base game isn't in the settings, add it. + if (find(begin(gameSettings_), end(gameSettings_), GameSettings(GameType::tes4)) == end(gameSettings_)) + gameSettings_.push_back(GameSettings(GameType::tes4)); - if (find(begin(gameSettings), end(gameSettings), GameSettings(GameType::tes5)) == end(gameSettings)) - gameSettings.push_back(GameSettings(GameType::tes5)); + if (find(begin(gameSettings_), end(gameSettings_), GameSettings(GameType::tes5)) == end(gameSettings_)) + gameSettings_.push_back(GameSettings(GameType::tes5)); - if (find(begin(gameSettings), end(gameSettings), GameSettings(GameType::fo3)) == end(gameSettings)) - gameSettings.push_back(GameSettings(GameType::fo3)); + if (find(begin(gameSettings_), end(gameSettings_), GameSettings(GameType::fo3)) == end(gameSettings_)) + gameSettings_.push_back(GameSettings(GameType::fo3)); - if (find(begin(gameSettings), end(gameSettings), GameSettings(GameType::fonv)) == end(gameSettings)) - gameSettings.push_back(GameSettings(GameType::fonv)); + if (find(begin(gameSettings_), end(gameSettings_), GameSettings(GameType::fonv)) == end(gameSettings_)) + gameSettings_.push_back(GameSettings(GameType::fonv)); - if (find(begin(gameSettings), end(gameSettings), GameSettings(GameType::fo4)) == end(gameSettings)) - gameSettings.push_back(GameSettings(GameType::fo4)); - } + if (find(begin(gameSettings_), end(gameSettings_), GameSettings(GameType::fo4)) == end(gameSettings_)) + gameSettings_.push_back(GameSettings(GameType::fo4)); + } - if (settings["filters"]) - filters = settings["filters"].as>(); - } + if (settings["filters"]) + filters_ = settings["filters"].as>(); +} + +void LootSettings::load(const boost::filesystem::path& file) { + boost::filesystem::ifstream in(file); + YAML::Node content = YAML::Load(in); + load(content); +} + +void LootSettings::save(const boost::filesystem::path& file) { + lock_guard guard(mutex_); - void LootSettings::load(const boost::filesystem::path& file) { - boost::filesystem::ifstream in(file); - YAML::Node content = YAML::Load(in); - load(content); - } + YAML::Emitter yout; + yout.SetIndent(2); + yout << toYaml(); - void LootSettings::save(const boost::filesystem::path& file) { - std::lock_guard guard(mutex); + boost::filesystem::ofstream out(file); + out << yout.c_str(); +} - YAML::Emitter yout; - yout.SetIndent(2); - yout << toYaml(); +bool LootSettings::isDebugLoggingEnabled() const { + lock_guard guard(mutex_); - boost::filesystem::ofstream out(file); - out << yout.c_str(); - } + return enableDebugLogging_; +} - bool LootSettings::isDebugLoggingEnabled() const { - std::lock_guard guard(mutex); +bool LootSettings::isWindowPositionStored() const { + lock_guard guard(mutex_); - return enableDebugLogging; - } + return windowPosition_.top != 0 || windowPosition_.bottom != 0 || windowPosition_.left != 0 || windowPosition_.right != 0; +} - bool LootSettings::isWindowPositionStored() const { - std::lock_guard guard(mutex); +std::string LootSettings::getGame() const { + lock_guard guard(mutex_); - return windowPosition.top != 0 || windowPosition.bottom != 0 || windowPosition.left != 0 || windowPosition.right != 0; - } + return game_; +} - std::string LootSettings::getGame() const { - std::lock_guard guard(mutex); +std::string LootSettings::getLastGame() const { + lock_guard guard(mutex_); - return game; - } + return lastGame_; +} - std::string LootSettings::getLastGame() const { - std::lock_guard guard(mutex); +std::string LootSettings::getLastVersion() const { + lock_guard guard(mutex_); - return lastGame; - } + return lastVersion_; +} - std::string LootSettings::getLastVersion() const { - std::lock_guard guard(mutex); +const Language& LootSettings::getLanguage() const { + lock_guard guard(mutex_); - return lastVersion; - } + return language_; +} - const Language& LootSettings::getLanguage() const { - std::lock_guard guard(mutex); +const LootSettings::WindowPosition& LootSettings::getWindowPosition() const { + lock_guard guard(mutex_); - return language; - } + return windowPosition_; +} - const LootSettings::WindowPosition& LootSettings::getWindowPosition() const { - std::lock_guard guard(mutex); +std::vector LootSettings::getGameSettings() const { + lock_guard guard(mutex_); - return windowPosition; - } + return gameSettings_; +} - std::vector LootSettings::getGameSettings() const { - std::lock_guard guard(mutex); +void LootSettings::storeLastGame(const std::string& lastGame) { + lock_guard guard(mutex_); - return gameSettings; - } + this->lastGame_ = lastGame; +} - void LootSettings::storeLastGame(const std::string& lastGame) { - std::lock_guard guard(mutex); +void LootSettings::storeWindowPosition(const WindowPosition& position) { + lock_guard guard(mutex_); - this->lastGame = lastGame; - } + windowPosition_ = position; +} - void LootSettings::storeWindowPosition(const WindowPosition& position) { - std::lock_guard guard(mutex); +void LootSettings::storeGameSettings(const std::vector& gameSettings) { + lock_guard guard(mutex_); - windowPosition = position; - } + this->gameSettings_ = gameSettings; +} - void LootSettings::storeGameSettings(const std::vector& gameSettings) { - std::lock_guard guard(mutex); +void LootSettings::storeFilterState(const std::string& filterId, bool enabled) { + lock_guard guard(mutex_); - this->gameSettings = gameSettings; - } + filters_[filterId] = enabled; +} - void LootSettings::storeFilterState(const std::string& filterId, bool enabled) { - std::lock_guard guard(mutex); +void LootSettings::updateLastVersion() { + lock_guard guard(mutex_); - filters[filterId] = enabled; - } + lastVersion_ = LootVersion::string(); +} - void LootSettings::updateLastVersion() { - std::lock_guard guard(mutex); +YAML::Node LootSettings::toYaml() const { + lock_guard guard(mutex_); - lastVersion = LootVersion::string(); - } + YAML::Node node; - YAML::Node LootSettings::toYaml() const { - std::lock_guard guard(mutex); + node["enableDebugLogging"] = enableDebugLogging_; + node["updateMasterlist"] = updateMasterlist_; + node["game"] = game_; + node["language"] = language_.GetLocale(); + node["lastGame"] = lastGame_; + node["lastVersion"] = lastVersion_; - YAML::Node node; + if (isWindowPositionStored()) { + node["window"]["top"] = windowPosition_.top; + node["window"]["bottom"] = windowPosition_.bottom; + node["window"]["left"] = windowPosition_.left; + node["window"]["right"] = windowPosition_.right; + } - node["enableDebugLogging"] = enableDebugLogging; - node["updateMasterlist"] = updateMasterlist; - node["game"] = game; - node["language"] = language.GetLocale(); - node["lastGame"] = lastGame; - node["lastVersion"] = lastVersion; + node["games"] = gameSettings_; - if (isWindowPositionStored()) { - node["window"]["top"] = windowPosition.top; - node["window"]["bottom"] = windowPosition.bottom; - node["window"]["left"] = windowPosition.left; - node["window"]["right"] = windowPosition.right; - } + if (!filters_.empty()) + node["filters"] = filters_; - node["games"] = gameSettings; + return node; +} - if (!filters.empty()) - node["filters"] = filters; +void LootSettings::upgradeYaml(YAML::Node& yaml) { + // Upgrade YAML settings' keys and values from those used in earlier + // versions of LOOT. - return node; - } + if (yaml["Debug Verbosity"] && !yaml["enableDebugLogging"]) + yaml["enableDebugLogging"] = yaml["Debug Verbosity"].as() > 0; - void LootSettings::upgradeYaml(YAML::Node& yaml) { - // Upgrade YAML settings' keys and values from those used in earlier - // versions of LOOT. + if (yaml["Update Masterlist"] && !yaml["updateMasterlist"]) + yaml["updateMasterlist"] = yaml["Update Masterlist"]; - if (yaml["Debug Verbosity"] && !yaml["enableDebugLogging"]) - yaml["enableDebugLogging"] = yaml["Debug Verbosity"].as() > 0; + if (yaml["Game"] && !yaml["game"]) + yaml["game"] = yaml["Game"]; - if (yaml["Update Masterlist"] && !yaml["updateMasterlist"]) - yaml["updateMasterlist"] = yaml["Update Masterlist"]; + if (yaml["Language"] && !yaml["language"]) + yaml["language"] = yaml["Language"]; - if (yaml["Game"] && !yaml["game"]) - yaml["game"] = yaml["Game"]; + if (yaml["Last Game"] && !yaml["lastGame"]) + yaml["lastGame"] = yaml["Last Game"]; - if (yaml["Language"] && !yaml["language"]) - yaml["language"] = yaml["Language"]; + if (yaml["Games"] && !yaml["games"]) { + yaml["games"] = yaml["Games"]; - if (yaml["Last Game"] && !yaml["lastGame"]) - yaml["lastGame"] = yaml["Last Game"]; + for (auto node : yaml["games"]) { + if (node["url"]) { + node["repo"] = node["url"]; + node["branch"] = "v0.8"; + } + } + } - if (yaml["Games"] && !yaml["games"]) { - yaml["games"] = yaml["Games"]; + if (yaml["games"]) { + const std::set oldDefaultBranches({ + "master", + "v0.7", + }); - for (auto node : yaml["games"]) { - if (node["url"]) { - node["repo"] = node["url"]; - node["branch"] = "v0.8"; - } - } - } + // Handle exception if YAML is invalid, eg. if an unrecognised + // game type is used (which can happen if downgrading from a + // later version of LOOT that supports more game types). + // However, can't remove elements from a sequence Node, so have to + // copy the valid elements into a new node then overwrite the + // original. + YAML::Node validGames; + for (auto node : yaml["games"]) { + try { + GameSettings settings(node.as()); - if (yaml["games"]) { - const set oldDefaultBranches({ - "master", - "v0.7", - }); - - // Handle exception if YAML is invalid, eg. if an unrecognised - // game type is used (which can happen if downgrading from a - // later version of LOOT that supports more game types). - // However, can't remove elements from a sequence Node, so have to - // copy the valid elements into a new node then overwrite the - // original. - YAML::Node validGames; - for (auto node : yaml["games"]) { - try { - GameSettings settings(node.as()); - - if (!yaml["Games"]) { - // Update existing default branch, if the default - // repositories are used. - if (settings.RepoURL() == GameSettings(settings.Type()).RepoURL() - && oldDefaultBranches.count(settings.RepoBranch()) == 1) { - settings.SetRepoBranch("v0.8"); - } - } - - validGames.push_back(settings); - } - catch (...) {} - } - yaml["games"] = validGames; + if (!yaml["Games"]) { + // Update existing default branch, if the default + // repositories are used. + if (settings.RepoURL() == GameSettings(settings.Type()).RepoURL() + && oldDefaultBranches.count(settings.RepoBranch()) == 1) { + settings.SetRepoBranch("v0.8"); + } } - if (yaml["filters"]) - yaml["filters"].remove("contentFilter"); + validGames.push_back(settings); + } catch (...) {} } + yaml["games"] = validGames; + } + + if (yaml["filters"]) + yaml["filters"].remove("contentFilter"); +} } diff --git a/src/backend/app/loot_settings.h b/src/backend/app/loot_settings.h index a0c4575d..7224af48 100644 --- a/src/backend/app/loot_settings.h +++ b/src/backend/app/loot_settings.h @@ -22,11 +22,8 @@ . */ -#ifndef LOOT_BACKEND_LOOT_SETTINGS -#define LOOT_BACKEND_LOOT_SETTINGS - -#include "backend/game/game_settings.h" -#include "backend/helpers/language.h" +#ifndef LOOT_BACKEND_APP_LOOT_SETTINGS +#define LOOT_BACKEND_APP_LOOT_SETTINGS #include #include @@ -36,55 +33,58 @@ #include #include +#include "backend/game/game_settings.h" +#include "backend/helpers/language.h" + namespace loot { - class LootSettings { - public: - struct WindowPosition { - WindowPosition(); +class LootSettings { +public: + struct WindowPosition { + WindowPosition(); - long top; - long bottom; - long left; - long right; - }; + long top; + long bottom; + long left; + long right; + }; - LootSettings(); + LootSettings(); - void load(YAML::Node& settings); - void load(const boost::filesystem::path& file); - void save(const boost::filesystem::path& file); + void load(YAML::Node& settings); + void load(const boost::filesystem::path& file); + void save(const boost::filesystem::path& file); - bool isDebugLoggingEnabled() const; - bool isWindowPositionStored() const; - std::string getGame() const; - std::string getLastGame() const; - std::string getLastVersion() const; - const Language& getLanguage() const; - const WindowPosition& getWindowPosition() const; - std::vector getGameSettings() const; + bool isDebugLoggingEnabled() const; + bool isWindowPositionStored() const; + std::string getGame() const; + std::string getLastGame() const; + std::string getLastVersion() const; + const Language& getLanguage() const; + const WindowPosition& getWindowPosition() const; + std::vector getGameSettings() const; - void storeLastGame(const std::string& lastGame); - void storeWindowPosition(const WindowPosition& position); - void storeGameSettings(const std::vector& gameSettings); - void storeFilterState(const std::string& filterId, bool enabled); - void updateLastVersion(); + void storeLastGame(const std::string& lastGame); + void storeWindowPosition(const WindowPosition& position); + void storeGameSettings(const std::vector& gameSettings); + void storeFilterState(const std::string& filterId, bool enabled); + void updateLastVersion(); - YAML::Node toYaml() const; - private: - bool enableDebugLogging; - bool updateMasterlist; - std::string game; - std::string lastGame; - std::string lastVersion; - Language language; - WindowPosition windowPosition; - std::vector gameSettings; - std::map filters; + YAML::Node toYaml() const; +private: + static void upgradeYaml(YAML::Node& yaml); - mutable std::recursive_mutex mutex; + bool enableDebugLogging_; + bool updateMasterlist_; + std::string game_; + std::string lastGame_; + std::string lastVersion_; + Language language_; + WindowPosition windowPosition_; + std::vector gameSettings_; + std::map filters_; - static void upgradeYaml(YAML::Node& yaml); - }; + mutable std::recursive_mutex mutex_; +}; } #endif diff --git a/src/backend/app/loot_state.cpp b/src/backend/app/loot_state.cpp index 69bc12f5..e050df3a 100644 --- a/src/backend/app/loot_state.cpp +++ b/src/backend/app/loot_state.cpp @@ -22,263 +22,262 @@ . */ -#include "loot_state.h" -#include "loot_paths.h" - -#include "backend/error.h" -#include "backend/app/loot_version.h" -#include "backend/helpers/helpers.h" -#include "backend/helpers/language.h" +#include "backend/app/loot_state.h" #include #include #include #include -#include #include -#include -#include #include +#include +#include +#include + +#include "backend/error.h" +#include "backend/app/loot_paths.h" +#include "backend/app/loot_version.h" +#include "backend/helpers/helpers.h" +#include "backend/helpers/language.h" #ifdef _WIN32 #include #endif -using namespace std; -using boost::locale::translate; using boost::format; +using boost::locale::translate; +using std::exception; +using std::locale; +using std::lock_guard; +using std::mutex; +using std::string; +using std::vector; namespace fs = boost::filesystem; namespace loot { - LootState::LootState() : unappliedChangeCounter(0), _currentGame(_games.end()) {} +LootState::LootState() : unappliedChangeCounter_(0), currentGame_(games_.end()) {} - void LootState::load(YAML::Node& settings) { - std::lock_guard guard(mutex); +void LootState::load(YAML::Node& settings) { + lock_guard guard(mutex_); - LootSettings::load(settings); + LootSettings::load(settings); - // Enable/disable debug logging in case it has changed. - boost::log::core::get()->set_logging_enabled(isDebugLoggingEnabled()); + // Enable/disable debug logging in case it has changed. + boost::log::core::get()->set_logging_enabled(isDebugLoggingEnabled()); - // Update existing games, add new games. - unordered_set newGameFolders; - BOOST_LOG_TRIVIAL(trace) << "Updating existing games and adding new games."; - for (const auto &game : getGameSettings()) { - auto pos = find(_games.begin(), _games.end(), game); + // Update existing games, add new games. + std::unordered_set newGameFolders; + BOOST_LOG_TRIVIAL(trace) << "Updating existing games and adding new games."; + for (const auto &game : getGameSettings()) { + auto pos = find(games_.begin(), games_.end(), game); - if (pos != _games.end()) { - pos->SetName(game.Name()) - .SetMaster(game.Master()) - .SetRepoURL(game.RepoURL()) - .SetRepoBranch(game.RepoBranch()) - .SetGamePath(game.GamePath()) - .SetRegistryKey(game.RegistryKey()); - } - else { - BOOST_LOG_TRIVIAL(trace) << "Adding new game entry for: " << game.FolderName(); - _games.push_back(game); - } - - newGameFolders.insert(game.FolderName()); - } - - // Remove deleted games. As the current game is stored using its index, - // removing an earlier game may invalidate it. - BOOST_LOG_TRIVIAL(trace) << "Removing deleted games."; - for (auto it = _games.begin(); it != _games.end();) { - if (newGameFolders.find(it->FolderName()) == newGameFolders.end()) { - BOOST_LOG_TRIVIAL(trace) << "Removing game: " << it->FolderName(); - it = _games.erase(it); - } - else - ++it; - } - - // Re-initialise the current game in case the game path setting was changed. - _currentGame->Init(true); - // Update game path in settings object. - storeGameSettings(ToGameSettings(_games)); + if (pos != games_.end()) { + pos->SetName(game.Name()) + .SetMaster(game.Master()) + .SetRepoURL(game.RepoURL()) + .SetRepoBranch(game.RepoBranch()) + .SetGamePath(game.GamePath()) + .SetRegistryKey(game.RegistryKey()); + } else { + BOOST_LOG_TRIVIAL(trace) << "Adding new game entry for: " << game.FolderName(); + games_.push_back(game); } - void LootState::Init(const std::string& cmdLineGame) { - // Do some preliminary locale / UTF-8 support setup here, in case the settings file reading requires it. - //Boost.Locale initialisation: Specify location of language dictionaries. - boost::locale::generator gen; - gen.add_messages_path(LootPaths::getL10nPath().string()); - gen.add_messages_domain("loot"); + newGameFolders.insert(game.FolderName()); + } - //Boost.Locale initialisation: Generate and imbue locales. - locale::global(gen(Language(Language::Code::english).GetLocale() + ".UTF-8")); - boost::filesystem::path::imbue(locale()); + // Remove deleted games. As the current game is stored using its index, + // removing an earlier game may invalidate it. + BOOST_LOG_TRIVIAL(trace) << "Removing deleted games."; + for (auto it = games_.begin(); it != games_.end();) { + if (newGameFolders.find(it->FolderName()) == newGameFolders.end()) { + BOOST_LOG_TRIVIAL(trace) << "Removing game: " << it->FolderName(); + it = games_.erase(it); + } else + ++it; + } - // Check if the LOOT local app data folder exists, and create it if not. - if (!fs::exists(LootPaths::getLootDataPath())) { - BOOST_LOG_TRIVIAL(info) << "Local app data LOOT folder doesn't exist, creating it."; - try { - fs::create_directory(LootPaths::getLootDataPath()); - } - catch (exception& e) { - _initErrors.push_back((format(translate("Error: Could not create LOOT settings file. %1%")) % e.what()).str()); - } - } - if (fs::exists(LootPaths::getSettingsPath())) { - try { - LootSettings::load(LootPaths::getSettingsPath()); - } - catch (exception& e) { - _initErrors.push_back((format(translate("Error: Settings parsing failed. %1%")) % e.what()).str()); - } - } + // Re-initialise the current game in case the game path setting was changed. + currentGame_->Init(true); + // Update game path in settings object. + storeGameSettings(toGameSettings(games_)); +} - //Set up logging. - boost::log::add_file_log( - boost::log::keywords::file_name = LootPaths::getLogPath().string().c_str(), - boost::log::keywords::auto_flush = true, - boost::log::keywords::format = ( - boost::log::expressions::stream - << "[" << boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%H:%M:%S") << "]" - << " [" << boost::log::trivial::severity << "]: " - << boost::log::expressions::smessage - ) - ); - boost::log::add_common_attributes(); - boost::log::core::get()->set_logging_enabled(isDebugLoggingEnabled()); +void LootState::init(const std::string& cmdLineGame) { + // Do some preliminary locale / UTF-8 support setup here, in case the settings file reading requires it. + //Boost.Locale initialisation: Specify location of language dictionaries. + boost::locale::generator gen; + gen.add_messages_path(LootPaths::getL10nPath().string()); + gen.add_messages_domain("loot"); - // Log some useful info. - BOOST_LOG_TRIVIAL(info) << "LOOT Version: " << LootVersion::major << "." << LootVersion::minor << "." << LootVersion::patch; - BOOST_LOG_TRIVIAL(info) << "LOOT Build Revision: " << LootVersion::revision; + //Boost.Locale initialisation: Generate and imbue locales. + locale::global(gen(Language(Language::Code::english).GetLocale() + ".UTF-8")); + boost::filesystem::path::imbue(locale()); + + // Check if the LOOT local app data folder exists, and create it if not. + if (!fs::exists(LootPaths::getLootDataPath())) { + BOOST_LOG_TRIVIAL(info) << "Local app data LOOT folder doesn't exist, creating it."; + try { + fs::create_directory(LootPaths::getLootDataPath()); + } catch (exception& e) { + initErrors_.push_back((format(translate("Error: Could not create LOOT settings file. %1%")) % e.what()).str()); + } + } + if (fs::exists(LootPaths::getSettingsPath())) { + try { + LootSettings::load(LootPaths::getSettingsPath()); + } catch (exception& e) { + initErrors_.push_back((format(translate("Error: Settings parsing failed. %1%")) % e.what()).str()); + } + } + + //Set up logging. + boost::log::add_file_log( + boost::log::keywords::file_name = LootPaths::getLogPath().string().c_str(), + boost::log::keywords::auto_flush = true, + boost::log::keywords::format = ( + boost::log::expressions::stream + << "[" << boost::log::expressions::format_date_time< boost::posix_time::ptime >("TimeStamp", "%H:%M:%S") << "]" + << " [" << boost::log::trivial::severity << "]: " + << boost::log::expressions::smessage + ) + ); + boost::log::add_common_attributes(); + boost::log::core::get()->set_logging_enabled(isDebugLoggingEnabled()); + + // Log some useful info. + BOOST_LOG_TRIVIAL(info) << "LOOT Version: " << LootVersion::major << "." << LootVersion::minor << "." << LootVersion::patch; + BOOST_LOG_TRIVIAL(info) << "LOOT Build Revision: " << LootVersion::revision; #ifdef _WIN32 // Check if LOOT is being run through Mod Organiser. - bool runFromMO = GetModuleHandle(ToWinWide("hook.dll").c_str()) != NULL; - if (runFromMO) { - BOOST_LOG_TRIVIAL(info) << "LOOT is being run through Mod Organiser."; - } + bool runFromMO = GetModuleHandle(ToWinWide("hook.dll").c_str()) != NULL; + if (runFromMO) { + BOOST_LOG_TRIVIAL(info) << "LOOT is being run through Mod Organiser."; + } #endif // The CEF debug log is appended to, not overwritten, so it gets really long. // Delete the current CEF debug log. - fs::remove(LootPaths::getLootDataPath() / "CEFDebugLog.txt"); + fs::remove(LootPaths::getLootDataPath() / "CEFDebugLog.txt"); - // Now that settings have been loaded, set the locale again to handle translations. - if (getLanguage().GetCode() != Language::Code::english) { - BOOST_LOG_TRIVIAL(debug) << "Initialising language settings."; - loot::Language lang(getLanguage()); - BOOST_LOG_TRIVIAL(debug) << "Selected language: " << lang.GetName(); + // Now that settings have been loaded, set the locale again to handle translations. + if (getLanguage().GetCode() != Language::Code::english) { + BOOST_LOG_TRIVIAL(debug) << "Initialising language settings."; + Language lang(getLanguage()); + BOOST_LOG_TRIVIAL(debug) << "Selected language: " << lang.GetName(); - //Boost.Locale initialisation: Generate and imbue locales. - locale::global(gen(lang.GetLocale() + ".UTF-8")); - boost::filesystem::path::imbue(locale()); - } + //Boost.Locale initialisation: Generate and imbue locales. + locale::global(gen(lang.GetLocale() + ".UTF-8")); + boost::filesystem::path::imbue(locale()); + } - // Detect games & select startup game - //----------------------------------- + // Detect games & select startup game + //----------------------------------- - //Detect installed games. - BOOST_LOG_TRIVIAL(debug) << "Detecting installed games."; - _games = ToGames(getGameSettings()); + //Detect installed games. + BOOST_LOG_TRIVIAL(debug) << "Detecting installed games."; + games_ = toGames(getGameSettings()); - try { - BOOST_LOG_TRIVIAL(debug) << "Selecting game."; - SelectGame(cmdLineGame); - BOOST_LOG_TRIVIAL(debug) << "Initialising game-specific settings."; - _currentGame->Init(true); - // Update game path in settings object. - storeGameSettings(ToGameSettings(_games)); - } - catch (loot::Error &e) { - if (e.code() == loot::Error::Code::no_game_detected) { - _initErrors.push_back(e.what()); - } - else { - BOOST_LOG_TRIVIAL(error) << "Game-specific settings could not be initialised. " << e.what(); - _initErrors.push_back((format(translate("Error: Game-specific settings could not be initialised. %1%")) % e.what()).str()); - } - } - BOOST_LOG_TRIVIAL(debug) << "Game selected is " << _currentGame->Name(); - } - - const std::vector& LootState::InitErrors() const { - return _initErrors; - } - - void LootState::save(const boost::filesystem::path & file) { - storeLastGame(_currentGame->FolderName()); - updateLastVersion(); - LootSettings::save(file); - } - - void LootState::ChangeGame(const std::string& newGameFolder) { - std::lock_guard guard(mutex); - - BOOST_LOG_TRIVIAL(debug) << "Changing current game to that with folder: " << newGameFolder; - _currentGame = find(_games.begin(), _games.end(), Game(GameType::autodetect, newGameFolder)); - _currentGame->Init(true); - - // Update game path in settings object. - storeGameSettings(ToGameSettings(_games)); - BOOST_LOG_TRIVIAL(debug) << "New game is " << _currentGame->Name(); - } - - Game& LootState::CurrentGame() { - std::lock_guard guard(mutex); - - return *_currentGame; - } - - std::vector LootState::InstalledGames() { - vector installedGames; - for (auto &game : _games) { - if (game.IsInstalled()) - installedGames.push_back(game.FolderName()); - } - return installedGames; - } - - bool LootState::hasUnappliedChanges() const { - return unappliedChangeCounter > 0; - } - - void LootState::incrementUnappliedChangeCounter() { - ++unappliedChangeCounter; - } - - void LootState::decrementUnappliedChangeCounter() { - if (unappliedChangeCounter > 0) - --unappliedChangeCounter; - } - - void LootState::SelectGame(std::string preferredGame) { - if (preferredGame.empty()) { - // Get preferred game from settings. - if (getGame() != "auto") - preferredGame = getGame(); - else if (getLastGame() != "auto") - preferredGame = getLastGame(); - } - - // Get iterator to preferred game. - _currentGame = find_if(begin(_games), end(_games), [&](Game& game) { - return (preferredGame.empty() || preferredGame == game.FolderName()) && game.IsInstalled(); - }); - // If the preferred game cannot be found, get the first installed game. - if (_currentGame == end(_games)) { - _currentGame = find_if(begin(_games), end(_games), [](Game& game) { - return game.IsInstalled(); - }); - } - // If no game can be selected, throw an exception. - if (_currentGame == end(_games)) { - BOOST_LOG_TRIVIAL(error) << "None of the supported games were detected."; - throw Error(Error::Code::no_game_detected, translate("None of the supported games were detected.")); - } - } - - std::list LootState::ToGames(const std::vector& settings) { - return list(settings.begin(), settings.end()); - } - - std::vector LootState::ToGameSettings(const std::list& games) { - return vector(games.begin(), games.end()); + try { + BOOST_LOG_TRIVIAL(debug) << "Selecting game."; + selectGame(cmdLineGame); + BOOST_LOG_TRIVIAL(debug) << "Initialising game-specific settings."; + currentGame_->Init(true); + // Update game path in settings object. + storeGameSettings(toGameSettings(games_)); + } catch (Error &e) { + if (e.code() == Error::Code::no_game_detected) { + initErrors_.push_back(e.what()); + } else { + BOOST_LOG_TRIVIAL(error) << "Game-specific settings could not be initialised. " << e.what(); + initErrors_.push_back((format(translate("Error: Game-specific settings could not be initialised. %1%")) % e.what()).str()); } + } + BOOST_LOG_TRIVIAL(debug) << "Game selected is " << currentGame_->Name(); +} + +const std::vector& LootState::getInitErrors() const { + return initErrors_; +} + +void LootState::save(const boost::filesystem::path & file) { + storeLastGame(currentGame_->FolderName()); + updateLastVersion(); + LootSettings::save(file); +} + +void LootState::changeGame(const std::string& newGameFolder) { + lock_guard guard(mutex_); + + BOOST_LOG_TRIVIAL(debug) << "Changing current game to that with folder: " << newGameFolder; + currentGame_ = find(games_.begin(), games_.end(), Game(GameType::autodetect, newGameFolder)); + currentGame_->Init(true); + + // Update game path in settings object. + storeGameSettings(toGameSettings(games_)); + BOOST_LOG_TRIVIAL(debug) << "New game is " << currentGame_->Name(); +} + +Game& LootState::getCurrentGame() { + lock_guard guard(mutex_); + + return *currentGame_; +} + +std::vector LootState::getInstalledGames() { + vector installedGames; + for (auto &game : games_) { + if (game.IsInstalled()) + installedGames.push_back(game.FolderName()); + } + return installedGames; +} + +bool LootState::hasUnappliedChanges() const { + return unappliedChangeCounter_ > 0; +} + +void LootState::incrementUnappliedChangeCounter() { + ++unappliedChangeCounter_; +} + +void LootState::decrementUnappliedChangeCounter() { + if (unappliedChangeCounter_ > 0) + --unappliedChangeCounter_; +} + +void LootState::selectGame(std::string preferredGame) { + if (preferredGame.empty()) { + // Get preferred game from settings. + if (getGame() != "auto") + preferredGame = getGame(); + else if (getLastGame() != "auto") + preferredGame = getLastGame(); + } + + // Get iterator to preferred game. + currentGame_ = find_if(begin(games_), end(games_), [&](Game& game) { + return (preferredGame.empty() || preferredGame == game.FolderName()) && game.IsInstalled(); + }); + // If the preferred game cannot be found, get the first installed game. + if (currentGame_ == end(games_)) { + currentGame_ = find_if(begin(games_), end(games_), [](Game& game) { + return game.IsInstalled(); + }); + } + // If no game can be selected, throw an exception. + if (currentGame_ == end(games_)) { + BOOST_LOG_TRIVIAL(error) << "None of the supported games were detected."; + throw Error(Error::Code::no_game_detected, translate("None of the supported games were detected.")); + } +} + +std::list LootState::toGames(const std::vector& settings) { + return std::list(settings.begin(), settings.end()); +} + +std::vector LootState::toGameSettings(const std::list& games) { + return vector(games.begin(), games.end()); +} } diff --git a/src/backend/app/loot_state.h b/src/backend/app/loot_state.h index 73d8a9a5..d6b9e862 100644 --- a/src/backend/app/loot_state.h +++ b/src/backend/app/loot_state.h @@ -22,49 +22,49 @@ . */ -#ifndef LOOT_BACKEND_LOOT_STATE -#define LOOT_BACKEND_LOOT_STATE +#ifndef LOOT_BACKEND_APP_LOOT_STATE +#define LOOT_BACKEND_APP_LOOT_STATE -#include "loot_settings.h" +#include "backend/app/loot_settings.h" #include "backend/game/game.h" namespace loot { - class LootState : public LootSettings { - public: - LootState(); +class LootState : public LootSettings { +public: + LootState(); - void load(YAML::Node& settings); - void Init(const std::string& cmdLineGame); - const std::vector& InitErrors() const; + void load(YAML::Node& settings); + void init(const std::string& cmdLineGame); + const std::vector& getInitErrors() const; - void save(const boost::filesystem::path& file); + void save(const boost::filesystem::path& file); - Game& CurrentGame(); - void ChangeGame(const std::string& newGameFolder); + Game& getCurrentGame(); + void changeGame(const std::string& newGameFolder); - // Get the folder names of the installed games. - std::vector InstalledGames(); + // Get the folder names of the installed games. + std::vector getInstalledGames(); - bool hasUnappliedChanges() const; - void incrementUnappliedChangeCounter(); - void decrementUnappliedChangeCounter(); - private: - std::list _games; - std::list::iterator _currentGame; - std::vector _initErrors; + bool hasUnappliedChanges() const; + void incrementUnappliedChangeCounter(); + void decrementUnappliedChangeCounter(); +private: + // Select initial game. + void selectGame(std::string cmdLineGame); - // Used to check if LOOT has unaccepted sorting or metadata changes on quit. - size_t unappliedChangeCounter; + static std::list toGames(const std::vector& settings); + static std::vector toGameSettings(const std::list& games); - // Select initial game. - void SelectGame(std::string cmdLineGame); + std::list games_; + std::list::iterator currentGame_; + std::vector initErrors_; - static std::list ToGames(const std::vector& settings); - static std::vector ToGameSettings(const std::list& games); + // Used to check if LOOT has unaccepted sorting or metadata changes on quit. + size_t unappliedChangeCounter_; - // Mutex used to protect access to member variables. - std::mutex mutex; - }; + // Mutex used to protect access to member variables. + std::mutex mutex_; +}; } #endif diff --git a/src/backend/app/loot_version.h b/src/backend/app/loot_version.h index 0dab2f3c..b4ae9799 100644 --- a/src/backend/app/loot_version.h +++ b/src/backend/app/loot_version.h @@ -22,21 +22,21 @@ along with LOOT. If not, see . */ -#ifndef LOOT_BACKEND_LOOT_VERSION -#define LOOT_BACKEND_LOOT_VERSION +#ifndef LOOT_BACKEND_APP_LOOT_VERSION +#define LOOT_BACKEND_APP_LOOT_VERSION #include namespace loot { - class LootVersion { - public: - static const unsigned int major; - static const unsigned int minor; - static const unsigned int patch; - static const std::string revision; +class LootVersion { +public: + static const unsigned int major; + static const unsigned int minor; + static const unsigned int patch; + static const std::string revision; - static std::string string(); - }; + static std::string string(); +}; } #endif diff --git a/src/backend/error.h b/src/backend/error.h index 73d14d6d..ab114985 100644 --- a/src/backend/error.h +++ b/src/backend/error.h @@ -22,52 +22,52 @@ . */ -#ifndef __LOOT_ERROR__ -#define __LOOT_ERROR__ +#ifndef LOOT_BACKEND_ERROR +#define LOOT_BACKEND_ERROR #include #include namespace loot { - class Error : public std::exception { - public: - enum struct Code : unsigned int { - // These must not be changed for API stability. - ok = 0, - liblo_error = 1, - path_write_fail = 2, - path_read_fail = 3, - condition_eval_fail = 4, - regex_eval_fail = 5, - no_mem = 6, - invalid_args = 7, - no_tag_map = 8, - path_not_found = 9, - no_game_detected = 10, - //11 was subversion_error, and was removed along with svn support. - git_error = 12, - windows_error = 13, - sorting_error = 14, - }; +class Error : public std::exception { +public: + enum struct Code : unsigned int { + // These must not be changed for API stability. + ok = 0, + liblo_error = 1, + path_write_fail = 2, + path_read_fail = 3, + condition_eval_fail = 4, + regex_eval_fail = 5, + no_mem = 6, + invalid_args = 7, + no_tag_map = 8, + path_not_found = 9, + no_game_detected = 10, + //11 was subversion_error, and was removed along with svn support. + git_error = 12, + windows_error = 13, + sorting_error = 14, + }; - Error(const Code code_arg, const std::string& what_arg) : _code(code_arg), _what(what_arg) {} - ~Error() throw() {}; + Error(const Code code_arg, const std::string& what_arg) : code_(code_arg), what_(what_arg) {} + ~Error() throw() {}; - Code code() const { return _code; } + Code code() const { return code_; } - unsigned int codeAsUnsignedInt() const { - return asUnsignedInt(_code); - } + unsigned int codeAsUnsignedInt() const { + return asUnsignedInt(code_); + } - const char * what() const throw() { return _what.c_str(); } + const char * what() const throw() { return what_.c_str(); } - static unsigned int asUnsignedInt(Code code) { - return static_cast(code); - } - private: - Code _code; - std::string _what; - }; + static unsigned int asUnsignedInt(Code code) { + return static_cast(code); + } +private: + Code code_; + std::string what_; +}; } #endif diff --git a/src/backend/game/game.cpp b/src/backend/game/game.cpp index ffc7ac28..e73c72f1 100644 --- a/src/backend/game/game.cpp +++ b/src/backend/game/game.cpp @@ -22,10 +22,7 @@ . */ -#include "game.h" -#include "../app/loot_paths.h" -#include "../helpers/helpers.h" -#include "../error.h" +#include "backend/game/game.h" #include #include @@ -34,165 +31,169 @@ #include #include -using namespace std; +#include "backend/app/loot_paths.h" +#include "backend/error.h" +#include "backend/helpers/helpers.h" + +using boost::locale::translate; +using std::list; +using std::string; +using std::thread; +using std::vector; namespace fs = boost::filesystem; -namespace lc = boost::locale; namespace loot { - Game::Game() : _pluginsFullyLoaded(false) {} +Game::Game() : pluginsFullyLoaded_(false) {} - Game::Game(const GameSettings& gameSettings) : GameSettings(gameSettings), _pluginsFullyLoaded(false) { - this->SetName(gameSettings.Name()) - .SetMaster(gameSettings.Master()) - .SetRepoURL(gameSettings.RepoURL()) - .SetRepoBranch(gameSettings.RepoBranch()) - .SetGamePath(gameSettings.GamePath()) - .SetRegistryKey(gameSettings.RegistryKey()); +Game::Game(const GameSettings& gameSettings) : GameSettings(gameSettings), pluginsFullyLoaded_(false) { + this->SetName(gameSettings.Name()) + .SetMaster(gameSettings.Master()) + .SetRepoURL(gameSettings.RepoURL()) + .SetRepoBranch(gameSettings.RepoBranch()) + .SetGamePath(gameSettings.GamePath()) + .SetRegistryKey(gameSettings.RegistryKey()); +} + +Game::Game(const GameType gameType, const std::string& folder) : GameSettings(gameType, folder), pluginsFullyLoaded_(false) {} + +void Game::Init(bool createFolder, const boost::filesystem::path& gameLocalAppData) { + if (Type() != GameType::tes4 && Type() != GameType::tes5 && Type() != GameType::fo3 && Type() != GameType::fonv && Type() != GameType::fo4) { + throw Error(Error::Code::invalid_args, translate("Invalid game ID supplied.").str()); + } + + BOOST_LOG_TRIVIAL(info) << "Initialising filesystem-related data for game: " << Name(); + + if (!this->IsInstalled()) { + BOOST_LOG_TRIVIAL(error) << "Game path could not be detected."; + throw Error(Error::Code::path_not_found, translate("Game path could not be detected.").str()); + } + + if (createFolder) { + //Make sure that the LOOT game path exists. + try { + if (!fs::exists(LootPaths::getLootDataPath() / FolderName())) + fs::create_directories(LootPaths::getLootDataPath() / FolderName()); + } catch (fs::filesystem_error& e) { + BOOST_LOG_TRIVIAL(error) << "Could not create LOOT folder for game. Details: " << e.what(); + throw Error(Error::Code::path_write_fail, translate("Could not create LOOT folder for game. Details:").str() + " " + e.what()); } + } + + LoadOrderHandler::Init(*this, gameLocalAppData); +} + +void Game::RedatePlugins() { + if (Type() != GameType::tes5) + return; + + list loadorder = GetLoadOrder(); + if (!loadorder.empty()) { + time_t lastTime = 0; + for (const auto &pluginName : loadorder) { + fs::path filepath = DataPath() / pluginName; + if (!fs::exists(filepath)) { + if (fs::exists(filepath.string() + ".ghost")) + filepath += ".ghost"; + else + continue; + } + + time_t thisTime = fs::last_write_time(filepath); + BOOST_LOG_TRIVIAL(info) << "Current timestamp for \"" << filepath.filename().string() << "\": " << thisTime; + if (thisTime >= lastTime) { + lastTime = thisTime; + BOOST_LOG_TRIVIAL(trace) << "No need to redate \"" << filepath.filename().string() << "\"."; + } else { + lastTime += 60; + fs::last_write_time(filepath, lastTime); //Space timestamps by a minute. + BOOST_LOG_TRIVIAL(info) << "Redated \"" << filepath.filename().string() << "\" to: " << lastTime; + } + } + } +} - Game::Game(const GameType gameType, const std::string& folder) : GameSettings(gameType, folder), _pluginsFullyLoaded(false) {} - - void Game::Init(bool createFolder, const boost::filesystem::path& gameLocalAppData) { - if (Type() != GameType::tes4 && Type() != GameType::tes5 && Type() != GameType::fo3 && Type() != GameType::fonv && Type() != GameType::fo4) { - throw Error(Error::Code::invalid_args, lc::translate("Invalid game ID supplied.").str()); - } - - BOOST_LOG_TRIVIAL(info) << "Initialising filesystem-related data for game: " << Name(); - - if (!this->IsInstalled()) { - BOOST_LOG_TRIVIAL(error) << "Game path could not be detected."; - throw Error(Error::Code::path_not_found, lc::translate("Game path could not be detected.").str()); - } - - if (createFolder) { - //Make sure that the LOOT game path exists. - try { - if (!fs::exists(LootPaths::getLootDataPath() / FolderName())) - fs::create_directories(LootPaths::getLootDataPath() / FolderName()); - } - catch (fs::filesystem_error& e) { - BOOST_LOG_TRIVIAL(error) << "Could not create LOOT folder for game. Details: " << e.what(); - throw Error(Error::Code::path_write_fail, lc::translate("Could not create LOOT folder for game. Details:").str() + " " + e.what()); - } - } - - LoadOrderHandler::Init(*this, gameLocalAppData); - } +void Game::LoadPlugins(bool headersOnly) { + uintmax_t meanFileSize = 0; + std::multimap sizeMap; - void Game::RedatePlugins() { - if (Type() != GameType::tes5) - return; - - list loadorder = GetLoadOrder(); - if (!loadorder.empty()) { - time_t lastTime = 0; - for (const auto &pluginName : loadorder) { - fs::path filepath = DataPath() / pluginName; - if (!fs::exists(filepath)) { - if (fs::exists(filepath.string() + ".ghost")) - filepath += ".ghost"; - else - continue; - } - - time_t thisTime = fs::last_write_time(filepath); - BOOST_LOG_TRIVIAL(info) << "Current timestamp for \"" << filepath.filename().string() << "\": " << thisTime; - if (thisTime >= lastTime) { - lastTime = thisTime; - BOOST_LOG_TRIVIAL(trace) << "No need to redate \"" << filepath.filename().string() << "\"."; - } - else { - lastTime += 60; - fs::last_write_time(filepath, lastTime); //Space timestamps by a minute. - BOOST_LOG_TRIVIAL(info) << "Redated \"" << filepath.filename().string() << "\" to: " << lastTime; - } - } - } - } + // First find out how many plugins there are, and their sizes. + BOOST_LOG_TRIVIAL(trace) << "Scanning for plugins in " << this->DataPath(); + for (fs::directory_iterator it(this->DataPath()); it != fs::directory_iterator(); ++it) { + if (fs::is_regular_file(it->status()) && Plugin::IsValid(it->path().filename().string(), *this)) { + string name = it->path().filename().string(); + BOOST_LOG_TRIVIAL(info) << "Found plugin: " << name; - void Game::LoadPlugins(bool headersOnly) { - uintmax_t meanFileSize = 0; - multimap sizeMap; - - // First find out how many plugins there are, and their sizes. - BOOST_LOG_TRIVIAL(trace) << "Scanning for plugins in " << this->DataPath(); - for (fs::directory_iterator it(this->DataPath()); it != fs::directory_iterator(); ++it) { - if (fs::is_regular_file(it->status()) && Plugin::IsValid(it->path().filename().string(), *this)) { - string name = it->path().filename().string(); - BOOST_LOG_TRIVIAL(info) << "Found plugin: " << name; - - // Trim .ghost extension if present. - if (boost::iends_with(name, ".ghost")) - name = name.substr(0, name.length() - 6); - - uintmax_t fileSize = fs::file_size(it->path()); - meanFileSize += fileSize; - - sizeMap.emplace(fileSize, name); - } - } - meanFileSize /= sizeMap.size(); //Rounding error, but not important. - - // Get the number of threads to use. - // hardware_concurrency() may be zero, if so then use only one thread. - size_t threadsToUse = std::min((size_t)thread::hardware_concurrency(), sizeMap.size()); - threadsToUse = std::max(threadsToUse, (size_t)1); - - // Divide the plugins up by thread. - unsigned int pluginsPerThread = ceil((double)sizeMap.size() / threadsToUse); - vector> pluginGroups(threadsToUse); - BOOST_LOG_TRIVIAL(info) << "Loading " << sizeMap.size() << " plugins using " << threadsToUse << " threads, with up to " << pluginsPerThread << " plugins per thread."; - - // The plugins should be split between the threads so that the data - // load is as evenly spread as possible. - size_t currentGroup = 0; - for (const auto& plugin : sizeMap) { - if (currentGroup == threadsToUse) - currentGroup = 0; - BOOST_LOG_TRIVIAL(trace) << "Adding plugin " << plugin.second << " to loading group " << currentGroup; - pluginGroups[currentGroup].push_back(plugin.second); - ++currentGroup; - } - - // Clear the existing plugin cache. - ClearCachedPlugins(); - - // Load the plugins. - BOOST_LOG_TRIVIAL(trace) << "Starting plugin loading."; - vector threads; - while (threads.size() < threadsToUse) { - vector& pluginGroup = pluginGroups[threads.size()]; - threads.push_back(thread([&]() { - for (auto pluginName : pluginGroup) { - BOOST_LOG_TRIVIAL(trace) << "Loading " << pluginName; - if (boost::iequals(pluginName, Master())) - AddPlugin(Plugin(*this, pluginName, true)); - else - AddPlugin(Plugin(*this, pluginName, headersOnly)); - } - })); - } - - // Join all threads. - for (auto& thread : threads) { - if (thread.joinable()) - thread.join(); - } - - _pluginsFullyLoaded = !headersOnly; - } + // Trim .ghost extension if present. + if (boost::iends_with(name, ".ghost")) + name = name.substr(0, name.length() - 6); - bool Game::ArePluginsFullyLoaded() const { - return _pluginsFullyLoaded; - } + uintmax_t fileSize = fs::file_size(it->path()); + meanFileSize += fileSize; - bool Game::IsPluginActive(const std::string& pluginName) const { - try { - return GetPlugin(pluginName).IsActive(); - } - catch (...) { - return LoadOrderHandler::IsPluginActive(pluginName); - } + sizeMap.emplace(fileSize, name); } + } + meanFileSize /= sizeMap.size(); //Rounding error, but not important. + + // Get the number of threads to use. + // hardware_concurrency() may be zero, if so then use only one thread. + size_t threadsToUse = std::min((size_t)thread::hardware_concurrency(), sizeMap.size()); + threadsToUse = std::max(threadsToUse, (size_t)1); + + // Divide the plugins up by thread. + unsigned int pluginsPerThread = ceil((double)sizeMap.size() / threadsToUse); + vector> pluginGroups(threadsToUse); + BOOST_LOG_TRIVIAL(info) << "Loading " << sizeMap.size() << " plugins using " << threadsToUse << " threads, with up to " << pluginsPerThread << " plugins per thread."; + + // The plugins should be split between the threads so that the data + // load is as evenly spread as possible. + size_t currentGroup = 0; + for (const auto& plugin : sizeMap) { + if (currentGroup == threadsToUse) + currentGroup = 0; + BOOST_LOG_TRIVIAL(trace) << "Adding plugin " << plugin.second << " to loading group " << currentGroup; + pluginGroups[currentGroup].push_back(plugin.second); + ++currentGroup; + } + + // Clear the existing plugin cache. + ClearCachedPlugins(); + + // Load the plugins. + BOOST_LOG_TRIVIAL(trace) << "Starting plugin loading."; + vector threads; + while (threads.size() < threadsToUse) { + vector& pluginGroup = pluginGroups[threads.size()]; + threads.push_back(thread([&]() { + for (auto pluginName : pluginGroup) { + BOOST_LOG_TRIVIAL(trace) << "Loading " << pluginName; + if (boost::iequals(pluginName, Master())) + AddPlugin(Plugin(*this, pluginName, true)); + else + AddPlugin(Plugin(*this, pluginName, headersOnly)); + } + })); + } + + // Join all threads. + for (auto& thread : threads) { + if (thread.joinable()) + thread.join(); + } + + pluginsFullyLoaded_ = !headersOnly; +} + +bool Game::ArePluginsFullyLoaded() const { + return pluginsFullyLoaded_; +} + +bool Game::IsPluginActive(const std::string& pluginName) const { + try { + return GetPlugin(pluginName).IsActive(); + } catch (...) { + return LoadOrderHandler::IsPluginActive(pluginName); + } +} } diff --git a/src/backend/game/game.h b/src/backend/game/game.h index ed59ab6b..1f73b662 100644 --- a/src/backend/game/game.h +++ b/src/backend/game/game.h @@ -22,38 +22,38 @@ . */ -#ifndef __LOOT_GAME__ -#define __LOOT_GAME__ - -#include "game_cache.h" -#include "game_settings.h" -#include "load_order_handler.h" +#ifndef LOOT_BACKEND_GAME_GAME +#define LOOT_BACKEND_GAME_GAME #include #include +#include "backend/game/game_cache.h" +#include "backend/game/game_settings.h" +#include "backend/game/load_order_handler.h" + namespace loot { - class Game : public GameSettings, public LoadOrderHandler, public GameCache { - public: - //Game functions. - Game(); //Sets game to GameType::autodetect, with all other vars being empty. - Game(const GameSettings& gameSettings); - Game(const GameType gameType, const std::string& lootFolder = ""); +class Game : public GameSettings, public LoadOrderHandler, public GameCache { +public: + //Game functions. + Game(); //Sets game to GameType::autodetect, with all other vars being empty. + Game(const GameSettings& gameSettings); + Game(const GameType gameType, const std::string& lootFolder = ""); - void Init(bool createFolder, const boost::filesystem::path& gameLocalAppData = ""); + void Init(bool createFolder, const boost::filesystem::path& gameLocalAppData = ""); - void RedatePlugins(); //Change timestamps to match load order (Skyrim only). + void RedatePlugins(); //Change timestamps to match load order (Skyrim only). - void LoadPlugins(bool headersOnly); //Loads all installed plugins. - bool ArePluginsFullyLoaded() const; // Checks if the game's plugins have already been loaded. + void LoadPlugins(bool headersOnly); //Loads all installed plugins. + bool ArePluginsFullyLoaded() const; // Checks if the game's plugins have already been loaded. - // Check if the plugin is active by using the cached value if - // available, and otherwise asking the load order handler. - bool IsPluginActive(const std::string& pluginName) const; - private: - bool _pluginsFullyLoaded; - }; + // Check if the plugin is active by using the cached value if + // available, and otherwise asking the load order handler. + bool IsPluginActive(const std::string& pluginName) const; +private: + bool pluginsFullyLoaded_; +}; } #endif diff --git a/src/backend/game/game_cache.cpp b/src/backend/game/game_cache.cpp index a88224ab..adf7737b 100644 --- a/src/backend/game/game_cache.cpp +++ b/src/backend/game/game_cache.cpp @@ -22,9 +22,7 @@ . */ -#include "game_cache.h" -#include "../helpers/helpers.h" -#include "../error.h" +#include "backend/game/game_cache.h" #include @@ -32,118 +30,122 @@ #include #include -using namespace std; +#include "backend/error.h" +#include "backend/helpers/helpers.h" -namespace fs = boost::filesystem; -namespace lc = boost::locale; +using boost::locale::to_lower; +using std::lock_guard; +using std::mutex; +using std::pair; +using std::string; namespace loot { - GameCache::GameCache() : isLoadOrderSorted(false) {} +GameCache::GameCache() : isLoadOrderSorted_(false) {} - GameCache::GameCache(const GameCache& cache) : - masterlist(cache.masterlist), - userlist(cache.userlist), - conditionCache(cache.conditionCache), - plugins(cache.plugins), - messages(cache.messages), - isLoadOrderSorted(cache.isLoadOrderSorted) {} +GameCache::GameCache(const GameCache& cache) : + masterlist_(cache.masterlist_), + userlist_(cache.userlist_), + conditions_(cache.conditions_), + plugins_(cache.plugins_), + messages_(cache.messages_), + isLoadOrderSorted_(cache.isLoadOrderSorted_) {} - GameCache& GameCache::operator=(const GameCache& cache) { - if (&cache != this) { - masterlist = cache.masterlist; - userlist = cache.userlist; - conditionCache = cache.conditionCache; - plugins = cache.plugins; - messages = cache.messages; - isLoadOrderSorted = cache.isLoadOrderSorted; - } +GameCache& GameCache::operator=(const GameCache& cache) { + if (&cache != this) { + masterlist_ = cache.masterlist_; + userlist_ = cache.userlist_; + conditions_ = cache.conditions_; + plugins_ = cache.plugins_; + messages_ = cache.messages_; + isLoadOrderSorted_ = cache.isLoadOrderSorted_; + } - return *this; - } - - Masterlist & GameCache::GetMasterlist() { - return masterlist; - } - - MetadataList & GameCache::GetUserlist() { - return userlist; - } - - void GameCache::CacheCondition(const std::string& condition, bool result) { - std::lock_guard guard(mutex); - conditionCache.insert(pair(boost::locale::to_lower(condition), result)); - } - - std::pair GameCache::GetCachedCondition(const std::string& condition) const { - std::lock_guard guard(mutex); - - auto it = conditionCache.find(boost::locale::to_lower(condition)); - - if (it != conditionCache.end()) - return std::pair(it->second, true); - else - return std::pair(false, false); - } - - std::set GameCache::GetPlugins() const { - std::set output; - std::transform(begin(plugins), - end(plugins), - inserter>(output, begin(output)), - [](const pair& pluginPair) { - return pluginPair.second; - }); - return output; - } - - const Plugin& GameCache::GetPlugin(const std::string & pluginName) const { - auto it = plugins.find(boost::locale::to_lower(pluginName)); - if (it != end(plugins)) - return it->second; - - throw Error(Error::Code::invalid_args, "No plugin \"" + pluginName + "\" exists."); - } - - void GameCache::AddPlugin(const Plugin&& plugin) { - std::lock_guard lock(mutex); - - auto pair = plugins.emplace(boost::locale::to_lower(plugin.Name()), plugin); - if (!pair.second) - pair.first->second = plugin; - } - - std::vector GameCache::GetMessages() const { - vector output(messages); - if (!isLoadOrderSorted) - output.push_back(Message(Message::Type::warn, "You have not sorted your load order this session.")); - - return output; - } - - void GameCache::AppendMessage(const Message& message) { - std::lock_guard guard(mutex); - - messages.push_back(message); - } - - void GameCache::SetLoadOrderSorted(bool isLoadOrderSorted) { - this->isLoadOrderSorted = isLoadOrderSorted; - } - - void GameCache::ClearCachedConditions() { - std::lock_guard guard(mutex); - - conditionCache.clear(); - } - - void GameCache::ClearCachedPlugins() { - std::lock_guard guard(mutex); - - plugins.clear(); - } - void GameCache::ClearMessages() { - std::lock_guard guard(mutex); - - messages.clear(); - } + return *this; +} + +Masterlist & GameCache::GetMasterlist() { + return masterlist_; +} + +MetadataList & GameCache::GetUserlist() { + return userlist_; +} + +void GameCache::CacheCondition(const std::string& condition, bool result) { + lock_guard guard(mutex_); + conditions_.insert(pair(to_lower(condition), result)); +} + +std::pair GameCache::GetCachedCondition(const std::string& condition) const { + lock_guard guard(mutex_); + + auto it = conditions_.find(to_lower(condition)); + + if (it != conditions_.end()) + return pair(it->second, true); + else + return pair(false, false); +} + +std::set GameCache::GetPlugins() const { + std::set output; + std::transform(begin(plugins_), + end(plugins_), + std::inserter>(output, begin(output)), + [](const pair& pluginPair) { + return pluginPair.second; + }); + return output; +} + +const Plugin& GameCache::GetPlugin(const std::string & pluginName) const { + auto it = plugins_.find(to_lower(pluginName)); + if (it != end(plugins_)) + return it->second; + + throw Error(Error::Code::invalid_args, "No plugin \"" + pluginName + "\" exists."); +} + +void GameCache::AddPlugin(const Plugin&& plugin) { + lock_guard lock(mutex_); + + auto pair = plugins_.emplace(to_lower(plugin.Name()), plugin); + if (!pair.second) + pair.first->second = plugin; +} + +std::vector GameCache::GetMessages() const { + std::vector output(messages_); + if (!isLoadOrderSorted_) + output.push_back(Message(Message::Type::warn, "You have not sorted your load order this session.")); + + return output; +} + +void GameCache::AppendMessage(const Message& message) { + lock_guard guard(mutex_); + + messages_.push_back(message); +} + +void GameCache::SetLoadOrderSorted(bool isLoadOrderSorted) { + this->isLoadOrderSorted_ = isLoadOrderSorted; +} + +void GameCache::ClearCachedConditions() { + lock_guard guard(mutex_); + + conditions_.clear(); +} + +void GameCache::ClearCachedPlugins() { + lock_guard guard(mutex_); + + plugins_.clear(); +} +void GameCache::ClearMessages() { + lock_guard guard(mutex_); + + messages_.clear(); +} } diff --git a/src/backend/game/game_cache.h b/src/backend/game/game_cache.h index 36911366..7f7f0ef8 100644 --- a/src/backend/game/game_cache.h +++ b/src/backend/game/game_cache.h @@ -22,54 +22,54 @@ . */ -#ifndef __LOOT_GAME_CRC_CACHE__ -#define __LOOT_GAME_CRC_CACHE__ +#ifndef LOOT_BACKEND_GAME_GAME_CACHE +#define LOOT_BACKEND_GAME_GAME_CACHE -#include "../metadata_list.h" -#include "../masterlist.h" -#include "../plugin/plugin.h" - -#include #include +#include #include +#include "backend/masterlist.h" +#include "backend/metadata_list.h" +#include "backend/plugin/plugin.h" + namespace loot { - class GameCache { - public: - GameCache(); - GameCache(const GameCache& cache); +class GameCache { +public: + GameCache(); + GameCache(const GameCache& cache); - GameCache& operator=(const GameCache& cache); + GameCache& operator=(const GameCache& cache); - Masterlist& GetMasterlist(); - MetadataList& GetUserlist(); + Masterlist& GetMasterlist(); + MetadataList& GetUserlist(); - // Returns false for second bool if no cached condition. - std::pair GetCachedCondition(const std::string& condition) const; - void CacheCondition(const std::string& condition, bool result); + // Returns false for second bool if no cached condition. + std::pair GetCachedCondition(const std::string& condition) const; + void CacheCondition(const std::string& condition, bool result); - std::set GetPlugins() const; - const Plugin& GetPlugin(const std::string& pluginName) const; - void AddPlugin(const Plugin&& plugin); + std::set GetPlugins() const; + const Plugin& GetPlugin(const std::string& pluginName) const; + void AddPlugin(const Plugin&& plugin); - std::vector GetMessages() const; - void AppendMessage(const Message& message); + std::vector GetMessages() const; + void AppendMessage(const Message& message); - void SetLoadOrderSorted(bool isLoadOrderSorted); + void SetLoadOrderSorted(bool isLoadOrderSorted); - void ClearCachedConditions(); - void ClearCachedPlugins(); - void ClearMessages(); - private: - Masterlist masterlist; - MetadataList userlist; - std::unordered_map conditionCache; - std::unordered_map plugins; - std::vector messages; - bool isLoadOrderSorted; + void ClearCachedConditions(); + void ClearCachedPlugins(); + void ClearMessages(); +private: + Masterlist masterlist_; + MetadataList userlist_; + std::unordered_map conditions_; + std::unordered_map plugins_; + std::vector messages_; + bool isLoadOrderSorted_; - mutable std::mutex mutex; - }; + mutable std::mutex mutex_; +}; } #endif diff --git a/src/backend/game/game_settings.cpp b/src/backend/game/game_settings.cpp index 6dfc76b5..9d74b127 100644 --- a/src/backend/game/game_settings.cpp +++ b/src/backend/game/game_settings.cpp @@ -22,225 +22,218 @@ . */ -#include "game_settings.h" -#include "../app/loot_paths.h" -#include "../helpers/helpers.h" -#include "../error.h" +#include "backend/game/game_settings.h" #include #include #include -using namespace std; +#include "backend/app/loot_paths.h" +#include "backend/error.h" +#include "backend/helpers/helpers.h" namespace fs = boost::filesystem; -namespace lc = boost::locale; namespace loot { - GameSettings::GameSettings() : type_(GameType::autodetect) {} +GameSettings::GameSettings() : type_(GameType::autodetect) {} - GameSettings::GameSettings(const GameType gameType, const std::string& folder) : type_(gameType) { - if (Type() == GameType::tes4) { - _name = "TES IV: Oblivion"; - _registryKey = "Software\\Bethesda Softworks\\Oblivion\\Installed Path"; - _lootFolderName = "Oblivion"; - _masterFile = "Oblivion.esm"; - _repositoryURL = "https://github.com/loot/oblivion.git"; - _repositoryBranch = "v0.8"; - } - else if (Type() == GameType::tes5) { - _name = "TES V: Skyrim"; - _registryKey = "Software\\Bethesda Softworks\\Skyrim\\Installed Path"; - _lootFolderName = "Skyrim"; - _masterFile = "Skyrim.esm"; - _repositoryURL = "https://github.com/loot/skyrim.git"; - _repositoryBranch = "v0.8"; - } - else if (Type() == GameType::fo3) { - _name = "Fallout 3"; - _registryKey = "Software\\Bethesda Softworks\\Fallout3\\Installed Path"; - _lootFolderName = "Fallout3"; - _masterFile = "Fallout3.esm"; - _repositoryURL = "https://github.com/loot/fallout3.git"; - _repositoryBranch = "v0.8"; - } - else if (Type() == GameType::fonv) { - _name = "Fallout: New Vegas"; - _registryKey = "Software\\Bethesda Softworks\\FalloutNV\\Installed Path"; - _lootFolderName = "FalloutNV"; - _masterFile = "FalloutNV.esm"; - _repositoryURL = "https://github.com/loot/falloutnv.git"; - _repositoryBranch = "v0.8"; - } - else if (Type() == GameType::fo4) { - _name = "Fallout 4"; - _registryKey = "Software\\Bethesda Softworks\\Fallout4\\Installed Path"; - _lootFolderName = "Fallout4"; - _masterFile = "Fallout4.esm"; - _repositoryURL = "https://github.com/loot/fallout4.git"; - _repositoryBranch = "v0.8"; - } +GameSettings::GameSettings(const GameType gameCode, const std::string& folder) : type_(gameCode) { + if (Type() == GameType::tes4) { + name_ = "TES IV: Oblivion"; + registryKey_ = "Software\\Bethesda Softworks\\Oblivion\\Installed Path"; + lootFolderName_ = "Oblivion"; + masterFile_ = "Oblivion.esm"; + repositoryURL_ = "https://github.com/loot/oblivion.git"; + repositoryBranch_ = "v0.8"; + } else if (Type() == GameType::tes5) { + name_ = "TES V: Skyrim"; + registryKey_ = "Software\\Bethesda Softworks\\Skyrim\\Installed Path"; + lootFolderName_ = "Skyrim"; + masterFile_ = "Skyrim.esm"; + repositoryURL_ = "https://github.com/loot/skyrim.git"; + repositoryBranch_ = "v0.8"; + } else if (Type() == GameType::fo3) { + name_ = "Fallout 3"; + registryKey_ = "Software\\Bethesda Softworks\\Fallout3\\Installed Path"; + lootFolderName_ = "Fallout3"; + masterFile_ = "Fallout3.esm"; + repositoryURL_ = "https://github.com/loot/fallout3.git"; + repositoryBranch_ = "v0.8"; + } else if (Type() == GameType::fonv) { + name_ = "Fallout: New Vegas"; + registryKey_ = "Software\\Bethesda Softworks\\FalloutNV\\Installed Path"; + lootFolderName_ = "FalloutNV"; + masterFile_ = "FalloutNV.esm"; + repositoryURL_ = "https://github.com/loot/falloutnv.git"; + repositoryBranch_ = "v0.8"; + } else if (Type() == GameType::fo4) { + name_ = "Fallout 4"; + registryKey_ = "Software\\Bethesda Softworks\\Fallout4\\Installed Path"; + lootFolderName_ = "Fallout4"; + masterFile_ = "Fallout4.esm"; + repositoryURL_ = "https://github.com/loot/fallout4.git"; + repositoryBranch_ = "v0.8"; + } - if (!folder.empty()) - _lootFolderName = folder; + if (!folder.empty()) + lootFolderName_ = folder; +} + +bool GameSettings::IsInstalled() { + try { + BOOST_LOG_TRIVIAL(trace) << "Checking if game \"" << name_ << "\" is installed."; + if (!gamePath_.empty() && fs::exists(gamePath_ / "Data" / masterFile_)) + return true; + + if (fs::exists(fs::path("..") / "Data" / masterFile_)) { + gamePath_ = ".."; + return true; } - bool GameSettings::IsInstalled() { - try { - BOOST_LOG_TRIVIAL(trace) << "Checking if game \"" << _name << "\" is installed."; - if (!_gamePath.empty() && fs::exists(_gamePath / "Data" / _masterFile)) - return true; - - if (fs::exists(fs::path("..") / "Data" / _masterFile)) { - _gamePath = ".."; - return true; - } - #ifdef _WIN32 - string path; - string key_parent = fs::path(_registryKey).parent_path().string(); - string key_name = fs::path(_registryKey).filename().string(); - path = RegKeyStringValue("HKEY_LOCAL_MACHINE", key_parent, key_name); - if (!path.empty() && fs::exists(fs::path(path) / "Data" / _masterFile)) { - _gamePath = path; - return true; - } + std::string path; + std::string key_parent = fs::path(registryKey_).parent_path().string(); + std::string key_name = fs::path(registryKey_).filename().string(); + path = RegKeyStringValue("HKEY_LOCAL_MACHINE", key_parent, key_name); + if (!path.empty() && fs::exists(fs::path(path) / "Data" / masterFile_)) { + gamePath_ = path; + return true; + } #endif - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << "Error while checking if game \"" << _name << "\" is installed: " << e.what(); - } + } catch (std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Error while checking if game \"" << name_ << "\" is installed: " << e.what(); + } - return false; - } + return false; +} - bool GameSettings::operator == (const GameSettings& rhs) const { - return (boost::iequals(_name, rhs.Name()) || boost::iequals(_lootFolderName, rhs.FolderName())); - } +bool GameSettings::operator == (const GameSettings& rhs) const { + return (boost::iequals(name_, rhs.Name()) || boost::iequals(lootFolderName_, rhs.FolderName())); +} - GameType GameSettings::Type() const { - return type_; - } +GameType GameSettings::Type() const { + return type_; +} - libespm::GameId GameSettings::LibespmId() const { - if (type_ == GameType::tes4) - return libespm::GameId::OBLIVION; - else if (type_ == GameType::tes5) - return libespm::GameId::SKYRIM; - else if (type_ == GameType::fo3) - return libespm::GameId::FALLOUT3; - else if (type_ == GameType::fonv) - return libespm::GameId::FALLOUTNV; - else - return libespm::GameId::FALLOUT4; - } +libespm::GameId GameSettings::LibespmId() const { + if (type_ == GameType::tes4) + return libespm::GameId::OBLIVION; + else if (type_ == GameType::tes5) + return libespm::GameId::SKYRIM; + else if (type_ == GameType::fo3) + return libespm::GameId::FALLOUT3; + else if (type_ == GameType::fonv) + return libespm::GameId::FALLOUTNV; + else + return libespm::GameId::FALLOUT4; +} - string GameSettings::Name() const { - return _name; - } +std::string GameSettings::Name() const { + return name_; +} - string GameSettings::FolderName() const { - return _lootFolderName; - } +std::string GameSettings::FolderName() const { + return lootFolderName_; +} - std::string GameSettings::Master() const { - return _masterFile; - } +std::string GameSettings::Master() const { + return masterFile_; +} - std::string GameSettings::RegistryKey() const { - return _registryKey; - } +std::string GameSettings::RegistryKey() const { + return registryKey_; +} - std::string GameSettings::RepoURL() const { - return _repositoryURL; - } +std::string GameSettings::RepoURL() const { + return repositoryURL_; +} - std::string GameSettings::RepoBranch() const { - return _repositoryBranch; - } +std::string GameSettings::RepoBranch() const { + return repositoryBranch_; +} - fs::path GameSettings::GamePath() const { - return _gamePath; - } +fs::path GameSettings::GamePath() const { + return gamePath_; +} - fs::path GameSettings::DataPath() const { - if (_gamePath.empty()) - return ""; - else - return _gamePath / "Data"; - } +fs::path GameSettings::DataPath() const { + if (gamePath_.empty()) + return ""; + else + return gamePath_ / "Data"; +} - fs::path GameSettings::MasterlistPath() const { - if (_lootFolderName.empty()) - return ""; - else - return LootPaths::getLootDataPath() / _lootFolderName / "masterlist.yaml"; - } +fs::path GameSettings::MasterlistPath() const { + if (lootFolderName_.empty()) + return ""; + else + return LootPaths::getLootDataPath() / lootFolderName_ / "masterlist.yaml"; +} - fs::path GameSettings::UserlistPath() const { - if (_lootFolderName.empty()) - return ""; - else - return LootPaths::getLootDataPath() / _lootFolderName / "userlist.yaml"; - } +fs::path GameSettings::UserlistPath() const { + if (lootFolderName_.empty()) + return ""; + else + return LootPaths::getLootDataPath() / lootFolderName_ / "userlist.yaml"; +} - std::string GameSettings::GetArchiveFileExtension() const { - if (type_ == GameType::fo4) - return ".ba2"; - else - return ".bsa"; - } +std::string GameSettings::GetArchiveFileExtension() const { + if (type_ == GameType::fo4) + return ".ba2"; + else + return ".bsa"; +} - GameSettings& GameSettings::SetName(const std::string& name) { - BOOST_LOG_TRIVIAL(trace) << "Setting \"" << _name << "\" name to: " << name; - _name = name; - return *this; - } +GameSettings& GameSettings::SetName(const std::string& name) { + BOOST_LOG_TRIVIAL(trace) << "Setting \"" << name_ << "\" name to: " << name; + name_ = name; + return *this; +} - GameSettings& GameSettings::SetMaster(const std::string& masterFile) { - BOOST_LOG_TRIVIAL(trace) << "Setting \"" << _name << "\" master file to: " << masterFile; - _masterFile = masterFile; - return *this; - } +GameSettings& GameSettings::SetMaster(const std::string& masterFile) { + BOOST_LOG_TRIVIAL(trace) << "Setting \"" << name_ << "\" master file to: " << masterFile; + masterFile_ = masterFile; + return *this; +} - GameSettings& GameSettings::SetRegistryKey(const std::string& registry) { - BOOST_LOG_TRIVIAL(trace) << "Setting \"" << _name << "\" registry key to: " << registry; - _registryKey = registry; - return *this; - } +GameSettings& GameSettings::SetRegistryKey(const std::string& registry) { + BOOST_LOG_TRIVIAL(trace) << "Setting \"" << name_ << "\" registry key to: " << registry; + registryKey_ = registry; + return *this; +} - GameSettings& GameSettings::SetRepoURL(const std::string& repositoryURL) { - BOOST_LOG_TRIVIAL(trace) << "Setting \"" << _name << "\" repo URL to: " << repositoryURL; - _repositoryURL = repositoryURL; - return *this; - } +GameSettings& GameSettings::SetRepoURL(const std::string& repositoryURL) { + BOOST_LOG_TRIVIAL(trace) << "Setting \"" << name_ << "\" repo URL to: " << repositoryURL; + repositoryURL_ = repositoryURL; + return *this; +} - GameSettings& GameSettings::SetRepoBranch(const std::string& repositoryBranch) { - BOOST_LOG_TRIVIAL(trace) << "Setting \"" << _name << "\" repo branch to: " << repositoryBranch; - _repositoryBranch = repositoryBranch; - return *this; - } +GameSettings& GameSettings::SetRepoBranch(const std::string& repositoryBranch) { + BOOST_LOG_TRIVIAL(trace) << "Setting \"" << name_ << "\" repo branch to: " << repositoryBranch; + repositoryBranch_ = repositoryBranch; + return *this; +} - GameSettings& GameSettings::SetGamePath(const boost::filesystem::path& path) { - BOOST_LOG_TRIVIAL(trace) << "Setting \"" << _name << "\" game path to: " << path; - _gamePath = path; - return *this; - } +GameSettings& GameSettings::SetGamePath(const boost::filesystem::path& path) { + BOOST_LOG_TRIVIAL(trace) << "Setting \"" << name_ << "\" game path to: " << path; + gamePath_ = path; + return *this; +} } namespace YAML { - Emitter& operator << (Emitter& out, const loot::GameSettings& rhs) { - out << BeginMap - << Key << "type" << Value << YAML::SingleQuoted << loot::GameSettings(rhs.Type()).FolderName() - << Key << "folder" << Value << YAML::SingleQuoted << rhs.FolderName() - << Key << "name" << Value << YAML::SingleQuoted << rhs.Name() - << Key << "master" << Value << YAML::SingleQuoted << rhs.Master() - << Key << "repo" << Value << YAML::SingleQuoted << rhs.RepoURL() - << Key << "branch" << Value << YAML::SingleQuoted << rhs.RepoBranch() - << Key << "path" << Value << YAML::SingleQuoted << rhs.GamePath().string() - << Key << "registry" << Value << YAML::SingleQuoted << rhs.RegistryKey() - << EndMap; +Emitter& operator << (Emitter& out, const loot::GameSettings& rhs) { + out << BeginMap + << Key << "type" << Value << YAML::SingleQuoted << loot::GameSettings(rhs.Type()).FolderName() + << Key << "folder" << Value << YAML::SingleQuoted << rhs.FolderName() + << Key << "name" << Value << YAML::SingleQuoted << rhs.Name() + << Key << "master" << Value << YAML::SingleQuoted << rhs.Master() + << Key << "repo" << Value << YAML::SingleQuoted << rhs.RepoURL() + << Key << "branch" << Value << YAML::SingleQuoted << rhs.RepoBranch() + << Key << "path" << Value << YAML::SingleQuoted << rhs.GamePath().string() + << Key << "registry" << Value << YAML::SingleQuoted << rhs.RegistryKey() + << EndMap; - return out; - } + return out; +} } diff --git a/src/backend/game/game_settings.h b/src/backend/game/game_settings.h index a45c66c0..b139fb50 100644 --- a/src/backend/game/game_settings.h +++ b/src/backend/game/game_settings.h @@ -22,125 +22,126 @@ . */ -#ifndef __LOOT_GAME_SETTINGS__ -#define __LOOT_GAME_SETTINGS__ +#ifndef LOOT_BACKEND_GAME_GAME_SETTINGS +#define LOOT_BACKEND_GAME_GAME_SETTINGS #include #include #include - -#include - #include +#include #include "backend/game/game_type.h" namespace loot { - class GameSettings { - public: - //Game functions. - GameSettings(); //Sets game to LOOT_GameType::autodetect, with all other vars being empty. - GameSettings(const GameType gameType, const std::string& lootFolder = ""); +class GameSettings { +public: + GameSettings(); //Sets game type to autodetect, with all other vars being empty. + GameSettings(const GameType gameType, const std::string& lootFolder = ""); - bool IsInstalled(); //Sets gamePath if the current value is not valid and a valid path is found. + bool IsInstalled(); //Sets gamePath if the current value is not valid and a valid path is found. - bool operator == (const GameSettings& rhs) const; //Compares names and folder names. + bool operator == (const GameSettings& rhs) const; //Compares names and folder names. - GameType Type() const; - libespm::GameId LibespmId() const; - std::string Name() const; //Returns the game's name, eg. "TES IV: Oblivion". - std::string FolderName() const; - std::string Master() const; - std::string RegistryKey() const; - std::string RepoURL() const; - std::string RepoBranch() const; + GameType Type() const; + libespm::GameId LibespmId() const; + std::string Name() const; //Returns the game's name, eg. "TES IV: Oblivion". + std::string FolderName() const; + std::string Master() const; + std::string RegistryKey() const; + std::string RepoURL() const; + std::string RepoBranch() const; - boost::filesystem::path GamePath() const; - boost::filesystem::path DataPath() const; - boost::filesystem::path MasterlistPath() const; - boost::filesystem::path UserlistPath() const; + boost::filesystem::path GamePath() const; + boost::filesystem::path DataPath() const; + boost::filesystem::path MasterlistPath() const; + boost::filesystem::path UserlistPath() const; - std::string GetArchiveFileExtension() const; + std::string GetArchiveFileExtension() const; - GameSettings& SetName(const std::string& name); - GameSettings& SetMaster(const std::string& masterFile); - GameSettings& SetRegistryKey(const std::string& registry); - GameSettings& SetRepoURL(const std::string& repositoryURL); - GameSettings& SetRepoBranch(const std::string& repositoryBranch); - GameSettings& SetGamePath(const boost::filesystem::path& path); - private: - GameType type_; - std::string _name; - std::string _masterFile; + GameSettings& SetName(const std::string& name); + GameSettings& SetMaster(const std::string& masterFile); + GameSettings& SetRegistryKey(const std::string& registry); + GameSettings& SetRepoURL(const std::string& repositoryURL); + GameSettings& SetRepoBranch(const std::string& repositoryBranch); + GameSettings& SetGamePath(const boost::filesystem::path& path); - std::string _registryKey; +private: + GameType type_; + std::string name_; + std::string masterFile_; - std::string _lootFolderName; - std::string _repositoryURL; - std::string _repositoryBranch; + std::string registryKey_; - boost::filesystem::path _gamePath; //Path to the game's folder. - }; + std::string lootFolderName_; + std::string repositoryURL_; + std::string repositoryBranch_; + + boost::filesystem::path gamePath_; //Path to the game's folder. +}; } namespace YAML { - template<> - struct convert < loot::GameSettings > { - static Node encode(const loot::GameSettings& rhs) { - Node node; +template<> +struct convert { + static Node encode(const loot::GameSettings& rhs) { + Node node; - node["type"] = loot::GameSettings(rhs.Type()).FolderName(); - node["name"] = rhs.Name(); - node["folder"] = rhs.FolderName(); - node["master"] = rhs.Master(); - node["repo"] = rhs.RepoURL(); - node["branch"] = rhs.RepoBranch(); - node["path"] = rhs.GamePath().string(); - node["registry"] = rhs.RegistryKey(); + node["type"] = loot::GameSettings(rhs.Type()).FolderName(); + node["name"] = rhs.Name(); + node["folder"] = rhs.FolderName(); + node["master"] = rhs.Master(); + node["repo"] = rhs.RepoURL(); + node["branch"] = rhs.RepoBranch(); + node["path"] = rhs.GamePath().string(); + node["registry"] = rhs.RegistryKey(); - return node; - } + return node; + } - static bool decode(const Node& node, loot::GameSettings& rhs) { - if (!node.IsMap()) - throw RepresentationException(node.Mark(), "bad conversion: 'game settings' object must be a map"); - if (!node["folder"]) - throw RepresentationException(node.Mark(), "bad conversion: 'folder' key missing from 'game settings' object"); - if (!node["type"]) - throw RepresentationException(node.Mark(), "bad conversion: 'type' key missing from 'game settings' object"); + static bool decode(const Node& node, loot::GameSettings& rhs) { + using loot::GameSettings; + using loot::GameType; - if (node["type"].as() == loot::GameSettings(loot::GameType::tes4).FolderName()) - rhs = loot::GameSettings(loot::GameType::tes4, node["folder"].as()); - else if (node["type"].as() == loot::GameSettings(loot::GameType::tes5).FolderName()) - rhs = loot::GameSettings(loot::GameType::tes5, node["folder"].as()); - else if (node["type"].as() == loot::GameSettings(loot::GameType::fo3).FolderName()) - rhs = loot::GameSettings(loot::GameType::fo3, node["folder"].as()); - else if (node["type"].as() == loot::GameSettings(loot::GameType::fonv).FolderName()) - rhs = loot::GameSettings(loot::GameType::fonv, node["folder"].as()); - else if (node["type"].as() == loot::GameSettings(loot::GameType::fo4).FolderName()) - rhs = loot::GameSettings(loot::GameType::fo4, node["folder"].as()); - else - throw RepresentationException(node.Mark(), "bad conversion: invalid value for 'type' key in 'game settings' object"); + if (!node.IsMap()) + throw RepresentationException(node.Mark(), "bad conversion: 'game settings' object must be a map"); + if (!node["folder"]) + throw RepresentationException(node.Mark(), "bad conversion: 'folder' key missing from 'game settings' object"); + if (!node["type"]) + throw RepresentationException(node.Mark(), "bad conversion: 'type' key missing from 'game settings' object"); - if (node["name"]) - rhs.SetName(node["name"].as()); - if (node["master"]) - rhs.SetMaster(node["master"].as()); - if (node["repo"]) - rhs.SetRepoURL(node["repo"].as()); - if (node["branch"]) - rhs.SetRepoBranch(node["branch"].as()); - if (node["path"]) - rhs.SetGamePath(node["path"].as()); - if (node["registry"]) - rhs.SetRegistryKey(node["registry"].as()); + if (node["type"].as() == GameSettings(GameType::tes4).FolderName()) + rhs = GameSettings(GameType::tes4, node["folder"].as()); + else if (node["type"].as() == GameSettings(GameType::tes5).FolderName()) + rhs = GameSettings(GameType::tes5, node["folder"].as()); + else if (node["type"].as() == GameSettings(GameType::fo3).FolderName()) + rhs = GameSettings(GameType::fo3, node["folder"].as()); + else if (node["type"].as() == GameSettings(GameType::fonv).FolderName()) + rhs = GameSettings(GameType::fonv, node["folder"].as()); + else if (node["type"].as() == GameSettings(GameType::fo4).FolderName()) + rhs = GameSettings(GameType::fo4, node["folder"].as()); + else + throw RepresentationException(node.Mark(), "bad conversion: invalid value for 'type' key in 'game settings' object"); - return true; - } - }; + if (node["name"]) + rhs.SetName(node["name"].as()); + if (node["master"]) + rhs.SetMaster(node["master"].as()); + if (node["repo"]) + rhs.SetRepoURL(node["repo"].as()); + if (node["branch"]) + rhs.SetRepoBranch(node["branch"].as()); + if (node["path"]) + rhs.SetGamePath(node["path"].as()); + if (node["registry"]) + rhs.SetRegistryKey(node["registry"].as()); - Emitter& operator << (Emitter& out, const loot::GameSettings& rhs); + return true; + } +}; + +Emitter& operator << (Emitter& out, const loot::GameSettings& rhs); } #endif diff --git a/src/backend/game/game_type.h b/src/backend/game/game_type.h index 9961ecda..507f2e92 100644 --- a/src/backend/game/game_type.h +++ b/src/backend/game/game_type.h @@ -26,14 +26,14 @@ along with LOOT. If not, see #define LOOT_BACKEND_GAME_GAME_TYPE namespace loot { - enum struct GameType : unsigned int { - autodetect = 0, - tes4 = 1, - tes5 = 2, - fo3 = 3, - fonv = 4, - fo4 = 5, - }; +enum struct GameType : unsigned int { + autodetect = 0, + tes4 = 1, + tes5 = 2, + fo3 = 3, + fonv = 4, + fo4 = 5, +}; } #endif diff --git a/src/backend/game/load_order_handler.cpp b/src/backend/game/load_order_handler.cpp index ca629bbc..60d1d152 100644 --- a/src/backend/game/load_order_handler.cpp +++ b/src/backend/game/load_order_handler.cpp @@ -22,180 +22,174 @@ . */ -#include "load_order_handler.h" -#include "../error.h" +#include "backend/game/load_order_handler.h" #include #include #include -using namespace std; +#include "backend/error.h" -namespace fs = boost::filesystem; -namespace lc = boost::locale; +using boost::locale::translate; +using std::string; namespace loot { - LoadOrderHandler::LoadOrderHandler() : _gh(nullptr) {} +LoadOrderHandler::LoadOrderHandler() : gh_(nullptr) {} - LoadOrderHandler::~LoadOrderHandler() { - lo_destroy_handle(_gh); +LoadOrderHandler::~LoadOrderHandler() { + lo_destroy_handle(gh_); +} + +void LoadOrderHandler::Init(const GameSettings& game, const boost::filesystem::path& gameLocalAppData) { + if (game.Type() != GameType::tes4 + && game.Type() != GameType::tes5 + && game.Type() != GameType::fo3 + && game.Type() != GameType::fonv + && game.Type() != GameType::fo4) { + throw Error(Error::Code::invalid_args, translate("Unsupported game ID supplied.").str()); + } + + if (game.GamePath().empty()) { + BOOST_LOG_TRIVIAL(error) << "Game path is not initialised."; + throw Error(Error::Code::invalid_args, translate("Game path is not initialised.").str()); + } + + const char * gameLocalDataPath = nullptr; + string tempPathString = gameLocalAppData.string(); + if (!tempPathString.empty()) + gameLocalDataPath = tempPathString.c_str(); + +// If the handle has already been initialised, close it and open another. + if (gh_ != nullptr) { + lo_destroy_handle(gh_); + gh_ = nullptr; + } + + int ret; + if (game.Type() == GameType::tes4) + ret = lo_create_handle(&gh_, LIBLO_GAME_TES4, game.GamePath().string().c_str(), gameLocalDataPath); + else if (game.Type() == GameType::tes5) + ret = lo_create_handle(&gh_, LIBLO_GAME_TES5, game.GamePath().string().c_str(), gameLocalDataPath); + else if (game.Type() == GameType::fo3) + ret = lo_create_handle(&gh_, LIBLO_GAME_FO3, game.GamePath().string().c_str(), gameLocalDataPath); + else if (game.Type() == GameType::fonv) + ret = lo_create_handle(&gh_, LIBLO_GAME_FNV, game.GamePath().string().c_str(), gameLocalDataPath); + else if (game.Type() == GameType::fo4) + ret = lo_create_handle(&gh_, LIBLO_GAME_FO4, game.GamePath().string().c_str(), gameLocalDataPath); + else + ret = LIBLO_ERROR_INVALID_ARGS; + + if (ret != LIBLO_OK && ret != LIBLO_WARN_BAD_FILENAME && ret != LIBLO_WARN_INVALID_LIST && ret != LIBLO_WARN_LO_MISMATCH) { + const char * e = nullptr; + string err; + lo_get_error_message(&e); + if (e == nullptr) { + BOOST_LOG_TRIVIAL(error) << "libloadorder failed to create a game handle. Details could not be fetched."; + err = translate("libloadorder failed to create a game handle. Details could not be fetched.").str(); + } else { + BOOST_LOG_TRIVIAL(error) << "libloadorder failed to create a game handle. Details: " << e; + err = translate("libloadorder failed to create a game handle. Details:").str() + " " + e; } - - void LoadOrderHandler::Init(const GameSettings& game, const boost::filesystem::path& gameLocalAppData) { - if (game.Type() != GameType::tes4 - && game.Type() != GameType::tes5 - && game.Type() != GameType::fo3 - && game.Type() != GameType::fonv - && game.Type() != GameType::fo4) { - throw Error(Error::Code::invalid_args, lc::translate("Unsupported game ID supplied.").str()); - } - - if (game.GamePath().empty()) { - BOOST_LOG_TRIVIAL(error) << "Game path is not initialised."; - throw Error(Error::Code::invalid_args, lc::translate("Game path is not initialised.").str()); - } - - const char * gameLocalDataPath = nullptr; - string tempPathString = gameLocalAppData.string(); - if (!tempPathString.empty()) - gameLocalDataPath = tempPathString.c_str(); - - // If the handle has already been initialised, close it and open another. - if (_gh != nullptr) { - lo_destroy_handle(_gh); - _gh = nullptr; - } - - int ret; - if (game.Type() == GameType::tes4) - ret = lo_create_handle(&_gh, LIBLO_GAME_TES4, game.GamePath().string().c_str(), gameLocalDataPath); - else if (game.Type() == GameType::tes5) - ret = lo_create_handle(&_gh, LIBLO_GAME_TES5, game.GamePath().string().c_str(), gameLocalDataPath); - else if (game.Type() == GameType::fo3) - ret = lo_create_handle(&_gh, LIBLO_GAME_FO3, game.GamePath().string().c_str(), gameLocalDataPath); - else if (game.Type() == GameType::fonv) - ret = lo_create_handle(&_gh, LIBLO_GAME_FNV, game.GamePath().string().c_str(), gameLocalDataPath); - else if (game.Type() == GameType::fo4) - ret = lo_create_handle(&_gh, LIBLO_GAME_FO4, game.GamePath().string().c_str(), gameLocalDataPath); - else - ret = LIBLO_ERROR_INVALID_ARGS; - - if (ret != LIBLO_OK && ret != LIBLO_WARN_BAD_FILENAME && ret != LIBLO_WARN_INVALID_LIST && ret != LIBLO_WARN_LO_MISMATCH) { - const char * e = nullptr; - string err; - lo_get_error_message(&e); - if (e == nullptr) { - BOOST_LOG_TRIVIAL(error) << "libloadorder failed to create a game handle. Details could not be fetched."; - err = lc::translate("libloadorder failed to create a game handle. Details could not be fetched.").str(); - } - else { - BOOST_LOG_TRIVIAL(error) << "libloadorder failed to create a game handle. Details: " << e; - err = lc::translate("libloadorder failed to create a game handle. Details:").str() + " " + e; - } - lo_cleanup(); - throw Error(Error::Code::liblo_error, err); - } - } - - bool LoadOrderHandler::IsPluginActive(const std::string& pluginName) const { - BOOST_LOG_TRIVIAL(debug) << "Checking if plugin \"" << pluginName << "\" is active."; - - bool result = false; - unsigned int ret = lo_get_plugin_active(_gh, pluginName.c_str(), &result); - if (ret != LIBLO_OK && ret != LIBLO_WARN_BAD_FILENAME) { - const char * e = nullptr; - string err; - lo_get_error_message(&e); - if (e == nullptr) { - BOOST_LOG_TRIVIAL(error) << "libloadorder failed to check if a plugin is active. Details could not be fetched."; - err = lc::translate("libloadorder failed to check if a plugin is active. Details could not be fetched.").str(); - } - else { - BOOST_LOG_TRIVIAL(error) << "libloadorder failed to check if a plugin is active. Details: " << e; - err = lc::translate("libloadorder failed to check if a plugin is active. Details:").str() + " " + e; - } - lo_cleanup(); - throw Error(Error::Code::liblo_error, err); - } - - return result; + lo_cleanup(); + throw Error(Error::Code::liblo_error, err); + } +} + +bool LoadOrderHandler::IsPluginActive(const std::string& pluginName) const { + BOOST_LOG_TRIVIAL(debug) << "Checking if plugin \"" << pluginName << "\" is active."; + + bool result = false; + unsigned int ret = lo_get_plugin_active(gh_, pluginName.c_str(), &result); + if (ret != LIBLO_OK && ret != LIBLO_WARN_BAD_FILENAME) { + const char * e = nullptr; + string err; + lo_get_error_message(&e); + if (e == nullptr) { + BOOST_LOG_TRIVIAL(error) << "libloadorder failed to check if a plugin is active. Details could not be fetched."; + err = translate("libloadorder failed to check if a plugin is active. Details could not be fetched.").str(); + } else { + BOOST_LOG_TRIVIAL(error) << "libloadorder failed to check if a plugin is active. Details: " << e; + err = translate("libloadorder failed to check if a plugin is active. Details:").str() + " " + e; } - - std::list LoadOrderHandler::GetLoadOrder() const { - BOOST_LOG_TRIVIAL(debug) << "Getting load order."; - - char ** pluginArr; - size_t pluginArrSize; - - unsigned int ret = lo_get_load_order(_gh, &pluginArr, &pluginArrSize); - if (ret != LIBLO_OK && ret != LIBLO_WARN_BAD_FILENAME && ret != LIBLO_WARN_INVALID_LIST && ret != LIBLO_WARN_LO_MISMATCH) { - const char * e = nullptr; - string err; - lo_get_error_message(&e); - if (e == nullptr) { - BOOST_LOG_TRIVIAL(error) << "libloadorder failed to get the load order. Details could not be fetched."; - err = lc::translate("libloadorder failed to get the load order. Details could not be fetched.").str(); - } - else { - BOOST_LOG_TRIVIAL(error) << "libloadorder failed to get the load order. Details: " << e; - err = lc::translate("libloadorder failed to get the load order. Details:").str() + " " + e; - } - lo_cleanup(); - throw Error(Error::Code::liblo_error, err); - } - - std::list loadOrder; - for (size_t i = 0; i < pluginArrSize; ++i) { - loadOrder.push_back(string(pluginArr[i])); - } - return loadOrder; + lo_cleanup(); + throw Error(Error::Code::liblo_error, err); + } + + return result; +} + +std::list LoadOrderHandler::GetLoadOrder() const { + BOOST_LOG_TRIVIAL(debug) << "Getting load order."; + + char ** pluginArr; + size_t pluginArrSize; + + unsigned int ret = lo_get_load_order(gh_, &pluginArr, &pluginArrSize); + if (ret != LIBLO_OK && ret != LIBLO_WARN_BAD_FILENAME && ret != LIBLO_WARN_INVALID_LIST && ret != LIBLO_WARN_LO_MISMATCH) { + const char * e = nullptr; + string err; + lo_get_error_message(&e); + if (e == nullptr) { + BOOST_LOG_TRIVIAL(error) << "libloadorder failed to get the load order. Details could not be fetched."; + err = translate("libloadorder failed to get the load order. Details could not be fetched.").str(); + } else { + BOOST_LOG_TRIVIAL(error) << "libloadorder failed to get the load order. Details: " << e; + err = translate("libloadorder failed to get the load order. Details:").str() + " " + e; } - - void LoadOrderHandler::SetLoadOrder(const char * const * const loadOrder, const size_t numPlugins) const { - BOOST_LOG_TRIVIAL(debug) << "Setting load order."; - - unsigned int ret = lo_set_load_order(_gh, loadOrder, numPlugins); - if (ret != LIBLO_OK && ret != LIBLO_WARN_BAD_FILENAME && ret != LIBLO_WARN_INVALID_LIST && ret != LIBLO_WARN_LO_MISMATCH) { - const char * e = nullptr; - string err; - lo_get_error_message(&e); - if (e == nullptr) { - BOOST_LOG_TRIVIAL(error) << "libloadorder failed to set the load order. Details could not be fetched."; - err = lc::translate("libloadorder failed to set the load order. Details could not be fetched.").str(); - } - else { - BOOST_LOG_TRIVIAL(error) << "libloadorder failed to set the load order. Details: " << e; - err = lc::translate("libloadorder failed to set the load order. Details:").str() + " " + e; - } - lo_cleanup(); - throw Error(Error::Code::liblo_error, err); - } - } - - void LoadOrderHandler::SetLoadOrder(const std::list& loadOrder) const { - BOOST_LOG_TRIVIAL(info) << "Setting load order."; - size_t pluginArrSize = loadOrder.size(); - char ** pluginArr = new char*[pluginArrSize]; - int i = 0; - for (const auto &plugin : loadOrder) { - BOOST_LOG_TRIVIAL(info) << '\t' << '\t' << plugin; - pluginArr[i] = new char[plugin.length() + 1]; - strcpy(pluginArr[i], plugin.c_str()); - ++i; - } - - try { - SetLoadOrder(pluginArr, pluginArrSize); - } - catch (Error &/*e*/) { - for (size_t i = 0; i < pluginArrSize; i++) - delete[] pluginArr[i]; - delete[] pluginArr; - throw; - } - - for (size_t i = 0; i < pluginArrSize; i++) - delete[] pluginArr[i]; - delete[] pluginArr; + lo_cleanup(); + throw Error(Error::Code::liblo_error, err); + } + + std::list loadOrder; + for (size_t i = 0; i < pluginArrSize; ++i) { + loadOrder.push_back(string(pluginArr[i])); + } + return loadOrder; +} + +void LoadOrderHandler::SetLoadOrder(const char * const * const loadOrder, const size_t numPlugins) const { + BOOST_LOG_TRIVIAL(debug) << "Setting load order."; + + unsigned int ret = lo_set_load_order(gh_, loadOrder, numPlugins); + if (ret != LIBLO_OK && ret != LIBLO_WARN_BAD_FILENAME && ret != LIBLO_WARN_INVALID_LIST && ret != LIBLO_WARN_LO_MISMATCH) { + const char * e = nullptr; + string err; + lo_get_error_message(&e); + if (e == nullptr) { + BOOST_LOG_TRIVIAL(error) << "libloadorder failed to set the load order. Details could not be fetched."; + err = translate("libloadorder failed to set the load order. Details could not be fetched.").str(); + } else { + BOOST_LOG_TRIVIAL(error) << "libloadorder failed to set the load order. Details: " << e; + err = translate("libloadorder failed to set the load order. Details:").str() + " " + e; } + lo_cleanup(); + throw Error(Error::Code::liblo_error, err); + } +} + +void LoadOrderHandler::SetLoadOrder(const std::list& loadOrder) const { + BOOST_LOG_TRIVIAL(info) << "Setting load order."; + size_t pluginArrSize = loadOrder.size(); + char ** pluginArr = new char*[pluginArrSize]; + int i = 0; + for (const auto &plugin : loadOrder) { + BOOST_LOG_TRIVIAL(info) << '\t' << '\t' << plugin; + pluginArr[i] = new char[plugin.length() + 1]; + strcpy(pluginArr[i], plugin.c_str()); + ++i; + } + + try { + SetLoadOrder(pluginArr, pluginArrSize); + } catch (Error &/*e*/) { + for (size_t i = 0; i < pluginArrSize; i++) + delete[] pluginArr[i]; + delete[] pluginArr; + throw; + } + + for (size_t i = 0; i < pluginArrSize; i++) + delete[] pluginArr[i]; + delete[] pluginArr; +} } diff --git a/src/backend/game/load_order_handler.h b/src/backend/game/load_order_handler.h index 738f7872..abd7b62e 100644 --- a/src/backend/game/load_order_handler.h +++ b/src/backend/game/load_order_handler.h @@ -22,37 +22,36 @@ . */ -#ifndef __LOOT_LOAD_ORDER_HANDLER__ -#define __LOOT_LOAD_ORDER_HANDLER__ +#ifndef LOOT_BACKEND_GAME_LOAD_ORDER_HANDLER +#define LOOT_BACKEND_GAME_LOAD_ORDER_HANDLER -#include "game_settings.h" - -#include #include +#include #include #include - #include +#include "backend/game/game_settings.h" + namespace loot { - class LoadOrderHandler { - public: - LoadOrderHandler(); - ~LoadOrderHandler(); +class LoadOrderHandler { +public: + LoadOrderHandler(); + ~LoadOrderHandler(); - void Init(const GameSettings& game, const boost::filesystem::path& gameLocalAppData = ""); + void Init(const GameSettings& game, const boost::filesystem::path& gameLocalAppData = ""); - std::list GetLoadOrder() const; + std::list GetLoadOrder() const; - bool IsPluginActive(const std::string& pluginName) const; + bool IsPluginActive(const std::string& pluginName) const; - //These modify game load order, even though const. - void SetLoadOrder(const char * const * const loadOrder, const size_t numPlugins) const; // For API. - void SetLoadOrder(const std::list& loadOrder) const; - private: - lo_game_handle _gh; - }; + //These modify game load order, even though const. + void SetLoadOrder(const char * const * const loadOrder, const size_t numPlugins) const; // For API. + void SetLoadOrder(const std::list& loadOrder) const; +private: + lo_game_handle gh_; +}; } #endif diff --git a/src/backend/helpers/git_helper.cpp b/src/backend/helpers/git_helper.cpp index 5d972e98..4691ac73 100644 --- a/src/backend/helpers/git_helper.cpp +++ b/src/backend/helpers/git_helper.cpp @@ -22,309 +22,313 @@ . */ -#include "git_helper.h" -#include "../error.h" +#include "backend/helpers/git_helper.h" -#include -#include #include +#include +#include -using namespace std; +#include "backend/error.h" + +using boost::locale::translate; +using std::string; namespace fs = boost::filesystem; -namespace lc = boost::locale; namespace loot { - GitHelper::GitHelper() : - repo(nullptr), - remote(nullptr), - cfg(nullptr), - obj(nullptr), - commit(nullptr), - ref(nullptr), - ref2(nullptr), - blob(nullptr), - annotated_commit(nullptr), - tree(nullptr), - diff(nullptr), - buf({0}) { - // Init threading system and OpenSSL (for Linux builds). - git_libgit2_init(); +GitHelper::GitData::GitData() : + repo(nullptr), + remote(nullptr), + config(nullptr), + object(nullptr), + commit(nullptr), + reference(nullptr), + reference2(nullptr), + blob(nullptr), + annotated_commit(nullptr), + tree(nullptr), + diff(nullptr), + buffer({0}) { + // Init threading system and OpenSSL (for Linux builds). + git_libgit2_init(); - checkout_options = GIT_CHECKOUT_OPTIONS_INIT; - clone_options = GIT_CLONE_OPTIONS_INIT; + checkout_options = GIT_CHECKOUT_OPTIONS_INIT; + clone_options = GIT_CLONE_OPTIONS_INIT; +} + +GitHelper::GitData::~GitData() { + string path; + if (repo != nullptr) + path = git_repository_path(repo); + + git_commit_free(commit); + git_object_free(object); + git_config_free(config); + git_remote_free(remote); + git_repository_free(repo); + git_reference_free(reference); + git_reference_free(reference2); + git_blob_free(blob); + git_annotated_commit_free(annotated_commit); + git_tree_free(tree); + git_diff_free(diff); + git_buf_free(&buffer); + + // Also free any path strings in the checkout options. + for (size_t i = 0; i < checkout_options.paths.count; ++i) { + delete[] checkout_options.paths.strings[i]; + } + + if (!path.empty()) { + try { + FixRepoPermissions(path); + } catch (std::exception&) {} + } + + git_libgit2_shutdown(); +} + +void GitHelper::Call(int error_code) { + if (!error_code) + return; + + const git_error * last_error = giterr_last(); + std::string gitError; + if (last_error == nullptr) + gitError = std::to_string(error_code) + "."; + else + gitError = std::to_string(error_code) + "; " + last_error->message; + giterr_clear(); + + if (errorMessage_.empty()) + errorMessage_ = (boost::format(translate("Git operation failed. Error: %1%")) % gitError).str(); + + BOOST_LOG_TRIVIAL(error) << "Git operation failed. Error: " << gitError; + throw Error(Error::Code::git_error, errorMessage_); +} + +void GitHelper::SetErrorMessage(const std::string& message) { + errorMessage_ = message; +} + +bool GitHelper::IsRepository(const boost::filesystem::path& path) { + return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0; +} + +// Removes the read-only flag from some files in git repositories created by libgit2. +void GitHelper::FixRepoPermissions(const boost::filesystem::path& path) { + BOOST_LOG_TRIVIAL(trace) << "Recursively setting write permission on directory: " << path; + for (fs::recursive_directory_iterator it(path); it != fs::recursive_directory_iterator(); ++it) { + if ((it->status().permissions() & (fs::owner_write | fs::group_write | fs::others_write)) == 0) { + BOOST_LOG_TRIVIAL(trace) << "Setting write permission for: " << it->path(); + fs::permissions(it->path(), fs::add_perms | fs::owner_write); } - - GitHelper::~GitHelper() { - string path; - if (repo != nullptr) - path = git_repository_path(repo); - - git_commit_free(commit); - git_object_free(obj); - git_config_free(cfg); - git_remote_free(remote); - git_repository_free(repo); - git_reference_free(ref); - git_reference_free(ref2); - git_blob_free(blob); - git_annotated_commit_free(annotated_commit); - git_tree_free(tree); - git_diff_free(diff); - git_buf_free(&buf); - - // Also free any path strings in the checkout options. - for (size_t i = 0; i < checkout_options.paths.count; ++i) { - delete[] checkout_options.paths.strings[i]; - } - - if (!path.empty()) { - try { - FixRepoPermissions(path); - } - catch (exception&) {} - } - - git_libgit2_shutdown(); - } - - void GitHelper::Call(int error_code) { - if (!error_code) - return; - - const git_error * last_error = giterr_last(); - std::string gitError; - if (last_error == nullptr) - gitError = to_string(error_code) + "."; - else - gitError = to_string(error_code) + "; " + last_error->message; - giterr_clear(); - - if (errorMessage.empty()) - errorMessage = (boost::format(lc::translate("Git operation failed. Error: %1%")) % gitError).str(); - - BOOST_LOG_TRIVIAL(error) << "Git operation failed. Error: " << gitError; - throw loot::Error(loot::Error::Code::git_error, errorMessage); - } - - void GitHelper::SetErrorMessage(const std::string& message) { - errorMessage = message; - } - - bool GitHelper::IsRepository(const boost::filesystem::path& path) { - return git_repository_open_ext(NULL, path.string().c_str(), GIT_REPOSITORY_OPEN_NO_SEARCH, NULL) == 0; - } - - // Removes the read-only flag from some files in git repositories created by libgit2. - void GitHelper::FixRepoPermissions(const boost::filesystem::path& path) { - BOOST_LOG_TRIVIAL(trace) << "Recursively setting write permission on directory: " << path; - for (fs::recursive_directory_iterator it(path); it != fs::recursive_directory_iterator(); ++it) { - if ((it->status().permissions() & (fs::owner_write | fs::group_write | fs::others_write)) == 0) { - BOOST_LOG_TRIVIAL(trace) << "Setting write permission for: " << it->path(); - fs::permissions(it->path(), fs::add_perms | fs::owner_write); - } - } - } - - int GitHelper::diff_file_cb(const git_diff_delta *delta, float progress, void * payload) { - BOOST_LOG_TRIVIAL(trace) << "Checking diff for: " << delta->old_file.path; - git_diff_payload * gdp = (git_diff_payload*)payload; - if (strcmp(delta->old_file.path, gdp->fileToFind) == 0) { - BOOST_LOG_TRIVIAL(warning) << "Edited masterlist found."; - gdp->fileFound = true; - } - - return 0; - } - - // Clones a repository and opens it. - void GitHelper::Clone(const boost::filesystem::path& path, const std::string& url) { - if (this->repo != nullptr) - throw Error(Error::Code::git_error, "Cannot clone repository that has already been opened."); - - this->SetErrorMessage(lc::translate("An error occurred while trying to clone the remote masterlist repository.")); - // Clone the remote repository. - BOOST_LOG_TRIVIAL(info) << "Repository doesn't exist, cloning the remote repository."; - - fs::path tempPath = fs::temp_directory_path() / fs::unique_path(); - - // Create the temporary parent folder in case it doesn't already exist. - fs::create_directories(tempPath.parent_path()); - - //Delete temporary folder in case it already exists. - fs::remove_all(tempPath); - - if (!fs::is_empty(path)) { - // Directory is non-empty. Delete the masterlist file and - // .git folder, then move any remaining files to a temporary - // folder while the repo is cloned, before moving them back. - BOOST_LOG_TRIVIAL(trace) << "Repo path not empty, renaming folder."; - - // Clear any read-only flags first. - this->FixRepoPermissions(path); - - // Now move to temp path. - fs::rename(path, tempPath); - - // Recreate the game folder so that we don't inadvertently - // cause any other errors (everything past LOOT init assumes - // it exists). - fs::create_directory(path); - } - - // Perform the clone. - this->Call(git_clone(&this->repo, url.c_str(), path.string().c_str(), &this->clone_options)); - - if (fs::exists(tempPath)) { - //Move contents back in. - BOOST_LOG_TRIVIAL(trace) << "Repo path wasn't empty, moving previous files back in."; - for (fs::directory_iterator it(tempPath); it != fs::directory_iterator(); ++it) { - if (!fs::exists(path / it->path().filename())) { - //No conflict, OK to move back in. - fs::rename(it->path(), path / it->path().filename()); - } - } - //Delete temporary folder. - fs::remove_all(tempPath); - } - } - - void GitHelper::Fetch(const std::string& remote) { - if (this->repo == nullptr) - throw Error(Error::Code::git_error, "Cannot fetch updates for repository that has not been opened."); - - BOOST_LOG_TRIVIAL(trace) << "Fetching updates from remote."; - this->SetErrorMessage(lc::translate("An error occurred while trying to update the masterlist. This could be due to a server-side error. Try again in a few minutes.")); - - // Get the origin remote. - this->Call(git_remote_lookup(&this->remote, this->repo, remote.c_str())); - - // Now fetch any updates. - git_fetch_options fetch_options = GIT_FETCH_OPTIONS_INIT; - this->Call(git_remote_fetch(this->remote, nullptr, &fetch_options, nullptr)); - - // Log some stats on what was fetched either during update or clone. - const git_transfer_progress * stats = git_remote_stats(this->remote); - BOOST_LOG_TRIVIAL(info) << "Received " << stats->indexed_objects << " of " << stats->total_objects << " objects in " << stats->received_bytes << " bytes."; - - git_remote_free(this->remote); - this->remote = nullptr; - } - - void GitHelper::CheckoutNewBranch(const std::string& remote, const std::string& branch) { - if (this->repo == nullptr) - throw Error(Error::Code::git_error, "Cannot fetch updates for repository that has not been opened."); - else if (this->commit != nullptr) - throw Error(Error::Code::git_error, "Cannot fetch repository updates, commit memory already allocated."); - else if (this->obj != nullptr) - throw Error(Error::Code::git_error, "Cannot fetch repository updates, object memory already allocated."); - else if (this->ref != nullptr) - throw Error(Error::Code::git_error, "Cannot fetch repository updates, reference memory already allocated."); - - BOOST_LOG_TRIVIAL(trace) << "Looking up commit referred to by the remote branch \"" << branch << "\"."; - this->Call(git_revparse_single(&this->obj, this->repo, (remote + "/" + branch).c_str())); - const git_oid * commit_id = git_object_id(this->obj); - - // Create a branch. - BOOST_LOG_TRIVIAL(trace) << "Creating the new branch."; - this->Call(git_commit_lookup(&this->commit, this->repo, commit_id)); - this->Call(git_branch_create(&this->ref, this->repo, branch.c_str(), this->commit, 0)); - - // Set upstream. - BOOST_LOG_TRIVIAL(trace) << "Setting the upstream for the new branch."; - this->Call(git_branch_set_upstream(this->ref, (remote + "/" + branch).c_str())); - - // Check if HEAD points to the desired branch and set it to if not. - if (!git_branch_is_head(this->ref)) { - BOOST_LOG_TRIVIAL(trace) << "Setting HEAD to follow branch: " << branch; - this->Call(git_repository_set_head(this->repo, (string("refs/heads/") + branch).c_str())); - } - - BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD."; - this->Call(git_checkout_head(this->repo, &this->checkout_options)); - - // Free tree and commit pointers. Reference pointer is still used below. - git_object_free(this->obj); - git_commit_free(this->commit); - git_reference_free(this->ref); - this->commit = nullptr; - this->obj = nullptr; - this->ref = nullptr; - } - - void GitHelper::CheckoutRevision(const std::string& revision) { - if (this->repo == nullptr) - throw Error(Error::Code::git_error, "Cannot checkout revision for repository that has not been opened."); - else if (this->obj != nullptr) - throw Error(Error::Code::git_error, "Cannot fetch repository updates, object memory already allocated."); - - // Get an object ID for 'HEAD^'. - this->Call(git_revparse_single(&this->obj, this->repo, revision.c_str())); - const git_oid * oid = git_object_id(this->obj); - - // Detach HEAD to HEAD~1. This will roll back HEAD by one commit each time it is called. - this->Call(git_repository_set_head_detached(this->repo, oid)); - - // Checkout the new HEAD. - BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD."; - this->Call(git_checkout_head(this->repo, &this->checkout_options)); - - git_object_free(this->obj); - this->obj = nullptr; - } - - std::string GitHelper::GetHeadShortId() { - if (this->repo == nullptr) - throw Error(Error::Code::git_error, "Cannot checkout revision for repository that has not been opened."); - else if (this->obj != nullptr) - throw Error(Error::Code::git_error, "Cannot fetch repository updates, object memory already allocated."); - else if (this->ref != nullptr) - throw Error(Error::Code::git_error, "Cannot fetch repository updates, reference memory already allocated."); - else if (this->buf.ptr != nullptr) - throw Error(Error::Code::git_error, "Cannot fetch repository updates, buffer memory already allocated."); - - BOOST_LOG_TRIVIAL(trace) << "Getting the Git object for HEAD."; - this->Call(git_repository_head(&this->ref, this->repo)); - this->Call(git_reference_peel(&this->obj, this->ref, GIT_OBJ_COMMIT)); - - BOOST_LOG_TRIVIAL(trace) << "Generating hex string for Git object ID."; - this->Call(git_object_short_id(&this->buf, this->obj)); - string revision = this->buf.ptr; - - git_reference_free(this->ref); - git_object_free(this->obj); - git_buf_free(&this->buf); - this->ref = nullptr; - this->obj = nullptr; - this->buf = {0}; - - return revision; - } - - bool GitHelper::IsFileDifferent(const boost::filesystem::path& repoRoot, const std::string& filename) { - if (!IsRepository(repoRoot)) { - BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: Git repository missing."; - throw Error(Error::Code::ok, lc::translate("Unknown: Git repository missing")); - } - - BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it."; - GitHelper git; - git.Call(git_repository_open(&git.repo, repoRoot.string().c_str())); - - // Perform a git diff, then iterate the deltas to see if one exists for the masterlist. - BOOST_LOG_TRIVIAL(trace) << "Getting the tree for the HEAD revision."; - git.Call(git_revparse_single(&git.obj, git.repo, "HEAD^{tree}")); - git.Call(git_tree_lookup(&git.tree, git.repo, git_object_id(git.obj))); - - BOOST_LOG_TRIVIAL(trace) << "Performing git diff."; - git.Call(git_diff_tree_to_workdir_with_index(&git.diff, git.repo, git.tree, NULL)); - - BOOST_LOG_TRIVIAL(trace) << "Iterating over git diff deltas."; - GitHelper::git_diff_payload payload; - payload.fileFound = false; - payload.fileToFind = filename.c_str(); - git.Call(git_diff_foreach(git.diff, &git.diff_file_cb, NULL, NULL, NULL, &payload)); - - return payload.fileFound; + } +} + +int GitHelper::DiffFileCallback(const git_diff_delta *delta, float progress, void * payload) { + BOOST_LOG_TRIVIAL(trace) << "Checking diff for: " << delta->old_file.path; + DiffPayload * gdp = (DiffPayload*)payload; + if (strcmp(delta->old_file.path, gdp->fileToFind) == 0) { + BOOST_LOG_TRIVIAL(warning) << "Edited masterlist found."; + gdp->fileFound = true; + } + + return 0; +} + +// Clones a repository and opens it. +void GitHelper::Clone(const boost::filesystem::path& path, const std::string& url) { + if (data_.repo != nullptr) + throw Error(Error::Code::git_error, "Cannot clone repository that has already been opened."); + + SetErrorMessage(translate("An error occurred while trying to clone the remote masterlist repository.")); + // Clone the remote repository. + BOOST_LOG_TRIVIAL(info) << "Repository doesn't exist, cloning the remote repository."; + + fs::path tempPath = fs::temp_directory_path() / fs::unique_path(); + + // Create the temporary parent folder in case it doesn't already exist. + fs::create_directories(tempPath.parent_path()); + + //Delete temporary folder in case it already exists. + fs::remove_all(tempPath); + + if (!fs::is_empty(path)) { + // Directory is non-empty. Delete the masterlist file and + // .git folder, then move any remaining files to a temporary + // folder while the repo is cloned, before moving them back. + BOOST_LOG_TRIVIAL(trace) << "Repo path not empty, renaming folder."; + + // Clear any read-only flags first. + FixRepoPermissions(path); + + // Now move to temp path. + fs::rename(path, tempPath); + + // Recreate the game folder so that we don't inadvertently + // cause any other errors (everything past LOOT init assumes + // it exists). + fs::create_directory(path); + } + + // Perform the clone. + Call(git_clone(&data_.repo, url.c_str(), path.string().c_str(), &data_.clone_options)); + + if (fs::exists(tempPath)) { + //Move contents back in. + BOOST_LOG_TRIVIAL(trace) << "Repo path wasn't empty, moving previous files back in."; + for (fs::directory_iterator it(tempPath); it != fs::directory_iterator(); ++it) { + if (!fs::exists(path / it->path().filename())) { + //No conflict, OK to move back in. + fs::rename(it->path(), path / it->path().filename()); + } } + //Delete temporary folder. + fs::remove_all(tempPath); + } +} + +void GitHelper::Fetch(const std::string& remote) { + if (data_.repo == nullptr) + throw Error(Error::Code::git_error, "Cannot fetch updates for repository that has not been opened."); + + BOOST_LOG_TRIVIAL(trace) << "Fetching updates from remote."; + SetErrorMessage(translate("An error occurred while trying to update the masterlist. This could be due to a server-side error. Try again in a few minutes.")); + + // Get the origin remote. + Call(git_remote_lookup(&data_.remote, data_.repo, remote.c_str())); + + // Now fetch any updates. + git_fetch_options fetch_options = GIT_FETCH_OPTIONS_INIT; + Call(git_remote_fetch(data_.remote, nullptr, &fetch_options, nullptr)); + + // Log some stats on what was fetched either during update or clone. + const git_transfer_progress * stats = git_remote_stats(data_.remote); + BOOST_LOG_TRIVIAL(info) << "Received " << stats->indexed_objects << " of " << stats->total_objects << " objects in " << stats->received_bytes << " bytes."; + + git_remote_free(data_.remote); + data_.remote = nullptr; +} + +void GitHelper::CheckoutNewBranch(const std::string& remote, const std::string& branch) { + if (data_.repo == nullptr) + throw Error(Error::Code::git_error, "Cannot fetch updates for repository that has not been opened."); + else if (data_.commit != nullptr) + throw Error(Error::Code::git_error, "Cannot fetch repository updates, commit memory already allocated."); + else if (data_.object != nullptr) + throw Error(Error::Code::git_error, "Cannot fetch repository updates, object memory already allocated."); + else if (data_.reference != nullptr) + throw Error(Error::Code::git_error, "Cannot fetch repository updates, reference memory already allocated."); + + BOOST_LOG_TRIVIAL(trace) << "Looking up commit referred to by the remote branch \"" << branch << "\"."; + Call(git_revparse_single(&data_.object, data_.repo, (remote + "/" + branch).c_str())); + const git_oid * commit_id = git_object_id(data_.object); + + // Create a branch. + BOOST_LOG_TRIVIAL(trace) << "Creating the new branch."; + Call(git_commit_lookup(&data_.commit, data_.repo, commit_id)); + Call(git_branch_create(&data_.reference, data_.repo, branch.c_str(), data_.commit, 0)); + + // Set upstream. + BOOST_LOG_TRIVIAL(trace) << "Setting the upstream for the new branch."; + Call(git_branch_set_upstream(data_.reference, (remote + "/" + branch).c_str())); + + // Check if HEAD points to the desired branch and set it to if not. + if (!git_branch_is_head(data_.reference)) { + BOOST_LOG_TRIVIAL(trace) << "Setting HEAD to follow branch: " << branch; + Call(git_repository_set_head(data_.repo, (string("refs/heads/") + branch).c_str())); + } + + BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD."; + Call(git_checkout_head(data_.repo, &data_.checkout_options)); + + // Free tree and commit pointers. Reference pointer is still used below. + git_object_free(data_.object); + git_commit_free(data_.commit); + git_reference_free(data_.reference); + data_.commit = nullptr; + data_.object = nullptr; + data_.reference = nullptr; +} + +void GitHelper::CheckoutRevision(const std::string& revision) { + if (data_.repo == nullptr) + throw Error(Error::Code::git_error, "Cannot checkout revision for repository that has not been opened."); + else if (data_.object != nullptr) + throw Error(Error::Code::git_error, "Cannot fetch repository updates, object memory already allocated."); + +// Get an object ID for 'HEAD^'. + Call(git_revparse_single(&data_.object, data_.repo, revision.c_str())); + const git_oid * oid = git_object_id(data_.object); + + // Detach HEAD to HEAD~1. This will roll back HEAD by one commit each time it is called. + Call(git_repository_set_head_detached(data_.repo, oid)); + + // Checkout the new HEAD. + BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD."; + Call(git_checkout_head(data_.repo, &data_.checkout_options)); + + git_object_free(data_.object); + data_.object = nullptr; +} + +std::string GitHelper::GetHeadShortId() { + if (data_.repo == nullptr) + throw Error(Error::Code::git_error, "Cannot checkout revision for repository that has not been opened."); + else if (data_.object != nullptr) + throw Error(Error::Code::git_error, "Cannot fetch repository updates, object memory already allocated."); + else if (data_.reference != nullptr) + throw Error(Error::Code::git_error, "Cannot fetch repository updates, reference memory already allocated."); + else if (data_.buffer.ptr != nullptr) + throw Error(Error::Code::git_error, "Cannot fetch repository updates, buffer memory already allocated."); + + BOOST_LOG_TRIVIAL(trace) << "Getting the Git object for HEAD."; + Call(git_repository_head(&data_.reference, data_.repo)); + Call(git_reference_peel(&data_.object, data_.reference, GIT_OBJ_COMMIT)); + + BOOST_LOG_TRIVIAL(trace) << "Generating hex string for Git object ID."; + Call(git_object_short_id(&data_.buffer, data_.object)); + string revision = data_.buffer.ptr; + + git_reference_free(data_.reference); + git_object_free(data_.object); + git_buf_free(&data_.buffer); + data_.reference = nullptr; + data_.object = nullptr; + data_.buffer = {0}; + + return revision; +} + +GitHelper::GitData& GitHelper::GetData() { + return data_; +} + +bool GitHelper::IsFileDifferent(const boost::filesystem::path& repoRoot, const std::string& filename) { + if (!IsRepository(repoRoot)) { + BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: Git repository missing."; + throw Error(Error::Code::ok, translate("Unknown: Git repository missing")); + } + + BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it."; + GitHelper git; + git.Call(git_repository_open(&git.data_.repo, repoRoot.string().c_str())); + + // Perform a git diff, then iterate the deltas to see if one exists for the masterlist. + BOOST_LOG_TRIVIAL(trace) << "Getting the tree for the HEAD revision."; + git.Call(git_revparse_single(&git.data_.object, git.data_.repo, "HEAD^{tree}")); + git.Call(git_tree_lookup(&git.data_.tree, git.data_.repo, git_object_id(git.data_.object))); + + BOOST_LOG_TRIVIAL(trace) << "Performing git diff."; + git.Call(git_diff_tree_to_workdir_with_index(&git.data_.diff, git.data_.repo, git.data_.tree, NULL)); + + BOOST_LOG_TRIVIAL(trace) << "Iterating over git diff deltas."; + GitHelper::DiffPayload payload; + payload.fileFound = false; + payload.fileToFind = filename.c_str(); + git.Call(git_diff_foreach(git.data_.diff, &git.DiffFileCallback, NULL, NULL, NULL, &payload)); + + return payload.fileFound; +} } diff --git a/src/backend/helpers/git_helper.h b/src/backend/helpers/git_helper.h index 0333b084..6d538b01 100644 --- a/src/backend/helpers/git_helper.h +++ b/src/backend/helpers/git_helper.h @@ -22,69 +22,66 @@ . */ -#ifndef __LOOT_GIT_HELPER__ -#define __LOOT_GIT_HELPER__ +#ifndef LOOT_BACKEND_HELPERS_GIT_HELPER +#define LOOT_BACKEND_HELPERS_GIT_HELPER #include #include - #include namespace loot { - class GitHelper { - public: - GitHelper(); - ~GitHelper(); +class GitHelper { +public: + struct DiffPayload { + bool fileFound; + const char * fileToFind; + }; - void Call(int error_code); - void SetErrorMessage(const std::string& message); + struct GitData { + GitData(); + ~GitData(); - static bool IsRepository(const boost::filesystem::path& path); + git_repository * repo; + git_remote * remote; + git_config * config; + git_object * object; + git_commit * commit; + git_reference * reference; + git_reference * reference2; + git_blob * blob; + git_annotated_commit * annotated_commit; + git_tree * tree; + git_diff * diff; + git_buf buffer; - static bool IsFileDifferent(const boost::filesystem::path& repoRoot, const std::string& filename); + git_checkout_options checkout_options; + git_clone_options clone_options; + }; - // Clones a repository and opens it. Sets 'repo'. - void Clone(const boost::filesystem::path& path, const std::string& url); + void Call(int error_code); + void SetErrorMessage(const std::string& message); - // Fetch from remote. - void Fetch(const std::string& remote); + static bool IsRepository(const boost::filesystem::path& path); + static bool IsFileDifferent(const boost::filesystem::path& repoRoot, const std::string& filename); + static int DiffFileCallback(const git_diff_delta *delta, float progress, void * payload); - // Create and checkout a new remote-tracking branch. - void CheckoutNewBranch(const std::string& remote, const std::string& branch); + void Clone(const boost::filesystem::path& path, const std::string& url); + void Fetch(const std::string& remote); - void CheckoutRevision(const std::string& revision); + void CheckoutNewBranch(const std::string& remote, const std::string& branch); + void CheckoutRevision(const std::string& revision); - std::string GetHeadShortId(); + std::string GetHeadShortId(); + GitData& GetData(); - git_repository * repo; - git_remote * remote; - git_config * cfg; - git_object * obj; - git_commit * commit; - git_reference * ref; - git_reference * ref2; - git_blob * blob; - git_annotated_commit * annotated_commit; - git_tree * tree; - git_diff * diff; - git_buf buf; +private: + // Removes the read-only flag from some files in git repositories + // created by libgit2. + static void FixRepoPermissions(const boost::filesystem::path& path); - git_checkout_options checkout_options; - git_clone_options clone_options; - - struct git_diff_payload { - bool fileFound; - const char * fileToFind; - }; - - static int diff_file_cb(const git_diff_delta *delta, float progress, void * payload); - private: - std::string errorMessage; - - // Removes the read-only flag from some files in git repositories - // created by libgit2. - static void FixRepoPermissions(const boost::filesystem::path& path); - }; + GitData data_; + std::string errorMessage_; +}; } #endif diff --git a/src/backend/helpers/helpers.cpp b/src/backend/helpers/helpers.cpp index f29f28b8..55ee83d6 100644 --- a/src/backend/helpers/helpers.cpp +++ b/src/backend/helpers/helpers.cpp @@ -22,25 +22,26 @@ . */ -#include "helpers.h" -#include "../error.h" +#include "backend/helpers/helpers.h" -#include -#include -#include -#include -#include -#include -#include - -#include #include #include #include +#include #include #include #include +#include +#include +#include +#include +#include +#include +#include + +#include "backend/error.h" + #ifdef _WIN32 # ifndef UNICODE # define UNICODE @@ -53,136 +54,129 @@ # include "shlwapi.h" #endif +using boost::locale::translate; +using std::string; +using std::wstring; + namespace loot { - using namespace std; - using boost::algorithm::replace_all; - using boost::algorithm::replace_first; - namespace karma = boost::spirit::karma; - namespace fs = boost::filesystem; - namespace lc = boost::locale; - - ////////////////////////////////////////////////////////////////////////// - // Helper functions - ////////////////////////////////////////////////////////////////////////// - //Calculate the CRC of the given file for comparison purposes. - uint32_t GetCrc32(const fs::path& filename) { - uint32_t chksum = 0; - try { - fs::ifstream ifile(filename, ios::binary); - BOOST_LOG_TRIVIAL(trace) << "Calculating CRC for: " << filename.string(); - boost::crc_32_type result; - if (ifile) { - static const size_t buffer_size = 8192; - char buffer[buffer_size]; - do { - ifile.read(buffer, buffer_size); - result.process_bytes(buffer, ifile.gcount()); - } while (ifile); - chksum = result.checksum(); - } - else - throw exception(); - } - catch (exception&) { - BOOST_LOG_TRIVIAL(error) << "Unable to open \"" << filename.string() << "\" for CRC calculation."; - throw Error(Error::Code::path_read_fail, (boost::format(lc::translate("Unable to open \"%1%\" for CRC calculation.")) % filename.string()).str()); - } - BOOST_LOG_TRIVIAL(debug) << "CRC32(\"" << filename.string() << "\"): " << std::hex << chksum << std::dec; - return chksum; - } +uint32_t GetCrc32(const boost::filesystem::path& filename) { + uint32_t chksum = 0; + try { + boost::filesystem::ifstream ifile(filename, std::ios::binary); + BOOST_LOG_TRIVIAL(trace) << "Calculating CRC for: " << filename.string(); + boost::crc_32_type result; + if (ifile) { + static const size_t buffer_size = 8192; + char buffer[buffer_size]; + do { + ifile.read(buffer, buffer_size); + result.process_bytes(buffer, ifile.gcount()); + } while (ifile); + chksum = result.checksum(); + } else + throw std::exception(); + } catch (std::exception&) { + BOOST_LOG_TRIVIAL(error) << "Unable to open \"" << filename.string() << "\" for CRC calculation."; + throw Error(Error::Code::path_read_fail, (boost::format(translate("Unable to open \"%1%\" for CRC calculation.")) % filename.string()).str()); + } + BOOST_LOG_TRIVIAL(debug) << "CRC32(\"" << filename.string() << "\"): " << std::hex << chksum << std::dec; + return chksum; +} - //Converts an integer to a hex string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion... - std::string IntToHexString(const uint32_t n) { - string out; - back_insert_iterator sink(out); - karma::generate(sink, karma::upper[karma::hex], n); - return out; - } +//Converts an integer to a hex string using BOOST's Spirit.Karma, which is apparently a lot faster than a stringstream conversion... +std::string IntToHexString(const uint32_t n) { + namespace karma = boost::spirit::karma; - //Turns an absolute filesystem path into a valid file:// URL. - std::string ToFileURL(const fs::path& file) { - BOOST_LOG_TRIVIAL(trace) << "Converting file path " << file << " to a URL."; - string url; + string out; + std::back_insert_iterator sink(out); + karma::generate(sink, karma::upper[karma::hex], n); + + return out; +} + +//Turns an absolute filesystem path into a valid file:// URL. +std::string ToFileURL(const boost::filesystem::path& file) { + BOOST_LOG_TRIVIAL(trace) << "Converting file path " << file << " to a URL."; + string url; #ifdef _WIN32 - wstring wstr(MAX_PATH, 0); - DWORD len = MAX_PATH; - UrlCreateFromPath(ToWinWide(file.string()).c_str(), &wstr[0], &len, NULL); - url = FromWinWide(wstr.c_str()); // Passing c_str() cuts off any unused buffer. - BOOST_LOG_TRIVIAL(trace) << "Converted to: " << url; + wstring wstr(MAX_PATH, 0); + DWORD len = MAX_PATH; + UrlCreateFromPath(ToWinWide(file.string()).c_str(), &wstr[0], &len, NULL); + url = FromWinWide(wstr.c_str()); // Passing c_str() cuts off any unused buffer. + BOOST_LOG_TRIVIAL(trace) << "Converted to: " << url; #else // Let's be naive about this. - url = "file://" + file.string(); + url = "file://" + file.string(); #endif - return url; - } + return url; +} - //Opens the file in its registered default application. - void OpenInDefaultApplication(const boost::filesystem::path& file) { +//Opens the file in its registered default application. +void OpenInDefaultApplication(const boost::filesystem::path& file) { #ifdef _WIN32 - HINSTANCE ret = ShellExecute(0, NULL, ToWinWide(file.string()).c_str(), NULL, NULL, SW_SHOWNORMAL); - if ((int)ret <= 32) - throw Error(Error::Code::windows_error, lc::translate("Failed to open file in its default application.")); + HINSTANCE ret = ShellExecute(0, NULL, ToWinWide(file.string()).c_str(), NULL, NULL, SW_SHOWNORMAL); + if ((int)ret <= 32) + throw Error(Error::Code::windows_error, translate("Failed to open file in its default application.")); #else - if (system(("/usr/bin/xdg-open" + file.string()).c_str()) != 0) - throw Error(Error::Code::windows_error, lc::translate("Failed to open file in its default application.")); + if (system(("/usr/bin/xdg-open" + file.string()).c_str()) != 0) + throw Error(Error::Code::windows_error, translate("Failed to open file in its default application.")); #endif - } +} #ifdef _WIN32 //Get registry subkey value string. - string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value) { - HKEY hKey = NULL; - DWORD len = MAX_PATH; - wstring wstr(MAX_PATH, 0); +std::string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value) { + HKEY hKey = NULL; + DWORD len = MAX_PATH; + wstring wstr(MAX_PATH, 0); - if (keyStr == "HKEY_CLASSES_ROOT") - hKey = HKEY_CLASSES_ROOT; - else if (keyStr == "HKEY_CURRENT_CONFIG") - hKey = HKEY_CURRENT_CONFIG; - else if (keyStr == "HKEY_CURRENT_USER") - hKey = HKEY_CURRENT_USER; - else if (keyStr == "HKEY_LOCAL_MACHINE") - hKey = HKEY_LOCAL_MACHINE; - else if (keyStr == "HKEY_USERS") - hKey = HKEY_USERS; - else - throw Error(Error::Code::invalid_args, "Invalid registry key given."); + if (keyStr == "HKEY_CLASSES_ROOT") + hKey = HKEY_CLASSES_ROOT; + else if (keyStr == "HKEY_CURRENT_CONFIG") + hKey = HKEY_CURRENT_CONFIG; + else if (keyStr == "HKEY_CURRENT_USER") + hKey = HKEY_CURRENT_USER; + else if (keyStr == "HKEY_LOCAL_MACHINE") + hKey = HKEY_LOCAL_MACHINE; + else if (keyStr == "HKEY_USERS") + hKey = HKEY_USERS; + else + throw Error(Error::Code::invalid_args, "Invalid registry key given."); - BOOST_LOG_TRIVIAL(trace) << "Getting string for registry key, subkey and value: " << keyStr << " + " << subkey << " + " << value; - LONG ret = RegGetValue(hKey, - ToWinWide(subkey).c_str(), - ToWinWide(value).c_str(), - RRF_RT_REG_SZ | KEY_WOW64_32KEY, - NULL, - &wstr[0], - &len); + BOOST_LOG_TRIVIAL(trace) << "Getting string for registry key, subkey and value: " << keyStr << " + " << subkey << " + " << value; + LONG ret = RegGetValue(hKey, + ToWinWide(subkey).c_str(), + ToWinWide(value).c_str(), + RRF_RT_REG_SZ | KEY_WOW64_32KEY, + NULL, + &wstr[0], + &len); - if (ret == ERROR_SUCCESS) { - BOOST_LOG_TRIVIAL(info) << "Found string: " << wstr.c_str(); - return FromWinWide(wstr.c_str()); // Passing c_str() cuts off any unused buffer. - } - else { - BOOST_LOG_TRIVIAL(error) << "Failed to get string value."; - return ""; - } - } + if (ret == ERROR_SUCCESS) { + BOOST_LOG_TRIVIAL(info) << "Found string: " << wstr.c_str(); + return FromWinWide(wstr.c_str()); // Passing c_str() cuts off any unused buffer. + } else { + BOOST_LOG_TRIVIAL(error) << "Failed to get string value."; + return ""; + } +} - //Helper to turn UTF8 strings into strings that can be used by WinAPI. - std::wstring ToWinWide(const std::string& str) { - size_t len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), 0, 0); - std::wstring wstr(len, 0); - MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &wstr[0], len); - return wstr; - } +//Helper to turn UTF8 strings into strings that can be used by WinAPI. +std::wstring ToWinWide(const std::string& str) { + size_t len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), 0, 0); + wstring wstr(len, 0); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), str.length(), &wstr[0], len); + return wstr; +} - std::string FromWinWide(const std::wstring& wstr) { - size_t len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.length(), NULL, 0, NULL, NULL); - std::string str(len, 0); - WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.length(), &str[0], len, NULL, NULL); - return str; - } +std::string FromWinWide(const std::wstring& wstr) { + size_t len = WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.length(), NULL, 0, NULL, NULL); + string str(len, 0); + WideCharToMultiByte(CP_UTF8, 0, wstr.c_str(), wstr.length(), &str[0], len, NULL, NULL); + return str; +} #endif } diff --git a/src/backend/helpers/helpers.h b/src/backend/helpers/helpers.h index 338df43a..83efb935 100644 --- a/src/backend/helpers/helpers.h +++ b/src/backend/helpers/helpers.h @@ -22,34 +22,35 @@ . */ -#ifndef __LOOT_HELPERS__ -#define __LOOT_HELPERS__ +#ifndef LOOT_BACKEND_HELPERS_HELPERS +#define LOOT_BACKEND_HELPERS_HELPERS -#include #include +#include + #include namespace loot { //Calculate the CRC of the given file for comparison purposes. - uint32_t GetCrc32(const boost::filesystem::path& filename); +uint32_t GetCrc32(const boost::filesystem::path& filename); - //Converts an unsigned 32-bit integer to a hex string using BOOST's Spirit.Karma. Faster than a stringstream conversion. - std::string IntToHexString(const uint32_t n); +//Converts an unsigned 32-bit integer to a hex string using BOOST's Spirit.Karma. Faster than a stringstream conversion. +std::string IntToHexString(const uint32_t n); - //Turns an absolute filesystem path into a valid file:// URL. - std::string ToFileURL(const boost::filesystem::path& file); +//Turns an absolute filesystem path into a valid file:// URL. +std::string ToFileURL(const boost::filesystem::path& file); - //Opens the file in its registered default application. - void OpenInDefaultApplication(const boost::filesystem::path& file); +//Opens the file in its registered default application. +void OpenInDefaultApplication(const boost::filesystem::path& file); #ifdef _WIN32 //Get registry subkey value string. - std::string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value); +std::string RegKeyStringValue(const std::string& keyStr, const std::string& subkey, const std::string& value); - //Helper to turn UTF8 strings into strings that can be used by WinAPI. - std::wstring ToWinWide(const std::string& str); +//Helper to turn UTF8 strings into strings that can be used by WinAPI. +std::wstring ToWinWide(const std::string& str); - std::string FromWinWide(const std::wstring& wstr); +std::string FromWinWide(const std::wstring& wstr); #endif } diff --git a/src/backend/helpers/json.h b/src/backend/helpers/json.h index 24dec192..78bb36b4 100644 --- a/src/backend/helpers/json.h +++ b/src/backend/helpers/json.h @@ -22,54 +22,51 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_JSON__ -#define __LOOT_JSON__ - -#include +#ifndef LOOT_BACKEND_HELPERS_JSON +#define LOOT_BACKEND_HELPERS_JSON #include + #include +#include namespace loot { - // Handy class for turning YAML objects into JSON and vice-versa. - class JSON { - public: +namespace JSON { +YAML::Node parse(std::string& json) { + return YAML::Load(json); +} - inline static YAML::Node parse(std::string& json) { - return YAML::Load(json); - } +std::string stringify(const YAML::Node& yaml) { + YAML::Emitter out; + out.SetOutputCharset(YAML::EscapeNonAscii); + out.SetStringFormat(YAML::DoubleQuoted); + out.SetBoolFormat(YAML::TrueFalseBool); + out.SetSeqFormat(YAML::Flow); + out.SetMapFormat(YAML::Flow); - inline static std::string stringify(const YAML::Node& yaml) { - YAML::Emitter out; - out.SetOutputCharset(YAML::EscapeNonAscii); - out.SetStringFormat(YAML::DoubleQuoted); - out.SetBoolFormat(YAML::TrueFalseBool); - out.SetSeqFormat(YAML::Flow); - out.SetMapFormat(YAML::Flow); + out << yaml; - out << yaml; + // yaml-cpp produces `!` artifacts in its output, so remove them. + std::string json = out.c_str(); + boost::replace_all(json, "! ", ""); + // There's also a bit of weirdness where there are some \x escapes and some \u escapes. + // They should all be \u, so transform them. + boost::replace_all(json, "\\x", "\\u00"); + // yaml-cpp also emits booleans as "true" and "false" strings, whereas JSON expects the same unquoted basic values. The same happens for null and numbers. + boost::replace_all(json, "\": \"true\"", "\": true"); + boost::replace_all(json, "\": \"false\"", "\": false"); + boost::replace_all(json, "\": \"null\"", "\": null"); + boost::replace_all(json, "\": ~", "\": null"); - // yaml-cpp produces `!` artifacts in its output, so remove them. - std::string json = out.c_str(); - boost::replace_all(json, "! ", ""); - // There's also a bit of weirdness where there are some \x escapes and some \u escapes. - // They should all be \u, so transform them. - boost::replace_all(json, "\\x", "\\u00"); - // yaml-cpp also emits booleans as "true" and "false" strings, whereas JSON expects the same unquoted basic values. The same happens for null and numbers. - boost::replace_all(json, "\": \"true\"", "\": true"); - boost::replace_all(json, "\": \"false\"", "\": false"); - boost::replace_all(json, "\": \"null\"", "\": null"); - boost::replace_all(json, "\": ~", "\": null"); + // Using the definition at . + // Version numbers and revision IDs should be kept as strings though. + std::regex numbers("\"(?!version|revision)([^\"]+)\": \"(-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)\"", std::regex::ECMAScript); - // Using the definition at . - // Version numbers and revision IDs should be kept as strings though. - std::regex numbers("\"(?!version|revision)([^\"]+)\": \"(-?(?:0|[1-9]\\d*)(?:\\.\\d+)?(?:[eE][+-]?\\d+)?)\"", std::regex::ECMAScript); + json = std::regex_replace(json, numbers, "\"$1\": $2"); - json = std::regex_replace(json, numbers, "\"$1\": $2"); - - return json; - } - }; + return json; +} +} } #endif diff --git a/src/backend/helpers/language.cpp b/src/backend/helpers/language.cpp index 32564607..4dc78aa9 100644 --- a/src/backend/helpers/language.cpp +++ b/src/backend/helpers/language.cpp @@ -22,97 +22,86 @@ . */ -#include "language.h" +#include "backend/helpers/language.h" namespace loot { +const std::vector Language::codes({ + Code::english, + Code::spanish, + Code::russian, + Code::french, + Code::chinese, + Code::polish, + Code::brazilian_portuguese, + Code::finnish, + Code::german, + Code::danish, + Code::korean +}); - Language::Language(const Code code) { - Construct(code); - } - - Language::Language(const std::string& locale) { - for (Code code : Codes) { - if (locale == Language(code).GetLocale()) { - Construct(code); - return; - } - } +Language::Language(Code code) { + Construct(code); +} - Construct(Code::english); +Language::Language(const std::string& locale) { + for (Code code : codes) { + if (locale == Language(code).GetLocale()) { + Construct(code); + return; } + } - void Language::Construct(const Code code) { - _code = code; - if (_code == Code::spanish) { - _name = "Español"; - _locale = "es"; - } - else if (_code == Code::russian) { - _name = "Русский"; - _locale = "ru"; - } - else if (_code == Code::french) { - _name = "Français"; - _locale = "fr"; - } - else if (_code == Code::chinese) { - _name = "简体中文"; - _locale = "zh_CN"; - } - else if (_code == Code::polish) { - _name = "Polski"; - _locale = "pl"; - } - else if (_code == Code::brazilian_portuguese) { - _name = "Português do Brasil"; - _locale = "pt_BR"; - } - else if (_code == Code::finnish) { - _name = "suomi"; - _locale = "fi"; - } - else if (_code == Code::german) { - _name = "Deutsch"; - _locale = "de"; - } - else if (_code == Code::danish) { - _name = "Dansk"; - _locale = "da"; - } - else if (_code == Code::korean) { - _name = "한국어"; - _locale = "ko"; - } - else { - _code = Code::english; - _name = "English"; - _locale = "en"; - } - } + Construct(Code::english); +} - Language::Code Language::GetCode() const { - return _code; - } +void Language::Construct(const Code code) { + code_ = code; + if (code_ == Code::spanish) { + name_ = "Español"; + locale_ = "es"; + } else if (code_ == Code::russian) { + name_ = "Русский"; + locale_ = "ru"; + } else if (code_ == Code::french) { + name_ = "Français"; + locale_ = "fr"; + } else if (code_ == Code::chinese) { + name_ = "简体中文"; + locale_ = "zh_CN"; + } else if (code_ == Code::polish) { + name_ = "Polski"; + locale_ = "pl"; + } else if (code_ == Code::brazilian_portuguese) { + name_ = "Português do Brasil"; + locale_ = "pt_BR"; + } else if (code_ == Code::finnish) { + name_ = "suomi"; + locale_ = "fi"; + } else if (code_ == Code::german) { + name_ = "Deutsch"; + locale_ = "de"; + } else if (code_ == Code::danish) { + name_ = "Dansk"; + locale_ = "da"; + } else if (code_ == Code::korean) { + name_ = "한국어"; + locale_ = "ko"; + } else { + code_ = Code::english; + name_ = "English"; + locale_ = "en"; + } +} - std::string Language::GetName() const { - return _name; - } +Language::Code Language::GetCode() const { + return code_; +} - std::string Language::GetLocale() const { - return _locale; - } +std::string Language::GetName() const { + return name_; +} - const std::vector Language::Codes({ - Code::english, - Code::spanish, - Code::russian, - Code::french, - Code::chinese, - Code::polish, - Code::brazilian_portuguese, - Code::finnish, - Code::german, - Code::danish, - Code::korean - }); +std::string Language::GetLocale() const { + return locale_; +} } diff --git a/src/backend/helpers/language.h b/src/backend/helpers/language.h index fb63a9a0..93c82f2f 100644 --- a/src/backend/helpers/language.h +++ b/src/backend/helpers/language.h @@ -22,45 +22,45 @@ . */ -#ifndef __LOOT_LANGUAGE__ -#define __LOOT_LANGUAGE__ +#ifndef LOOT_BACKEND_HELPERS_LANGUAGE +#define LOOT_BACKEND_HELPERS_LANGUAGE #include #include namespace loot { //Language class for simpler language support. - class Language { - public: - enum struct Code : unsigned int { - english = 1, - spanish = 2, - russian = 3, - french = 4, - chinese = 5, - polish = 6, - brazilian_portuguese = 7, - finnish = 8, - german = 9, - danish = 10, - korean = 11, - }; +class Language { +public: + enum struct Code : unsigned int { + english = 1, + spanish = 2, + russian = 3, + french = 4, + chinese = 5, + polish = 6, + brazilian_portuguese = 7, + finnish = 8, + german = 9, + danish = 10, + korean = 11 + }; - Language(const Code code); - Language(const std::string& locale); + static const std::vector codes; - Code GetCode() const; - std::string GetName() const; - std::string GetLocale() const; + Language(const Code code); + Language(const std::string& locale); - static const std::vector Codes; - private: - Code _code; - std::string _name; - std::string _locale; + Code GetCode() const; + std::string GetName() const; + std::string GetLocale() const; +private: + void Construct(const Code code); - void Construct(const Code code); - }; + Code code_; + std::string name_; + std::string locale_; +}; } #endif diff --git a/src/backend/helpers/version.cpp b/src/backend/helpers/version.cpp index 4ad46097..a1e65e7d 100644 --- a/src/backend/helpers/version.cpp +++ b/src/backend/helpers/version.cpp @@ -21,15 +21,15 @@ along with LOOT. If not, see . */ - -#include "helpers.h" -#include "version.h" +#include "backend/helpers/version.h" #include #include #include +#include "backend/helpers/helpers.h" + #ifdef _WIN32 # ifndef UNICODE # define UNICODE @@ -40,126 +40,126 @@ # include "windows.h" #endif -namespace loot { - using namespace std; +using std::regex; +namespace loot { /* The string below matches timestamps that use forwardslashes for date separators. However, Pseudosem v1.0.1 will only compare the first two digits as it does not recognise forwardslashes as separators. */ - const std::string dateRegex = R"((\d{1,2}/\d{1,2}/\d{1,4} \d{1,2}:\d{1,2}:\d{1,2}))"; +const std::string dateRegex = R"((\d{1,2}/\d{1,2}/\d{1,4} \d{1,2}:\d{1,2}:\d{1,2}))"; - /* The string below matches the range of version strings supported by - Pseudosem v1.0.1, excluding space separators, as they make version - extraction from inside sentences very tricky and have not been - seen "in the wild". */ - const std::string pseudosemVersionRegex = - R"((\d+(?:\.\d+)+(?:[-._:]?[A-Za-z0-9]+)*))" - // The string below prevents version numbers followed by a comma from - // matching. - R"((?!,))"; +/* The string below matches the range of version strings supported by + Pseudosem v1.0.1, excluding space separators, as they make version + extraction from inside sentences very tricky and have not been + seen "in the wild". */ +const std::string pseudosemVersionRegex = +R"((\d+(?:\.\d+)+(?:[-._:]?[A-Za-z0-9]+)*))" +// The string below prevents version numbers followed by a comma from +// matching. +R"((?!,))"; - /* There are a few different version formats that can appear in strings - together, and in order to extract the correct one, they must be searched - for in order of priority. */ - const vector Version::versionRegexes({ - regex(dateRegex, regex::ECMAScript | regex::icase), - regex(R"(version:?\s)" + - pseudosemVersionRegex, regex::ECMAScript | regex::icase), - regex(R"((?:^|v|\s))" + - pseudosemVersionRegex, regex::ECMAScript | regex::icase), - regex( - /* The string below matches a number containing one or more digits - found at the start of the search string or preceded by 'v'. */ - R"((?:^|v)(\d+))", regex::ECMAScript | regex::icase), - }); +/* There are a few different version formats that can appear in strings + together, and in order to extract the correct one, they must be searched + for in order of priority. */ +const std::vector Version::versionRegexes({ + regex(dateRegex, regex::ECMAScript | regex::icase), + regex(R"(version:?\s)" + + pseudosemVersionRegex, regex::ECMAScript | regex::icase), + regex(R"((?:^|v|\s))" + + pseudosemVersionRegex, regex::ECMAScript | regex::icase), + regex( + /* The string below matches a number containing one or more digits + found at the start of the search string or preceded by 'v'. */ + R"((?:^|v)(\d+))", regex::ECMAScript | regex::icase), +}); - Version::Version() {} +Version::Version() {} - Version::Version(const std::string& ver) { - smatch what; - for (const auto& versionRegex : versionRegexes) { - if (regex_search(ver, what, versionRegex)) { - for (auto it = next(begin(what)); it != end(what); ++it) { - if (it->str().empty()) - continue; +Version::Version(const std::string& ver) { + std::smatch what; + for (const auto& versionRegex : versionRegexes) { + if (std::regex_search(ver, what, versionRegex)) { + for (auto it = next(begin(what)); it != end(what); ++it) { + if (it->str().empty()) + continue; - //Use the first non-empty sub-match. - verString = *it; - boost::trim(verString); - return; - } - } - } + //Use the first non-empty sub-match. + verString_ = *it; + boost::trim(verString_); + return; + } } + } +} - Version::Version(const boost::filesystem::path& file) { +Version::Version(const boost::filesystem::path& file) { #ifdef _WIN32 - DWORD dummy = 0; - DWORD size = GetFileVersionInfoSize(ToWinWide(file.string()).c_str(), &dummy); + DWORD dummy = 0; + DWORD size = GetFileVersionInfoSize(ToWinWide(file.string()).c_str(), &dummy); - if (size > 0) { - LPBYTE point = new BYTE[size]; - UINT uLen; - VS_FIXEDFILEINFO *info; + if (size > 0) { + LPBYTE point = new BYTE[size]; + UINT uLen; + VS_FIXEDFILEINFO *info; - GetFileVersionInfo(ToWinWide(file.string()).c_str(), 0, size, point); + GetFileVersionInfo(ToWinWide(file.string()).c_str(), 0, size, point); - VerQueryValue(point, L"\\", (LPVOID *)&info, &uLen); + VerQueryValue(point, L"\\", (LPVOID *)&info, &uLen); - DWORD dwLeftMost = HIWORD(info->dwFileVersionMS); - DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS); - DWORD dwSecondRight = HIWORD(info->dwFileVersionLS); - DWORD dwRightMost = LOWORD(info->dwFileVersionLS); + DWORD dwLeftMost = HIWORD(info->dwFileVersionMS); + DWORD dwSecondLeft = LOWORD(info->dwFileVersionMS); + DWORD dwSecondRight = HIWORD(info->dwFileVersionLS); + DWORD dwRightMost = LOWORD(info->dwFileVersionLS); - delete[] point; + delete[] point; - verString = to_string(dwLeftMost) + '.' + to_string(dwSecondLeft) + '.' + to_string(dwSecondRight) + '.' + to_string(dwRightMost); - } + verString_ = std::to_string(dwLeftMost) + '.' + std::to_string(dwSecondLeft) + '.' + std::to_string(dwSecondRight) + '.' + std::to_string(dwRightMost); + } #else // ensure filename has no quote characters in it to avoid command injection attacks - if (string::npos != file.string().find('"')) { - // command mostly borrowed from the gnome-exe-thumbnailer.sh script - // wrestool is part of the icoutils package - string cmd = "wrestool --extract --raw --type=version \"" + file.string() + "\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' | sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'"; + if (std::string::npos != file.string().find('"')) { + // command mostly borrowed from the gnome-exe-thumbnailer.sh script + // wrestool is part of the icoutils package + std::string cmd = "wrestool --extract --raw --type=version \"" + file.string() + "\" | tr '\\0, ' '\\t.\\0' | sed 's/\\t\\t/_/g' | tr -c -d '[:print:]' | sed -r 's/.*Version[^0-9]*([0-9]+(\\.[0-9]+)+).*/\\1/'"; - FILE *fp = popen(cmd.c_str(), "r"); + FILE *fp = popen(cmd.c_str(), "r"); - // read out the version string - static const uint32_t BUFSIZE = 32; - char buf[BUFSIZE]; - if (nullptr != fgets(buf, BUFSIZE, fp)) { - verString = string(buf); - } - pclose(fp); - } + // read out the version string + static const uint32_t BUFSIZE = 32; + char buf[BUFSIZE]; + if (nullptr != fgets(buf, BUFSIZE, fp)) { + verString_ = std::string(buf); + } + pclose(fp); + } #endif - } +} - string Version::AsString() const { - return verString; - } +std::string Version::AsString() const { + return verString_; +} - bool Version::operator < (const Version& ver) const { - return pseudosem::compare(this->verString, ver.AsString()) < 0; - } +bool Version::operator < (const Version& ver) const { + return pseudosem::compare(this->verString_, ver.AsString()) < 0; +} - bool Version::operator > (const Version& ver) const { - return pseudosem::compare(this->verString, ver.AsString()) > 0; - } +bool Version::operator > (const Version& ver) const { + return pseudosem::compare(this->verString_, ver.AsString()) > 0; +} - bool Version::operator >= (const Version& ver) const { - return pseudosem::compare(this->verString, ver.AsString()) >= 0; - } +bool Version::operator >= (const Version& ver) const { + return pseudosem::compare(this->verString_, ver.AsString()) >= 0; +} - bool Version::operator <= (const Version& ver) const { - return pseudosem::compare(this->verString, ver.AsString()) <= 0; - } +bool Version::operator <= (const Version& ver) const { + return pseudosem::compare(this->verString_, ver.AsString()) <= 0; +} - bool Version::operator == (const Version& ver) const { - return pseudosem::compare(this->verString, ver.AsString()) == 0; - } +bool Version::operator == (const Version& ver) const { + return pseudosem::compare(this->verString_, ver.AsString()) == 0; +} - bool Version::operator != (const Version& ver) const { - return pseudosem::compare(this->verString, ver.AsString()) != 0; - } +bool Version::operator != (const Version& ver) const { + return pseudosem::compare(this->verString_, ver.AsString()) != 0; +} } diff --git a/src/backend/helpers/version.h b/src/backend/helpers/version.h index 6ca88e1c..0e61ed18 100644 --- a/src/backend/helpers/version.h +++ b/src/backend/helpers/version.h @@ -22,33 +22,34 @@ . */ -#ifndef __LOOT_VERSION__ -#define __LOOT_VERSION__ +#ifndef LOOT_BACKEND_HELPERS_VERSION +#define LOOT_BACKEND_HELPERS_VERSION #include #include + #include namespace loot { //Version class for more robust version comparisons. - class Version { - public: - Version(); - Version(const std::string& ver); - Version(const boost::filesystem::path& file); +class Version { +public: + Version(); + Version(const std::string& ver); + Version(const boost::filesystem::path& file); - std::string AsString() const; + std::string AsString() const; - bool operator > (const Version&) const; - bool operator < (const Version&) const; - bool operator >= (const Version&) const; - bool operator <= (const Version&) const; - bool operator == (const Version&) const; - bool operator != (const Version&) const; - private: - std::string verString; - static const std::vector versionRegexes; - }; + bool operator > (const Version&) const; + bool operator < (const Version&) const; + bool operator >= (const Version&) const; + bool operator <= (const Version&) const; + bool operator == (const Version&) const; + bool operator != (const Version&) const; +private: + std::string verString_; + static const std::vector versionRegexes; +}; } #endif diff --git a/src/backend/helpers/yaml_set_helpers.h b/src/backend/helpers/yaml_set_helpers.h index 206f5e61..59861347 100644 --- a/src/backend/helpers/yaml_set_helpers.h +++ b/src/backend/helpers/yaml_set_helpers.h @@ -22,8 +22,8 @@ . */ -#ifndef __LOOT_YAML_SET_HELPERS__ -#define __LOOT_YAML_SET_HELPERS__ +#ifndef LOOT_BACKEND_HELPERS_YAML_SET_HELPERS +#define LOOT_BACKEND_HELPERS_YAML_SET_HELPERS #include #include @@ -31,73 +31,73 @@ #include namespace YAML { - template - struct convert < std::set > { - static Node encode(const std::set& rhs) { - Node node; - for (const auto &element : rhs) { - node.push_back(element); - } - return node; - } - - static bool decode(const Node& node, std::set& rhs) { - if (!node.IsSequence()) - throw RepresentationException(node.Mark(), "bad conversion: set must be a sequence of elements"); - - rhs.clear(); - for (const auto &element : node) { - if (!rhs.insert(element.template as()).second) - throw RepresentationException(node.Mark(), "bad conversion: set elements must be unique"); - } - return true; - } - }; - - template - Emitter& operator << (Emitter& out, const std::set& rhs) { - out << BeginSeq; - for (const auto &element : rhs) { - out << element; - } - out << EndSeq; - - return out; +template +struct convert> { + static Node encode(const std::set& rhs) { + Node node; + for (const auto &element : rhs) { + node.push_back(element); } + return node; + } - template - struct convert < std::unordered_set > { - static Node encode(const std::unordered_set& rhs) { - Node node; - for (const auto &element : rhs) { - node.push_back(element); - } - return node; - } + static bool decode(const Node& node, std::set& rhs) { + if (!node.IsSequence()) + throw RepresentationException(node.Mark(), "bad conversion: set must be a sequence of elements"); - static bool decode(const Node& node, std::unordered_set& rhs) { - if (!node.IsSequence()) - throw RepresentationException(node.Mark(), "bad conversion: unordered set must be a sequence of elements"); - - rhs.clear(); - for (const auto &element : node) { - if (!rhs.insert(element.template as()).second) - throw RepresentationException(node.Mark(), "bad conversion: unordered set elements must be unique"); - } - return true; - } - }; - - template - Emitter& operator << (Emitter& out, const std::unordered_set& rhs) { - out << BeginSeq; - for (const auto &element : rhs) { - out << element; - } - out << EndSeq; - - return out; + rhs.clear(); + for (const auto &element : node) { + if (!rhs.insert(element.template as()).second) + throw RepresentationException(node.Mark(), "bad conversion: set elements must be unique"); } + return true; + } +}; + +template +Emitter& operator << (Emitter& out, const std::set& rhs) { + out << BeginSeq; + for (const auto &element : rhs) { + out << element; + } + out << EndSeq; + + return out; +} + +template +struct convert> { + static Node encode(const std::unordered_set& rhs) { + Node node; + for (const auto &element : rhs) { + node.push_back(element); + } + return node; + } + + static bool decode(const Node& node, std::unordered_set& rhs) { + if (!node.IsSequence()) + throw RepresentationException(node.Mark(), "bad conversion: unordered set must be a sequence of elements"); + + rhs.clear(); + for (const auto &element : node) { + if (!rhs.insert(element.template as()).second) + throw RepresentationException(node.Mark(), "bad conversion: unordered set elements must be unique"); + } + return true; + } +}; + +template +Emitter& operator << (Emitter& out, const std::unordered_set& rhs) { + out << BeginSeq; + for (const auto &element : rhs) { + out << element; + } + out << EndSeq; + + return out; +} } #endif diff --git a/src/backend/masterlist.cpp b/src/backend/masterlist.cpp index 57ffa6ec..54ba2429 100644 --- a/src/backend/masterlist.cpp +++ b/src/backend/masterlist.cpp @@ -22,254 +22,249 @@ . */ -#include "masterlist.h" -#include "helpers/git_helper.h" -#include "game/game.h" -#include "error.h" +#include "backend/masterlist.h" #include -using namespace std; +#include "backend/error.h" +#include "backend/game/game.h" +#include "backend/helpers/git_helper.h" + +using boost::locale::translate; +using std::string; namespace fs = boost::filesystem; -namespace lc = boost::locale; namespace loot { - Masterlist::Info Masterlist::GetInfo(const boost::filesystem::path& path, bool shortID) { - // Compare HEAD and working copy, and get revision info. - GitHelper git; - Info info; - git.SetErrorMessage((boost::format(lc::translate("An error occurred while trying to read the local masterlist's version. If this error happens again, try deleting the \".git\" folder in %1%.")) % path.parent_path().string()).str()); +Masterlist::Info Masterlist::GetInfo(const boost::filesystem::path& path, bool shortID) { + // Compare HEAD and working copy, and get revision info. + GitHelper git; + Info info; + git.SetErrorMessage((boost::format(translate("An error occurred while trying to read the local masterlist's version. If this error happens again, try deleting the \".git\" folder in %1%.")) % path.parent_path().string()).str()); - if (!fs::exists(path)) { - BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: No masterlist present."; - throw Error(Error::Code::ok, lc::translate("N/A: No masterlist present")); - } - else if (!git.IsRepository(path.parent_path())) { - BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: Git repository missing."; - throw Error(Error::Code::ok, lc::translate("Unknown: Git repository missing")); - } + if (!fs::exists(path)) { + BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: No masterlist present."; + throw Error(Error::Code::ok, translate("N/A: No masterlist present")); + } else if (!git.IsRepository(path.parent_path())) { + BOOST_LOG_TRIVIAL(info) << "Unknown masterlist revision: Git repository missing."; + throw Error(Error::Code::ok, translate("Unknown: Git repository missing")); + } - BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it."; - git.Call(git_repository_open(&git.repo, path.parent_path().string().c_str())); + BOOST_LOG_TRIVIAL(debug) << "Existing repository found, attempting to open it."; + git.Call(git_repository_open(&git.GetData().repo, path.parent_path().string().c_str())); - //Need to get the HEAD object, because the individual file has a different SHA. - BOOST_LOG_TRIVIAL(info) << "Getting the Git object for the tree at HEAD."; - git.Call(git_revparse_single(&git.obj, git.repo, "HEAD")); + //Need to get the HEAD object, because the individual file has a different SHA. + BOOST_LOG_TRIVIAL(info) << "Getting the Git object for the tree at HEAD."; + git.Call(git_revparse_single(&git.GetData().object, git.GetData().repo, "HEAD")); - BOOST_LOG_TRIVIAL(trace) << "Generating hex string for Git object ID."; - if (shortID) { - git.Call(git_object_short_id(&git.buf, git.obj)); - info.revision = git.buf.ptr; - } - else { - char c_rev[GIT_OID_HEXSZ + 1]; - info.revision = git_oid_tostr(c_rev, GIT_OID_HEXSZ + 1, git_object_id(git.obj)); - } + BOOST_LOG_TRIVIAL(trace) << "Generating hex string for Git object ID."; + if (shortID) { + git.Call(git_object_short_id(&git.GetData().buffer, git.GetData().object)); + info.revision = git.GetData().buffer.ptr; + } else { + char c_rev[GIT_OID_HEXSZ + 1]; + info.revision = git_oid_tostr(c_rev, GIT_OID_HEXSZ + 1, git_object_id(git.GetData().object)); + } - BOOST_LOG_TRIVIAL(trace) << "Getting date for Git object."; - const git_oid * oid = git_object_id(git.obj); - git.Call(git_commit_lookup(&git.commit, git.repo, oid)); - git_time_t time = git_commit_time(git.commit); - boost::locale::date_time dateTime(time); - stringstream out; - out << boost::locale::as::ftime("%Y-%m-%d") << dateTime; - info.date = out.str(); + BOOST_LOG_TRIVIAL(trace) << "Getting date for Git object."; + const git_oid * oid = git_object_id(git.GetData().object); + git.Call(git_commit_lookup(&git.GetData().commit, git.GetData().repo, oid)); + git_time_t time = git_commit_time(git.GetData().commit); + boost::locale::date_time dateTime(time); + std::stringstream out; + out << boost::locale::as::ftime("%Y-%m-%d") << dateTime; + info.date = out.str(); - BOOST_LOG_TRIVIAL(trace) << "Diffing masterlist HEAD and working copy."; - if (GitHelper::IsFileDifferent(path.parent_path(), path.filename().string())) { - info.revision += string(" ") + lc::translate("(edited)").str(); - info.date += string(" ") + lc::translate("(edited)").str(); - } + BOOST_LOG_TRIVIAL(trace) << "Diffing masterlist HEAD and working copy."; + if (GitHelper::IsFileDifferent(path.parent_path(), path.filename().string())) { + info.revision += string(" ") + translate("(edited)").str(); + info.date += string(" ") + translate("(edited)").str(); + } - return info; - } - - bool Masterlist::Update(const Game& game) { - return Update(game.MasterlistPath(), game.RepoURL(), game.RepoBranch()); - } - - bool Masterlist::Update(const boost::filesystem::path& path, const std::string& repoUrl, const std::string& repoBranch) { - GitHelper git; - fs::path repoPath = path.parent_path(); - string filename = path.filename().string(); - - if (repoUrl.empty() || repoBranch.empty()) - throw Error(Error::Code::invalid_args, "Repository URL and branch must not be empty."); - - // Initialise checkout options. - BOOST_LOG_TRIVIAL(debug) << "Setting up checkout options."; - char * paths = new char[filename.length() + 1]; - strcpy(paths, filename.c_str()); - git.checkout_options.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_REMOVE_EXISTING; - git.checkout_options.paths.strings = &paths; - git.checkout_options.paths.count = 1; - - // Initialise clone options. - git.clone_options.checkout_opts = git.checkout_options; - git.clone_options.bare = 0; - git.clone_options.checkout_branch = repoBranch.c_str(); - - // Now try to access the repository if it exists, or clone one if it doesn't. - BOOST_LOG_TRIVIAL(trace) << "Attempting to open the Git repository at: " << repoPath; - if (!git.IsRepository(repoPath)) - git.Clone(repoPath, repoUrl); - else { - // Repository exists: check settings are correct, then pull updates. - git.SetErrorMessage((boost::format(lc::translate("An error occurred while trying to access the local masterlist repository. If this error happens again, try deleting the \".git\" folder in %1%.")) % repoPath.string()).str()); - - // Open the repository. - BOOST_LOG_TRIVIAL(info) << "Existing repository found, attempting to open it."; - git.Call(git_repository_open(&git.repo, repoPath.string().c_str())); - - // Set the remote URL. - BOOST_LOG_TRIVIAL(info) << "Using remote URL: " << repoUrl; - git.Call(git_remote_set_url(git.repo, "origin", repoUrl.c_str())); - - // Now fetch updates from the remote. - git.Fetch("origin"); - - // Check that a local branch with the correct name exists. - git.SetErrorMessage((boost::format(lc::translate("An error occurred while trying to access the local masterlist repository. If this error happens again, try deleting the \".git\" folder in %1%.")) % repoPath.string()).str()); - int ret = git_branch_lookup(&git.ref, git.repo, repoBranch.c_str(), GIT_BRANCH_LOCAL); - if (ret == GIT_ENOTFOUND) - // Branch doesn't exist. Create a new branch using the remote branch's latest commit. - git.CheckoutNewBranch("origin", repoBranch); - else { - // The local branch exists. Need to merge the remote branch - // into it. - git.Call(ret); // Handle other errors from preceding branch lookup. - - // Check if HEAD points to the desired branch and set it to if not. - if (!git_branch_is_head(git.ref)) { - BOOST_LOG_TRIVIAL(trace) << "Setting HEAD to follow branch: " << repoBranch; - git.Call(git_repository_set_head(git.repo, (string("refs/heads/") + repoBranch).c_str())); - } - - // Get remote branch reference. - git.Call(git_branch_upstream(&git.ref2, git.ref)); - - BOOST_LOG_TRIVIAL(trace) << "Checking HEAD and remote branch's mergeability."; - git_merge_analysis_t analysis; - git_merge_preference_t pref; - git.Call(git_annotated_commit_from_ref(&git.annotated_commit, git.repo, git.ref2)); - git.Call(git_merge_analysis(&analysis, &pref, git.repo, (const git_annotated_commit **)&git.annotated_commit, 1)); - - if ((analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) == 0 && (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) == 0) { - // The local branch can't be easily merged. Best just to delete and recreate it. - BOOST_LOG_TRIVIAL(trace) << "Local branch cannot be easily merged with remote branch."; - - BOOST_LOG_TRIVIAL(trace) << "Deleting the local branch."; - git.Call(git_branch_delete(git.ref)); - - // Need to free ref before calling git.CheckoutNewBranch() - git_reference_free(git.ref); - git.ref = nullptr; - git_reference_free(git.ref2); - git.ref2 = nullptr; - - git.CheckoutNewBranch("origin", repoBranch); - } - else { - // Get remote branch commit ID. - git.Call(git_reference_peel(&git.obj, git.ref2, GIT_OBJ_COMMIT)); - const git_oid * remote_commit_id = git_object_id(git.obj); - - git_object_free(git.obj); - git.obj = nullptr; - git_reference_free(git.ref2); - git.ref2 = nullptr; - - bool updateBranchHead = true; - if ((analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) != 0) { - // No merge is required, but HEAD might be ahead of the remote branch. Check - // to see if that's the case, and move HEAD back to match the remote branch - // if so. - BOOST_LOG_TRIVIAL(trace) << "Local branch is up-to-date with remote branch."; - BOOST_LOG_TRIVIAL(trace) << "Checking to see if local and remote branch heads are equal."; - - // Get local branch commit ID. - git.Call(git_reference_peel(&git.obj, git.ref, GIT_OBJ_COMMIT)); - const git_oid * local_commit_id = git_object_id(git.obj); - - git_object_free(git.obj); - git.obj = nullptr; - - updateBranchHead = local_commit_id->id != remote_commit_id->id; - - // If the masterlist in - // HEAD also matches the masterlist file, no further - // action needs to be taken. Otherwise, a checkout - // must be performed and the checked-out file parsed. - if (!updateBranchHead) { - BOOST_LOG_TRIVIAL(trace) << "Local and remote branch heads are equal."; - if (!GitHelper::IsFileDifferent(repoPath, filename)) { - BOOST_LOG_TRIVIAL(info) << "Local branch and masterlist file are already up to date."; - return false; - } - } - else - BOOST_LOG_TRIVIAL(trace) << "Local branch heads is ahead of remote branch head."; - } - else - BOOST_LOG_TRIVIAL(trace) << "Local branch can be fast-forwarded to remote branch."; - - if (updateBranchHead) { - // The remote branch reference points to a particular - // commit. Update the local branch reference to point - // to the same commit. - BOOST_LOG_TRIVIAL(trace) << "Syncing local branch head with remote branch head."; - git.Call(git_reference_set_target(&git.ref2, git.ref, remote_commit_id, "Setting branch reference.")); - - git_reference_free(git.ref2); - git.ref2 = nullptr; - } - - git_reference_free(git.ref); - git.ref = nullptr; - - BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD."; - git.Call(git_checkout_head(git.repo, &git.checkout_options)); - } + return info; +} + +bool Masterlist::Update(const Game& game) { + return Update(game.MasterlistPath(), game.RepoURL(), game.RepoBranch()); +} + +bool Masterlist::Update(const boost::filesystem::path& path, const std::string& repoUrl, const std::string& repoBranch) { + GitHelper git; + fs::path repoPath = path.parent_path(); + string filename = path.filename().string(); + + if (repoUrl.empty() || repoBranch.empty()) + throw Error(Error::Code::invalid_args, "Repository URL and branch must not be empty."); + +// Initialise checkout options. + BOOST_LOG_TRIVIAL(debug) << "Setting up checkout options."; + char * paths = new char[filename.length() + 1]; + strcpy(paths, filename.c_str()); + git.GetData().checkout_options.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DONT_REMOVE_EXISTING; + git.GetData().checkout_options.paths.strings = &paths; + git.GetData().checkout_options.paths.count = 1; + + // Initialise clone options. + git.GetData().clone_options.checkout_opts = git.GetData().checkout_options; + git.GetData().clone_options.bare = 0; + git.GetData().clone_options.checkout_branch = repoBranch.c_str(); + + // Now try to access the repository if it exists, or clone one if it doesn't. + BOOST_LOG_TRIVIAL(trace) << "Attempting to open the Git repository at: " << repoPath; + if (!git.IsRepository(repoPath)) + git.Clone(repoPath, repoUrl); + else { + // Repository exists: check settings are correct, then pull updates. + git.SetErrorMessage((boost::format(translate("An error occurred while trying to access the local masterlist repository. If this error happens again, try deleting the \".git\" folder in %1%.")) % repoPath.string()).str()); + + // Open the repository. + BOOST_LOG_TRIVIAL(info) << "Existing repository found, attempting to open it."; + git.Call(git_repository_open(&git.GetData().repo, repoPath.string().c_str())); + + // Set the remote URL. + BOOST_LOG_TRIVIAL(info) << "Using remote URL: " << repoUrl; + git.Call(git_remote_set_url(git.GetData().repo, "origin", repoUrl.c_str())); + + // Now fetch updates from the remote. + git.Fetch("origin"); + + // Check that a local branch with the correct name exists. + git.SetErrorMessage((boost::format(translate("An error occurred while trying to access the local masterlist repository. If this error happens again, try deleting the \".git\" folder in %1%.")) % repoPath.string()).str()); + int ret = git_branch_lookup(&git.GetData().reference, git.GetData().repo, repoBranch.c_str(), GIT_BRANCH_LOCAL); + if (ret == GIT_ENOTFOUND) + // Branch doesn't exist. Create a new branch using the remote branch's latest commit. + git.CheckoutNewBranch("origin", repoBranch); + else { + // The local branch exists. Need to merge the remote branch + // into it. + git.Call(ret); // Handle other errors from preceding branch lookup. + + // Check if HEAD points to the desired branch and set it to if not. + if (!git_branch_is_head(git.GetData().reference)) { + BOOST_LOG_TRIVIAL(trace) << "Setting HEAD to follow branch: " << repoBranch; + git.Call(git_repository_set_head(git.GetData().repo, (string("refs/heads/") + repoBranch).c_str())); + } + + // Get remote branch reference. + git.Call(git_branch_upstream(&git.GetData().reference2, git.GetData().reference)); + + BOOST_LOG_TRIVIAL(trace) << "Checking HEAD and remote branch's mergeability."; + git_merge_analysis_t analysis; + git_merge_preference_t pref; + git.Call(git_annotated_commit_from_ref(&git.GetData().annotated_commit, git.GetData().repo, git.GetData().reference2)); + git.Call(git_merge_analysis(&analysis, &pref, git.GetData().repo, (const git_annotated_commit **)&git.GetData().annotated_commit, 1)); + + if ((analysis & GIT_MERGE_ANALYSIS_FASTFORWARD) == 0 && (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) == 0) { + // The local branch can't be easily merged. Best just to delete and recreate it. + BOOST_LOG_TRIVIAL(trace) << "Local branch cannot be easily merged with remote branch."; + + BOOST_LOG_TRIVIAL(trace) << "Deleting the local branch."; + git.Call(git_branch_delete(git.GetData().reference)); + + // Need to free ref before calling git.CheckoutNewBranch() + git_reference_free(git.GetData().reference); + git.GetData().reference = nullptr; + git_reference_free(git.GetData().reference2); + git.GetData().reference2 = nullptr; + + git.CheckoutNewBranch("origin", repoBranch); + } else { + // Get remote branch commit ID. + git.Call(git_reference_peel(&git.GetData().object, git.GetData().reference2, GIT_OBJ_COMMIT)); + const git_oid * remote_commit_id = git_object_id(git.GetData().object); + + git_object_free(git.GetData().object); + git.GetData().object = nullptr; + git_reference_free(git.GetData().reference2); + git.GetData().reference2 = nullptr; + + bool updateBranchHead = true; + if ((analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE) != 0) { + // No merge is required, but HEAD might be ahead of the remote branch. Check + // to see if that's the case, and move HEAD back to match the remote branch + // if so. + BOOST_LOG_TRIVIAL(trace) << "Local branch is up-to-date with remote branch."; + BOOST_LOG_TRIVIAL(trace) << "Checking to see if local and remote branch heads are equal."; + + // Get local branch commit ID. + git.Call(git_reference_peel(&git.GetData().object, git.GetData().reference, GIT_OBJ_COMMIT)); + const git_oid * local_commit_id = git_object_id(git.GetData().object); + + git_object_free(git.GetData().object); + git.GetData().object = nullptr; + + updateBranchHead = local_commit_id->id != remote_commit_id->id; + + // If the masterlist in + // HEAD also matches the masterlist file, no further + // action needs to be taken. Otherwise, a checkout + // must be performed and the checked-out file parsed. + if (!updateBranchHead) { + BOOST_LOG_TRIVIAL(trace) << "Local and remote branch heads are equal."; + if (!GitHelper::IsFileDifferent(repoPath, filename)) { + BOOST_LOG_TRIVIAL(info) << "Local branch and masterlist file are already up to date."; + return false; } + } else + BOOST_LOG_TRIVIAL(trace) << "Local branch heads is ahead of remote branch head."; + } else + BOOST_LOG_TRIVIAL(trace) << "Local branch can be fast-forwarded to remote branch."; + + if (updateBranchHead) { + // The remote branch reference points to a particular + // commit. Update the local branch reference to point + // to the same commit. + BOOST_LOG_TRIVIAL(trace) << "Syncing local branch head with remote branch head."; + git.Call(git_reference_set_target(&git.GetData().reference2, git.GetData().reference, remote_commit_id, "Setting branch reference.")); + + git_reference_free(git.GetData().reference2); + git.GetData().reference2 = nullptr; } - // Now whether the repository was cloned or updated, the working directory contains - // the latest masterlist. Try parsing it: on failure, detach the HEAD back one commit - // and try again. + git_reference_free(git.GetData().reference); + git.GetData().reference = nullptr; - bool parsingFailed = false; - std::string parsingError; - git.SetErrorMessage((boost::format(lc::translate("An error occurred while trying to read information on the updated masterlist. If this error happens again, try deleting the \".git\" folder in %1%.")) % repoPath.string()).str()); - do { - // Get the HEAD revision's short ID. - string revision = git.GetHeadShortId(); + BOOST_LOG_TRIVIAL(trace) << "Performing a Git checkout of HEAD."; + git.Call(git_checkout_head(git.GetData().repo, &git.GetData().checkout_options)); + } + } + } + + // Now whether the repository was cloned or updated, the working directory contains + // the latest masterlist. Try parsing it: on failure, detach the HEAD back one commit + // and try again. + + bool parsingFailed = false; + std::string parsingError; + git.SetErrorMessage((boost::format(translate("An error occurred while trying to read information on the updated masterlist. If this error happens again, try deleting the \".git\" folder in %1%.")) % repoPath.string()).str()); + do { + // Get the HEAD revision's short ID. + string revision = git.GetHeadShortId(); + + //Now try parsing the masterlist. + BOOST_LOG_TRIVIAL(debug) << "Testing masterlist parsing."; + try { + this->Load(path); + + parsingFailed = false; + } catch (std::exception& e) { + parsingFailed = true; + if (parsingError.empty()) + parsingError = boost::locale::translate("Masterlist revision").str() + + " " + string(revision) + + ": " + e.what() + + ". " + + boost::locale::translate("The latest masterlist revision contains a syntax error, LOOT is using the most recent valid revision instead. Syntax errors are usually minor and fixed within hours.").str(); + + //There was an error, roll back one revision. + BOOST_LOG_TRIVIAL(error) << "Masterlist parsing failed. Masterlist revision " + string(revision) + ": " + e.what(); + git.CheckoutRevision("HEAD^"); + } + } while (parsingFailed); - //Now try parsing the masterlist. - BOOST_LOG_TRIVIAL(debug) << "Testing masterlist parsing."; - try { - this->Load(path); + if (!parsingError.empty()) + throw Error(Error::Code::ok, parsingError); //Throw an OK because the process still completed in a successful state. - parsingFailed = false; - } - catch (std::exception& e) { - parsingFailed = true; - if (parsingError.empty()) - parsingError = boost::locale::translate("Masterlist revision").str() + - " " + string(revision) + - ": " + e.what() + - ". " + - boost::locale::translate("The latest masterlist revision contains a syntax error, LOOT is using the most recent valid revision instead. Syntax errors are usually minor and fixed within hours.").str(); - - //There was an error, roll back one revision. - BOOST_LOG_TRIVIAL(error) << "Masterlist parsing failed. Masterlist revision " + string(revision) + ": " + e.what(); - git.CheckoutRevision("HEAD^"); - } - } while (parsingFailed); - - if (!parsingError.empty()) - throw Error(Error::Code::ok, parsingError); //Throw an OK because the process still completed in a successful state. - - return true; - } + return true; +} } diff --git a/src/backend/masterlist.h b/src/backend/masterlist.h index 0d76bd9f..5456c2c2 100644 --- a/src/backend/masterlist.h +++ b/src/backend/masterlist.h @@ -22,32 +22,32 @@ . */ -#ifndef __LOOT_MASTERLIST__ -#define __LOOT_MASTERLIST__ - -#include "metadata_list.h" +#ifndef LOOT_BACKEND_MASTERLIST +#define LOOT_BACKEND_MASTERLIST #include #include +#include "backend/metadata_list.h" + namespace loot { - class Game; +class Game; - class Masterlist : public MetadataList { - public: - struct Info { - std::string revision; - std::string date; - }; +class Masterlist : public MetadataList { +public: + struct Info { + std::string revision; + std::string date; + }; - bool Update(const Game& game); - bool Update(const boost::filesystem::path& path, - const std::string& repoURL, - const std::string& repoBranch); + bool Update(const Game& game); + bool Update(const boost::filesystem::path& path, + const std::string& repoURL, + const std::string& repoBranch); - static Info GetInfo(const boost::filesystem::path& path, bool shortID); - }; + static Info GetInfo(const boost::filesystem::path& path, bool shortID); +}; } #endif diff --git a/src/backend/metadata/condition_grammar.h b/src/backend/metadata/condition_grammar.h index c4b9d188..f56dc324 100644 --- a/src/backend/metadata/condition_grammar.h +++ b/src/backend/metadata/condition_grammar.h @@ -22,8 +22,8 @@ . */ -#ifndef __LOOT_CONDITION_PARSER__ -#define __LOOT_CONDITION_PARSER__ +#ifndef LOOT_BACKEND_METADATA_CONDITION_GRAMMAR +#define LOOT_BACKEND_METADATA_CONDITION_GRAMMAR #ifndef BOOST_SPIRIT_UNICODE #define BOOST_SPIRIT_UNICODE @@ -33,391 +33,381 @@ #define BOOST_SPIRIT_USE_PHOENIX_V3 1 #endif -#include "../game/game.h" -#include "../helpers/helpers.h" -#include "../plugin/plugin.h" -#include "../helpers/version.h" -#include "../error.h" - #include #include -#include #include #include -#include +#include +#include +#include +#include #include #include #include -#include -#include -#include +#include + +#include "backend/error.h" +#include "backend/game/game.h" +#include "backend/helpers/helpers.h" +#include "backend/helpers/version.h" +#include "backend/plugin/plugin.h" namespace loot { - /////////////////////////////// - // Condition parser/evaluator - /////////////////////////////// - - namespace qi = boost::spirit::qi; - namespace unicode = boost::spirit::unicode; +template +class ConditionGrammar : public boost::spirit::qi::grammar < Iterator, bool(), Skipper > { +public: + ConditionGrammar() : ConditionGrammar(nullptr) {} + ConditionGrammar(Game * game) : ConditionGrammar::base_type(expression_, "condition grammar"), game_(game) { + using boost::spirit::unicode::char_; + using boost::spirit::unicode::string; namespace phoenix = boost::phoenix; - - template - class ConditionGrammar : public qi::grammar < Iterator, bool(), Skipper > { - public: - ConditionGrammar() : ConditionGrammar(nullptr) {} - ConditionGrammar(Game * game) : ConditionGrammar::base_type(expression, "condition grammar"), _game(game) { - expression = - qi::eps > - compound[qi::labels::_val = qi::labels::_1] - >> *((qi::lit("or") >> compound)[qi::labels::_val = qi::labels::_val || qi::labels::_1]) - ; - - compound = - condition[qi::labels::_val = qi::labels::_1] - >> *((qi::lit("and") >> condition)[qi::labels::_val = qi::labels::_val && qi::labels::_1]) - ; - - condition = - function[qi::labels::_val = qi::labels::_1] - | (qi::lit("not") > condition)[qi::labels::_val = !qi::labels::_1] - | ('(' > expression > ')')[qi::labels::_val = qi::labels::_1] - ; - - function = - ("file(" > filePath > ')')[phoenix::bind(&ConditionGrammar::CheckFile, this, qi::labels::_val, qi::labels::_1)] - | ("regex(" > quotedStr > ')')[phoenix::bind(&ConditionGrammar::CheckRegex, this, qi::labels::_val, qi::labels::_1)] - | ("many(" > quotedStr > ')')[phoenix::bind(&ConditionGrammar::CheckMany, this, qi::labels::_val, qi::labels::_1)] - | ("checksum(" > filePath > ',' > qi::hex > ')')[phoenix::bind(&ConditionGrammar::CheckSum, this, qi::labels::_val, qi::labels::_1, qi::labels::_2)] - | ("version(" > filePath > ',' > quotedStr > ',' > comparator > ')')[phoenix::bind(&ConditionGrammar::CheckVersion, this, qi::labels::_val, qi::labels::_1, qi::labels::_2, qi::labels::_3)] - | ("active(" > filePath > ')')[phoenix::bind(&ConditionGrammar::CheckActive, this, qi::labels::_val, qi::labels::_1)] - ; - - quotedStr %= '"' > +(unicode::char_ - '"') > '"'; - - filePath %= '"' > +(unicode::char_ - invalidPathChars) > '"'; - - invalidPathChars %= - unicode::char_(':') - | unicode::char_('*') - | unicode::char_('?') - | unicode::char_('"') - | unicode::char_('<') - | unicode::char_('>') - | unicode::char_('|') - ; - - comparator %= - unicode::string("==") - | unicode::string("!=") - | unicode::string("<=") - | unicode::string(">=") - | unicode::string("<") - | unicode::string(">") - ; - - expression.name("expression"); - compound.name("compound condition"); - condition.name("condition"); - function.name("function"); - quotedStr.name("quoted string"); - filePath.name("file path"); - comparator.name("comparator"); - invalidPathChars.name("invalid file path characters"); - - qi::on_error(expression, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(compound, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(condition, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(function, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(quotedStr, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(filePath, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(comparator, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - qi::on_error(invalidPathChars, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); - } - - private: - qi::rule expression, compound, condition, function; - qi::rule quotedStr, filePath, comparator; - qi::rule invalidPathChars; - - Game * _game; - - //Eval's exact paths. Check for files and ghosted plugins. - void CheckFile(bool& result, const std::string& file) const { - BOOST_LOG_TRIVIAL(trace) << "Checking to see if the file \"" << file << "\" exists."; - - if (file == "LOOT") { - result = true; - return; - } - - if (!IsSafePath(file)) { - BOOST_LOG_TRIVIAL(error) << "Invalid file path: " << file; - throw loot::Error(loot::Error::Code::invalid_args, boost::locale::translate("Invalid file path:").str() + " " + file); - } - - if (_game == nullptr) - return; - - // Try first checking the plugin cache, as most file entries are - // for plugins. - try { - // GetPlugin throws if it can't find an entry. - _game->GetPlugin(file); - result = true; - } - catch (...) { - // Not a loaded plugin, check the filesystem. - if (boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) - result = boost::filesystem::exists(_game->DataPath() / file) || boost::filesystem::exists(_game->DataPath() / (file + ".ghost")); - else - result = boost::filesystem::exists(_game->DataPath() / file); - } - - if (result) - BOOST_LOG_TRIVIAL(trace) << "The file does exist."; - else - BOOST_LOG_TRIVIAL(trace) << "The file does not exist."; - } - - // Split a regex string into the non-regex filesystem parent path, and the regex filename. - std::pair SplitRegex(const std::string& regex) const { - //Can't support a regex string where all path components may be regex, since this could - //lead to massive scanning if an unfortunately-named directory is encountered. - //As such, only the filename portion can be a regex. Need to separate that from the rest - //of the string. - - /* Look for directory separators: in non-regex strings, they are '/' and '\'. In regex, - the backslash is special so must be escaped using another backslash, so look for '/' and "\\". - In C++ string literals, the backslash must be escaped once more to give "\\\\". - Split the regex with another regex! */ - - try { - std::regex(regex, std::regex::ECMAScript | std::regex::icase); - } - catch (std::regex_error& e) { - throw loot::Error(loot::Error::Code::invalid_args, (boost::format(boost::locale::translate("Invalid regex string \"%1%\": %2%")) % regex % e.what()).str()); - } - - std::regex sepReg("/|(\\\\\\\\)", std::regex::ECMAScript); - - std::sregex_token_iterator it(regex.begin(), regex.end(), sepReg, -1); - std::vector components(it, std::sregex_token_iterator()); - - std::string filename = components.back(); - components.pop_back(); - - boost::filesystem::path parent; - for (const auto& component : components) { - parent /= component; - } - - if (!IsSafePath(parent)) { - BOOST_LOG_TRIVIAL(error) << "Invalid folder path: " << parent; - throw loot::Error(loot::Error::Code::invalid_args, boost::locale::translate("Invalid folder path:").str() + " " + parent.string()); - } - - std::regex reg; - try { - reg = std::regex(filename, std::regex::ECMAScript | std::regex::icase); - } - catch (std::regex_error& e) { - BOOST_LOG_TRIVIAL(error) << "Invalid regex string:" << filename; - throw loot::Error(loot::Error::Code::invalid_args, (boost::format(boost::locale::translate("Invalid regex string \"%1%\": %2%")) % filename % e.what()).str()); - } - - return std::pair(parent, reg); - } - - void CheckRegex(bool& result, const std::string& regexStr) const { - result = false; - - BOOST_LOG_TRIVIAL(trace) << "Checking to see if any files matching the regex \"" << regexStr << "\" exist."; - - std::pair pathRegex = SplitRegex(regexStr); - - if (_game == nullptr) - return; - - //Now we have a valid parent path and a regex filename. Check that - //the parent path exists and is a directory. - boost::filesystem::path parent_path = _game->DataPath() / pathRegex.first; - if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) { - BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory."; - return; - } - - for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) { - if (std::regex_match(itr->path().filename().string(), pathRegex.second)) { - result = true; - BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path(); - return; - } - } - } - - void CheckMany(bool& result, const std::string& regexStr) const { - result = false; - - BOOST_LOG_TRIVIAL(trace) << "Checking to see if more than one file matching the regex \"" << regexStr << "\" exist."; - - std::pair pathRegex = SplitRegex(regexStr); - - if (_game == nullptr) - return; - - //Now we have a valid parent path and a regex filename. Check that - //the parent path exists and is a directory. - - boost::filesystem::path parent_path = _game->DataPath() / pathRegex.first; - if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) { - BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory."; - return; - } - - size_t count = 0; - for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) { - if (std::regex_match(itr->path().filename().string(), pathRegex.second)) { - ++count; - BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path(); - } - } - - result = count > 1; - } - - void CheckSum(bool& result, const std::string& file, const uint32_t checksum) { - BOOST_LOG_TRIVIAL(trace) << "Checking the CRC of the file \"" << file << "\"."; - - if (!IsSafePath(file)) { - BOOST_LOG_TRIVIAL(error) << "Invalid file path: " << file; - throw loot::Error(loot::Error::Code::invalid_args, boost::locale::translate("Invalid file path:").str() + " " + file); - } - - if (_game == nullptr) - return; - - uint32_t crc = 0; - if (file == "LOOT") - crc = GetCrc32(boost::filesystem::absolute("LOOT.exe")); - else { - // CRC could be for a plugin or a file. - // Get the CRC from the game plugin cache if possible. - try { - crc = _game->GetPlugin(file).Crc(); - } - catch (...) {} - - if (crc == 0) { - if (boost::filesystem::exists(_game->DataPath() / file)) - crc = GetCrc32(_game->DataPath() / file); - else if ((boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) && boost::filesystem::exists(_game->DataPath() / (file + ".ghost"))) - crc = GetCrc32(_game->DataPath() / (file + ".ghost")); - } - else { - result = false; - return; - } - } - - result = checksum == crc; - } - - void CheckVersion(bool& result, const std::string& file, const std::string& version, const std::string& comparator) const { - BOOST_LOG_TRIVIAL(trace) << "Checking version of file \"" << file << "\"."; - - CheckFile(result, file); - - if (_game == nullptr) - return; - - if (!result) { - if (comparator == "!=" || comparator == "<" || comparator == "<=") - result = true; - BOOST_LOG_TRIVIAL(trace) << "Version check result: " << result; - return; - } - - Version givenVersion = Version(version); - Version trueVersion; - if (file == "LOOT") - trueVersion = Version(boost::filesystem::absolute("LOOT.exe")); - else { - // If the file is a plugin, its version needs to be extracted - // from its description field. Try getting an entry from the - // plugin cache. - try { - Plugin plugin = _game->GetPlugin(file); - trueVersion = Version(plugin.getDescription()); - } - catch (...) { - // The file wasn't in the plugin cache, load it as a plugin - // if it appears to be valid, otherwise treat it as a non - // plugin file. - if (Plugin::IsValid(file, *_game)) { - Plugin plugin(*_game, file, true); - trueVersion = Version(plugin.getDescription()); - } - else - trueVersion = Version(_game->DataPath() / file); - } - } - - BOOST_LOG_TRIVIAL(trace) << "Version extracted: " << trueVersion.AsString(); - - if ((comparator == "==" && trueVersion != givenVersion) - || (comparator == "!=" && trueVersion == givenVersion) - || (comparator == "<" && trueVersion >= givenVersion) - || (comparator == ">" && trueVersion <= givenVersion) - || (comparator == "<=" && trueVersion > givenVersion) - || (comparator == ">=" && trueVersion < givenVersion)) - result = false; - - BOOST_LOG_TRIVIAL(trace) << "Version check result: " << result; - } - - void CheckActive(bool& result, const std::string& file) const { - if (!IsSafePath(file)) { - BOOST_LOG_TRIVIAL(error) << "Invalid file path: " << file; - throw loot::Error(loot::Error::Code::invalid_args, boost::locale::translate("Invalid file path:").str() + " " + file); - } - - if (_game == nullptr) - return; - - if (file == "LOOT") - result = false; - else - result = _game->IsPluginActive(file); - - BOOST_LOG_TRIVIAL(trace) << "Active check result: " << result; - } - - void SyntaxError(Iterator const& /*first*/, Iterator const& last, Iterator const& errorpos, boost::spirit::info const& what) { - std::string context(errorpos, last); - boost::trim(context); - - BOOST_LOG_TRIVIAL(error) << "Expected \"" << what.tag << "\" at \"" << context << "\"."; - - throw loot::Error(loot::Error::Code::condition_eval_fail, (boost::format(boost::locale::translate("Expected \"%1%\" at \"%2%\".")) % what.tag % context).str()); - } - - //Checks that the path (not regex) doesn't go outside any game folders. - bool IsSafePath(const boost::filesystem::path& path) const { - BOOST_LOG_TRIVIAL(trace) << "Checking to see if the path \"" << path << "\" is safe."; - - boost::filesystem::path temp; - for (const auto& component : path) { - if (component == ".") - continue; - - if (component == ".." && temp.filename() == "..") - return false; - - temp /= component; - } - - return true; - } - }; + namespace qi = boost::spirit::qi; + + expression_ = + qi::eps > + compound_[qi::labels::_val = qi::labels::_1] + >> *((qi::lit("or") >> compound_)[qi::labels::_val = qi::labels::_val || qi::labels::_1]) + ; + + compound_ = + condition_[qi::labels::_val = qi::labels::_1] + >> *((qi::lit("and") >> condition_)[qi::labels::_val = qi::labels::_val && qi::labels::_1]) + ; + + condition_ = + function_[qi::labels::_val = qi::labels::_1] + | (qi::lit("not") > condition_)[qi::labels::_val = !qi::labels::_1] + | ('(' > expression_ > ')')[qi::labels::_val = qi::labels::_1] + ; + + function_ = + ("file(" > filePath_ > ')')[phoenix::bind(&ConditionGrammar::CheckFile, this, qi::labels::_val, qi::labels::_1)] + | ("regex(" > quotedStr_ > ')')[phoenix::bind(&ConditionGrammar::CheckRegex, this, qi::labels::_val, qi::labels::_1)] + | ("many(" > quotedStr_ > ')')[phoenix::bind(&ConditionGrammar::CheckMany, this, qi::labels::_val, qi::labels::_1)] + | ("checksum(" > filePath_ > ',' > qi::hex > ')')[phoenix::bind(&ConditionGrammar::CheckSum, this, qi::labels::_val, qi::labels::_1, qi::labels::_2)] + | ("version(" > filePath_ > ',' > quotedStr_ > ',' > comparator_ > ')')[phoenix::bind(&ConditionGrammar::CheckVersion, this, qi::labels::_val, qi::labels::_1, qi::labels::_2, qi::labels::_3)] + | ("active(" > filePath_ > ')')[phoenix::bind(&ConditionGrammar::CheckActive, this, qi::labels::_val, qi::labels::_1)] + ; + + quotedStr_ %= '"' > +(char_ - '"') > '"'; + + filePath_ %= '"' > +(char_ - invalidPathChars_) > '"'; + + invalidPathChars_ %= + char_(':') + | char_('*') + | char_('?') + | char_('"') + | char_('<') + | char_('>') + | char_('|') + ; + + comparator_ %= + string("==") + | string("!=") + | string("<=") + | string(">=") + | string("<") + | string(">") + ; + + expression_.name("expression"); + compound_.name("compound condition"); + condition_.name("condition"); + function_.name("function"); + quotedStr_.name("quoted string"); + filePath_.name("file path"); + comparator_.name("comparator"); + invalidPathChars_.name("invalid file path characters"); + + qi::on_error(expression_, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(compound_, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(condition_, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(function_, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(quotedStr_, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(filePath_, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(comparator_, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + qi::on_error(invalidPathChars_, phoenix::bind(&ConditionGrammar::SyntaxError, this, qi::labels::_1, qi::labels::_2, qi::labels::_3, qi::labels::_4)); + } + +private: + //Eval's exact paths. Check for files and ghosted plugins. + void CheckFile(bool& result, const std::string& file) const { + BOOST_LOG_TRIVIAL(trace) << "Checking to see if the file \"" << file << "\" exists."; + + if (file == "LOOT") { + result = true; + return; + } + + if (!IsSafePath(file)) { + BOOST_LOG_TRIVIAL(error) << "Invalid file path: " << file; + throw Error(Error::Code::invalid_args, boost::locale::translate("Invalid file path:").str() + " " + file); + } + + if (game_ == nullptr) + return; + + // Try first checking the plugin cache, as most file entries are + // for plugins. + try { + // GetPlugin throws if it can't find an entry. + game_->GetPlugin(file); + result = true; + } catch (...) { + // Not a loaded plugin, check the filesystem. + if (boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) + result = boost::filesystem::exists(game_->DataPath() / file) || boost::filesystem::exists(game_->DataPath() / (file + ".ghost")); + else + result = boost::filesystem::exists(game_->DataPath() / file); + } + + if (result) + BOOST_LOG_TRIVIAL(trace) << "The file does exist."; + else + BOOST_LOG_TRIVIAL(trace) << "The file does not exist."; + } + + // Split a regex string into the non-regex filesystem parent path, and the regex filename. + std::pair SplitRegex(const std::string& regex) const { + //Can't support a regex string where all path components may be regex, since this could + //lead to massive scanning if an unfortunately-named directory is encountered. + //As such, only the filename portion can be a regex. Need to separate that from the rest + //of the string. + + /* Look for directory separators: in non-regex strings, they are '/' and '\'. In regex, + the backslash is special so must be escaped using another backslash, so look for '/' and "\\". + In C++ string literals, the backslash must be escaped once more to give "\\\\". + Split the regex with another regex! */ + + try { + std::regex(regex, std::regex::ECMAScript | std::regex::icase); + } catch (std::regex_error& e) { + throw Error(Error::Code::invalid_args, (boost::format(boost::locale::translate("Invalid regex string \"%1%\": %2%")) % regex % e.what()).str()); + } + + std::regex sepReg("/|(\\\\\\\\)", std::regex::ECMAScript); + + std::sregex_token_iterator it(regex.begin(), regex.end(), sepReg, -1); + std::vector components(it, std::sregex_token_iterator()); + + std::string filename = components.back(); + components.pop_back(); + + boost::filesystem::path parent; + for (const auto& component : components) { + parent /= component; + } + + if (!IsSafePath(parent)) { + BOOST_LOG_TRIVIAL(error) << "Invalid folder path: " << parent; + throw Error(Error::Code::invalid_args, boost::locale::translate("Invalid folder path:").str() + " " + parent.string()); + } + + std::regex reg; + try { + reg = std::regex(filename, std::regex::ECMAScript | std::regex::icase); + } catch (std::regex_error& e) { + BOOST_LOG_TRIVIAL(error) << "Invalid regex string:" << filename; + throw Error(Error::Code::invalid_args, (boost::format(boost::locale::translate("Invalid regex string \"%1%\": %2%")) % filename % e.what()).str()); + } + + return std::pair(parent, reg); + } + + void CheckRegex(bool& result, const std::string& regexStr) const { + result = false; + + BOOST_LOG_TRIVIAL(trace) << "Checking to see if any files matching the regex \"" << regexStr << "\" exist."; + + std::pair pathRegex = SplitRegex(regexStr); + + if (game_ == nullptr) + return; + + //Now we have a valid parent path and a regex filename. Check that + //the parent path exists and is a directory. + boost::filesystem::path parent_path = game_->DataPath() / pathRegex.first; + if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) { + BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory."; + return; + } + + for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) { + if (std::regex_match(itr->path().filename().string(), pathRegex.second)) { + result = true; + BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path(); + return; + } + } + } + + void CheckMany(bool& result, const std::string& regexStr) const { + result = false; + + BOOST_LOG_TRIVIAL(trace) << "Checking to see if more than one file matching the regex \"" << regexStr << "\" exist."; + + std::pair pathRegex = SplitRegex(regexStr); + + if (game_ == nullptr) + return; + + //Now we have a valid parent path and a regex filename. Check that + //the parent path exists and is a directory. + + boost::filesystem::path parent_path = game_->DataPath() / pathRegex.first; + if (!boost::filesystem::exists(parent_path) || !boost::filesystem::is_directory(parent_path)) { + BOOST_LOG_TRIVIAL(trace) << "The path \"" << parent_path << "\" does not exist or is not a directory."; + return; + } + + size_t count = 0; + for (boost::filesystem::directory_iterator itr(parent_path); itr != boost::filesystem::directory_iterator(); ++itr) { + if (std::regex_match(itr->path().filename().string(), pathRegex.second)) { + ++count; + BOOST_LOG_TRIVIAL(trace) << "Matching file found: " << itr->path(); + } + } + + result = count > 1; + } + + void CheckSum(bool& result, const std::string& file, const uint32_t checksum) { + BOOST_LOG_TRIVIAL(trace) << "Checking the CRC of the file \"" << file << "\"."; + + if (!IsSafePath(file)) { + BOOST_LOG_TRIVIAL(error) << "Invalid file path: " << file; + throw Error(Error::Code::invalid_args, boost::locale::translate("Invalid file path:").str() + " " + file); + } + + if (game_ == nullptr) + return; + + uint32_t crc = 0; + if (file == "LOOT") + crc = GetCrc32(boost::filesystem::absolute("LOOT.exe")); + else { + // CRC could be for a plugin or a file. + // Get the CRC from the game plugin cache if possible. + try { + crc = game_->GetPlugin(file).Crc(); + } catch (...) {} + + if (crc == 0) { + if (boost::filesystem::exists(game_->DataPath() / file)) + crc = GetCrc32(game_->DataPath() / file); + else if ((boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) && boost::filesystem::exists(game_->DataPath() / (file + ".ghost"))) + crc = GetCrc32(game_->DataPath() / (file + ".ghost")); + } else { + result = false; + return; + } + } + + result = checksum == crc; + } + + void CheckVersion(bool& result, const std::string& file, const std::string& version, const std::string& comparator) const { + BOOST_LOG_TRIVIAL(trace) << "Checking version of file \"" << file << "\"."; + + CheckFile(result, file); + + if (game_ == nullptr) + return; + + if (!result) { + if (comparator == "!=" || comparator == "<" || comparator == "<=") + result = true; + BOOST_LOG_TRIVIAL(trace) << "Version check result: " << result; + return; + } + + Version givenVersion = Version(version); + Version trueVersion; + if (file == "LOOT") + trueVersion = Version(boost::filesystem::absolute("LOOT.exe")); + else { + // If the file is a plugin, its version needs to be extracted + // from its description field. Try getting an entry from the + // plugin cache. + try { + Plugin plugin = game_->GetPlugin(file); + trueVersion = Version(plugin.getDescription()); + } catch (...) { + // The file wasn't in the plugin cache, load it as a plugin + // if it appears to be valid, otherwise treat it as a non + // plugin file. + if (Plugin::IsValid(file, *game_)) { + Plugin plugin(*game_, file, true); + trueVersion = Version(plugin.getDescription()); + } else + trueVersion = Version(game_->DataPath() / file); + } + } + + BOOST_LOG_TRIVIAL(trace) << "Version extracted: " << trueVersion.AsString(); + + if ((comparator == "==" && trueVersion != givenVersion) + || (comparator == "!=" && trueVersion == givenVersion) + || (comparator == "<" && trueVersion >= givenVersion) + || (comparator == ">" && trueVersion <= givenVersion) + || (comparator == "<=" && trueVersion > givenVersion) + || (comparator == ">=" && trueVersion < givenVersion)) + result = false; + + BOOST_LOG_TRIVIAL(trace) << "Version check result: " << result; + } + + void CheckActive(bool& result, const std::string& file) const { + if (!IsSafePath(file)) { + BOOST_LOG_TRIVIAL(error) << "Invalid file path: " << file; + throw Error(Error::Code::invalid_args, boost::locale::translate("Invalid file path:").str() + " " + file); + } + + if (game_ == nullptr) + return; + + if (file == "LOOT") + result = false; + else + result = game_->IsPluginActive(file); + + BOOST_LOG_TRIVIAL(trace) << "Active check result: " << result; + } + + void SyntaxError(Iterator const& /*first*/, Iterator const& last, Iterator const& errorpos, boost::spirit::info const& what) { + std::string context(errorpos, last); + boost::trim(context); + + BOOST_LOG_TRIVIAL(error) << "Expected \"" << what.tag << "\" at \"" << context << "\"."; + + throw Error(Error::Code::condition_eval_fail, (boost::format(boost::locale::translate("Expected \"%1%\" at \"%2%\".")) % what.tag % context).str()); + } + + //Checks that the path (not regex) doesn't go outside any game folders. + bool IsSafePath(const boost::filesystem::path& path) const { + BOOST_LOG_TRIVIAL(trace) << "Checking to see if the path \"" << path << "\" is safe."; + + boost::filesystem::path temp; + for (const auto& component : path) { + if (component == ".") + continue; + + if (component == ".." && temp.filename() == "..") + return false; + + temp /= component; + } + + return true; + } + + boost::spirit::qi::rule expression_, compound_, condition_, function_; + boost::spirit::qi::rule quotedStr_, filePath_, comparator_; + boost::spirit::qi::rule invalidPathChars_; + + Game * game_; +}; } #endif diff --git a/src/backend/metadata/conditional_metadata.cpp b/src/backend/metadata/conditional_metadata.cpp index 88c3e59e..d4b1d76c 100644 --- a/src/backend/metadata/conditional_metadata.cpp +++ b/src/backend/metadata/conditional_metadata.cpp @@ -22,91 +22,91 @@ . */ -#include "conditional_metadata.h" -#include "condition_grammar.h" +#include "backend/metadata/conditional_metadata.h" -#include #include +#include -using namespace std; +#include "backend/game/game.h" +#include "backend/metadata/condition_grammar.h" + +using boost::locale::translate; +using std::exception; +using std::string; namespace loot { - namespace lc = boost::locale; +ConditionalMetadata::ConditionalMetadata() {} - ConditionalMetadata::ConditionalMetadata() {} +ConditionalMetadata::ConditionalMetadata(const string& condition) : condition_(condition) {} - ConditionalMetadata::ConditionalMetadata(const string& condition) : _condition(condition) {} - - bool ConditionalMetadata::IsConditional() const { - return !_condition.empty(); - } - - std::string ConditionalMetadata::Condition() const { - return _condition; - } - - bool ConditionalMetadata::EvalCondition(Game& game) const { - if (_condition.empty()) - return true; - - BOOST_LOG_TRIVIAL(trace) << "Evaluating condition: " << _condition; - - auto cachedValue = game.GetCachedCondition(_condition); - if (cachedValue.second) - return cachedValue.first; - - ConditionGrammar grammar(&game); - boost::spirit::qi::space_type skipper; - std::string::const_iterator begin, end; - bool eval; - - begin = _condition.begin(); - end = _condition.end(); - - bool r; - try { - r = boost::spirit::qi::phrase_parse(begin, end, grammar, skipper, eval); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\": " << e.what(); - throw loot::Error(loot::Error::Code::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\": %2%")) % _condition % e.what()).str()); - } - - if (!r || begin != end) { - BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\"."; - throw loot::Error(loot::Error::Code::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\".")) % _condition).str()); - } - - game.CacheCondition(_condition, eval); - - return eval; - } - - void ConditionalMetadata::ParseCondition() const { - if (_condition.empty()) - return; - - BOOST_LOG_TRIVIAL(trace) << "Testing condition syntax: " << _condition; - - ConditionGrammar grammar(nullptr); - boost::spirit::qi::space_type skipper; - std::string::const_iterator begin, end; - - begin = _condition.begin(); - end = _condition.end(); - - bool r; - try { - r = boost::spirit::qi::phrase_parse(begin, end, grammar, skipper); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\": " << e.what(); - throw loot::Error(loot::Error::Code::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\": %2%")) % _condition % e.what()).str()); - } - - if (!r || begin != end) { - BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << _condition << "\"."; - throw loot::Error(loot::Error::Code::condition_eval_fail, (boost::format(lc::translate("Failed to parse condition \"%1%\".")) % _condition).str()); - } - } +bool ConditionalMetadata::IsConditional() const { + return !condition_.empty(); +} + +std::string ConditionalMetadata::Condition() const { + return condition_; +} + +bool ConditionalMetadata::EvalCondition(Game& game) const { + if (condition_.empty()) + return true; + + BOOST_LOG_TRIVIAL(trace) << "Evaluating condition: " << condition_; + + auto cachedValue = game.GetCachedCondition(condition_); + if (cachedValue.second) + return cachedValue.first; + + ConditionGrammar grammar(&game); + boost::spirit::qi::space_type skipper; + string::const_iterator begin, end; + bool eval; + + begin = condition_.begin(); + end = condition_.end(); + + bool r; + try { + r = boost::spirit::qi::phrase_parse(begin, end, grammar, skipper, eval); + } catch (exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << condition_ << "\": " << e.what(); + throw Error(Error::Code::condition_eval_fail, (boost::format(translate("Failed to parse condition \"%1%\": %2%")) % condition_ % e.what()).str()); + } + + if (!r || begin != end) { + BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << condition_ << "\"."; + throw Error(Error::Code::condition_eval_fail, (boost::format(translate("Failed to parse condition \"%1%\".")) % condition_).str()); + } + + game.CacheCondition(condition_, eval); + + return eval; +} + +void ConditionalMetadata::ParseCondition() const { + if (condition_.empty()) + return; + + BOOST_LOG_TRIVIAL(trace) << "Testing condition syntax: " << condition_; + + ConditionGrammar grammar(nullptr); + boost::spirit::qi::space_type skipper; + string::const_iterator begin, end; + + begin = condition_.begin(); + end = condition_.end(); + + bool r; + try { + r = boost::spirit::qi::phrase_parse(begin, end, grammar, skipper); + } catch (exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << condition_ << "\": " << e.what(); + throw Error(Error::Code::condition_eval_fail, (boost::format(translate("Failed to parse condition \"%1%\": %2%")) % condition_ % e.what()).str()); + } + + if (!r || begin != end) { + BOOST_LOG_TRIVIAL(error) << "Failed to parse condition \"" << condition_ << "\"."; + throw Error(Error::Code::condition_eval_fail, (boost::format(translate("Failed to parse condition \"%1%\".")) % condition_).str()); + } +} } diff --git a/src/backend/metadata/conditional_metadata.h b/src/backend/metadata/conditional_metadata.h index f24283ee..5bfcd3f3 100644 --- a/src/backend/metadata/conditional_metadata.h +++ b/src/backend/metadata/conditional_metadata.h @@ -21,26 +21,26 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA_CONDITIONAL_METADATA__ -#define __LOOT_METADATA_CONDITIONAL_METADATA__ +#ifndef LOOT_BACKEND_METADATA_CONDITIONAL_METADATA +#define LOOT_BACKEND_METADATA_CONDITIONAL_METADATA #include namespace loot { - class Game; +class Game; - class ConditionalMetadata { - public: - ConditionalMetadata(); - ConditionalMetadata(const std::string& condition); +class ConditionalMetadata { +public: + ConditionalMetadata(); + ConditionalMetadata(const std::string& condition); - bool IsConditional() const; - bool EvalCondition(Game& game) const; - void ParseCondition() const; // Throws error on parsing failure. + bool IsConditional() const; + bool EvalCondition(Game& game) const; + void ParseCondition() const; // Throws error on parsing failure. - std::string Condition() const; - private: - std::string _condition; - }; + std::string Condition() const; +private: + std::string condition_; +}; } #endif diff --git a/src/backend/metadata/file.cpp b/src/backend/metadata/file.cpp index 9348a78d..a44ecc91 100644 --- a/src/backend/metadata/file.cpp +++ b/src/backend/metadata/file.cpp @@ -22,55 +22,53 @@ . */ -#include "file.h" +#include "backend/metadata/file.h" #include -using namespace std; - namespace loot { - File::File() {} +File::File() {} - File::File(const std::string& name, const std::string& display, const std::string& condition) - : _name(name), _display(display), ConditionalMetadata(condition) {} +File::File(const std::string& name, const std::string& display, const std::string& condition) + : name_(name), display_(display), ConditionalMetadata(condition) {} - bool File::operator < (const File& rhs) const { - return boost::ilexicographical_compare(Name(), rhs.Name()); - } +bool File::operator < (const File& rhs) const { + return boost::ilexicographical_compare(Name(), rhs.Name()); +} - bool File::operator == (const File& rhs) const { - return boost::iequals(Name(), rhs.Name()); - } +bool File::operator == (const File& rhs) const { + return boost::iequals(Name(), rhs.Name()); +} - std::string File::Name() const { - return _name; - } +std::string File::Name() const { + return name_; +} - std::string File::DisplayName() const { - if (_display.empty()) - return _name; - else - return _display; - } +std::string File::DisplayName() const { + if (display_.empty()) + return name_; + else + return display_; +} } namespace YAML { - Emitter& operator << (Emitter& out, const loot::File& rhs) { - if (!rhs.IsConditional() && (rhs.DisplayName().empty() || rhs.DisplayName() == rhs.Name())) - out << YAML::SingleQuoted << rhs.Name(); - else { - out << BeginMap - << Key << "name" << Value << YAML::SingleQuoted << rhs.Name(); +Emitter& operator << (Emitter& out, const loot::File& rhs) { + if (!rhs.IsConditional() && (rhs.DisplayName().empty() || rhs.DisplayName() == rhs.Name())) + out << YAML::SingleQuoted << rhs.Name(); + else { + out << BeginMap + << Key << "name" << Value << YAML::SingleQuoted << rhs.Name(); - if (rhs.IsConditional()) - out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition(); + if (rhs.IsConditional()) + out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition(); - if (rhs.DisplayName() != rhs.Name()) - out << Key << "display" << Value << YAML::SingleQuoted << rhs.DisplayName(); + if (rhs.DisplayName() != rhs.Name()) + out << Key << "display" << Value << YAML::SingleQuoted << rhs.DisplayName(); - out << EndMap; - } + out << EndMap; + } - return out; - } + return out; +} } diff --git a/src/backend/metadata/file.h b/src/backend/metadata/file.h index d500fe63..0412d010 100644 --- a/src/backend/metadata/file.h +++ b/src/backend/metadata/file.h @@ -21,81 +21,79 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA_FILE__ -#define __LOOT_METADATA_FILE__ - -#include "conditional_metadata.h" +#ifndef LOOT_BACKEND_METADATA_FILE +#define LOOT_BACKEND_METADATA_FILE #include #include +#include "backend/metadata/conditional_metadata.h" + namespace loot { - class File : public ConditionalMetadata { - public: - File(); - File(const std::string& name, const std::string& display = "", - const std::string& condition = ""); +class File : public ConditionalMetadata { +public: + File(); + File(const std::string& name, const std::string& display = "", + const std::string& condition = ""); - bool operator < (const File& rhs) const; - bool operator == (const File& rhs) const; + bool operator < (const File& rhs) const; + bool operator == (const File& rhs) const; - std::string Name() const; - std::string DisplayName() const; - private: - std::string _name; - std::string _display; - }; + std::string Name() const; + std::string DisplayName() const; +private: + std::string name_; + std::string display_; +}; } namespace YAML { - template<> - struct convert < loot::File > { - static Node encode(const loot::File& rhs) { - Node node; - node["name"] = rhs.Name(); +template<> +struct convert { + static Node encode(const loot::File& rhs) { + Node node; + node["name"] = rhs.Name(); - if (rhs.IsConditional()) - node["condition"] = rhs.Condition(); + if (rhs.IsConditional()) + node["condition"] = rhs.Condition(); - if (rhs.DisplayName() != rhs.Name()) - node["display"] = rhs.DisplayName(); + if (rhs.DisplayName() != rhs.Name()) + node["display"] = rhs.DisplayName(); - return node; - } + return node; + } - static bool decode(const Node& node, loot::File& rhs) { - if (!node.IsMap() && !node.IsScalar()) - throw RepresentationException(node.Mark(), "bad conversion: 'file' object must be a map or scalar"); + static bool decode(const Node& node, loot::File& rhs) { + if (!node.IsMap() && !node.IsScalar()) + throw RepresentationException(node.Mark(), "bad conversion: 'file' object must be a map or scalar"); - if (node.IsMap()) { - if (!node["name"]) - throw RepresentationException(node.Mark(), "bad conversion: 'name' key missing from 'file' map object"); + if (node.IsMap()) { + if (!node["name"]) + throw RepresentationException(node.Mark(), "bad conversion: 'name' key missing from 'file' map object"); - std::string name = node["name"].as(); - std::string condition, display; - if (node["condition"]) - condition = node["condition"].as(); - if (node["display"]) - display = node["display"].as(); - rhs = loot::File(name, display, condition); - } - else - rhs = loot::File(node.as()); + std::string name = node["name"].as(); + std::string condition, display; + if (node["condition"]) + condition = node["condition"].as(); + if (node["display"]) + display = node["display"].as(); + rhs = loot::File(name, display, condition); + } else + rhs = loot::File(node.as()); - // Test condition syntax. - try { - rhs.ParseCondition(); - } - catch (std::exception& e) { - throw RepresentationException(node.Mark(), std::string("bad conversion: invalid condition syntax: ") + e.what()); - } + // Test condition syntax. + try { + rhs.ParseCondition(); + } catch (std::exception& e) { + throw RepresentationException(node.Mark(), std::string("bad conversion: invalid condition syntax: ") + e.what()); + } - return true; - } - }; + return true; + } +}; - Emitter& operator << (Emitter& out, const loot::File& rhs); +Emitter& operator << (Emitter& out, const loot::File& rhs); } #endif diff --git a/src/backend/metadata/location.cpp b/src/backend/metadata/location.cpp index ce68a6d3..c7542a23 100644 --- a/src/backend/metadata/location.cpp +++ b/src/backend/metadata/location.cpp @@ -26,40 +26,38 @@ #include -using namespace std; - namespace loot { - Location::Location() {} +Location::Location() {} - Location::Location(const std::string& url, const std::string& name) : _url(url), _name(name) {} +Location::Location(const std::string& url, const std::string& name) : url_(url), name_(name) {} - bool Location::operator < (const Location& rhs) const { - return boost::ilexicographical_compare(_url, rhs.URL()); - } +bool Location::operator < (const Location& rhs) const { + return boost::ilexicographical_compare(url_, rhs.URL()); +} - bool Location::operator == (const Location& rhs) const { - return boost::iequals(_url, rhs.URL()); - } +bool Location::operator == (const Location& rhs) const { + return boost::iequals(url_, rhs.URL()); +} - std::string Location::URL() const { - return _url; - } +std::string Location::URL() const { + return url_; +} - std::string Location::Name() const { - return _name; - } +std::string Location::Name() const { + return name_; +} } namespace YAML { - Emitter& operator << (Emitter& out, const loot::Location& rhs) { - if (rhs.Name().empty()) - out << YAML::SingleQuoted << rhs.URL(); - else { - out << BeginMap - << Key << "link" << Value << YAML::SingleQuoted << rhs.URL() - << Key << "name" << Value << YAML::SingleQuoted << rhs.Name() - << EndMap; - } - return out; - } +Emitter& operator << (Emitter& out, const loot::Location& rhs) { + if (rhs.Name().empty()) + out << YAML::SingleQuoted << rhs.URL(); + else { + out << BeginMap + << Key << "link" << Value << YAML::SingleQuoted << rhs.URL() + << Key << "name" << Value << YAML::SingleQuoted << rhs.Name() + << EndMap; + } + return out; +} } diff --git a/src/backend/metadata/location.h b/src/backend/metadata/location.h index 080d2880..b3a6e9b9 100644 --- a/src/backend/metadata/location.h +++ b/src/backend/metadata/location.h @@ -21,8 +21,8 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA_LOCATION__ -#define __LOOT_METADATA_LOCATION__ +#ifndef LOOT_BACKEND_METADATA_LOCATION +#define LOOT_BACKEND_METADATA_LOCATION #include #include @@ -30,60 +30,59 @@ #include namespace loot { - class Location { - public: - Location(); - Location(const std::string& url, const std::string& name = ""); +class Location { +public: + Location(); + Location(const std::string& url, const std::string& name = ""); - bool operator < (const Location& rhs) const; - bool operator == (const Location& rhs) const; + bool operator < (const Location& rhs) const; + bool operator == (const Location& rhs) const; - std::string URL() const; - std::string Name() const; - private: - std::string _url; - std::string _name; - }; + std::string URL() const; + std::string Name() const; +private: + std::string url_; + std::string name_; +}; } namespace YAML { - template<> - struct convert < loot::Location > { - static Node encode(const loot::Location& rhs) { - Node node; +template<> +struct convert { + static Node encode(const loot::Location& rhs) { + Node node; - node["link"] = rhs.URL(); - if (!rhs.Name().empty()) - node["name"] = rhs.Name(); + node["link"] = rhs.URL(); + if (!rhs.Name().empty()) + node["name"] = rhs.Name(); - return node; - } + return node; + } - static bool decode(const Node& node, loot::Location& rhs) { - if (!node.IsMap() && !node.IsScalar()) - throw RepresentationException(node.Mark(), "bad conversion: 'location' object must be a map or scalar"); + static bool decode(const Node& node, loot::Location& rhs) { + if (!node.IsMap() && !node.IsScalar()) + throw RepresentationException(node.Mark(), "bad conversion: 'location' object must be a map or scalar"); - std::string url; - std::string name; + std::string url; + std::string name; - if (node.IsMap()) { - if (!node["link"]) - throw RepresentationException(node.Mark(), "bad conversion: 'link' key missing from 'location' map object"); + if (node.IsMap()) { + if (!node["link"]) + throw RepresentationException(node.Mark(), "bad conversion: 'link' key missing from 'location' map object"); - url = node["link"].as(); - if (node["name"]) - name = node["name"].as(); - } - else - url = node.as(); + url = node["link"].as(); + if (node["name"]) + name = node["name"].as(); + } else + url = node.as(); - rhs = loot::Location(url, name); + rhs = loot::Location(url, name); - return true; - } - }; + return true; + } +}; - Emitter& operator << (Emitter& out, const loot::Location& rhs); +Emitter& operator << (Emitter& out, const loot::Location& rhs); } #endif diff --git a/src/backend/metadata/message.cpp b/src/backend/metadata/message.cpp index bfc921c2..a3f4fdd2 100644 --- a/src/backend/metadata/message.cpp +++ b/src/backend/metadata/message.cpp @@ -22,104 +22,103 @@ . */ -#include "message.h" -#include "../helpers/language.h" -#include "../error.h" +#include "backend/metadata/message.h" #include -using namespace std; +#include "backend/error.h" +#include "backend/game/game.h" +#include "backend/helpers/language.h" namespace loot { - Message::Message() : _type(Message::Type::say) {} +Message::Message() : type_(Type::say) {} - Message::Message(const Type type, const std::string& content, - const std::string& condition) : _type(type), ConditionalMetadata(condition) { - _content.push_back(MessageContent(content, Language::Code::english)); +Message::Message(const Type type, const std::string& content, + const std::string& condition) : type_(type), ConditionalMetadata(condition) { + content_.push_back(MessageContent(content, Language::Code::english)); +} + +Message::Message(const Type type, const std::vector& content, + const std::string& condition) : type_(type), content_(content), ConditionalMetadata(condition) { + if (content.size() > 1) { + bool englishStringExists = false; + for (const auto &mc : content) { + if (mc.GetLanguage() == Language::Code::english) + englishStringExists = true; } + if (!englishStringExists) + throw Error(Error::Code::invalid_args, "bad conversion: multilingual messages must contain an English content string"); + } +} - Message::Message(const Type type, const std::vector& content, - const std::string& condition) : _type(type), _content(content), ConditionalMetadata(condition) { - if (content.size() > 1) { - bool englishStringExists = false; - for (const auto &mc : content) { - if (mc.GetLanguage() == loot::Language::Code::english) - englishStringExists = true; - } - if (!englishStringExists) - throw loot::Error(Error::Code::invalid_args, "bad conversion: multilingual messages must contain an English content string"); - } +bool Message::operator < (const Message& rhs) const { + if (!content_.empty() && !rhs.GetContent().empty()) + return boost::ilexicographical_compare(ChooseContent(Language::Code::english).GetText(), rhs.ChooseContent(Language::Code::english).GetText()); + else if (content_.empty() && !rhs.GetContent().empty()) + return true; + else + return false; +} + +bool Message::operator == (const Message& rhs) const { + return (content_ == rhs.GetContent()); +} + +bool Message::EvalCondition(loot::Game& game, const Language::Code language) { + BOOST_LOG_TRIVIAL(trace) << "Choosing message content for language: " << Language(language).GetName(); + content_.assign({ChooseContent(language)}); + + return ConditionalMetadata::EvalCondition(game); +} + +MessageContent Message::ChooseContent(const Language::Code language) const { + BOOST_LOG_TRIVIAL(trace) << "Choosing message content."; + if (content_.empty()) + return MessageContent(); + else if (content_.size() == 1) + return content_[0]; + else { + MessageContent english; + for (const auto &mc : content_) { + if (mc.GetLanguage() == language) { + return mc; + } else if (mc.GetLanguage() == Language::Code::english) + english = mc; } + return english; + } +} - bool Message::operator < (const Message& rhs) const { - if (!_content.empty() && !rhs.GetContent().empty()) - return boost::ilexicographical_compare(ChooseContent(Language::Code::english).GetText(), rhs.ChooseContent(Language::Code::english).GetText()); - else if (_content.empty() && !rhs.GetContent().empty()) - return true; - else - return false; - } +Message::Type Message::GetType() const { + return type_; +} - bool Message::operator == (const Message& rhs) const { - return (_content == rhs.GetContent()); - } - - bool Message::EvalCondition(loot::Game& game, const Language::Code language) { - BOOST_LOG_TRIVIAL(trace) << "Choosing message content for language: " << Language(language).GetName(); - _content.assign({ChooseContent(language)}); - - return ConditionalMetadata::EvalCondition(game); - } - - MessageContent Message::ChooseContent(const Language::Code language) const { - BOOST_LOG_TRIVIAL(trace) << "Choosing message content."; - if (_content.empty()) - return MessageContent(); - else if (_content.size() == 1) - return _content[0]; - else { - MessageContent english; - for (const auto &mc : _content) { - if (mc.GetLanguage() == language) { - return mc; - } - else if (mc.GetLanguage() == Language::Code::english) - english = mc; - } - return english; - } - } - - Message::Type Message::GetType() const { - return _type; - } - - std::vector Message::GetContent() const { - return _content; - } +std::vector Message::GetContent() const { + return content_; +} } namespace YAML { - Emitter& operator << (Emitter& out, const loot::Message& rhs) { - out << BeginMap; +Emitter& operator << (Emitter& out, const loot::Message& rhs) { + out << BeginMap; - if (rhs.GetType() == loot::Message::Type::say) - out << Key << "type" << Value << "say"; - else if (rhs.GetType() == loot::Message::Type::warn) - out << Key << "type" << Value << "warn"; - else - out << Key << "type" << Value << "error"; + if (rhs.GetType() == loot::Message::Type::say) + out << Key << "type" << Value << "say"; + else if (rhs.GetType() == loot::Message::Type::warn) + out << Key << "type" << Value << "warn"; + else + out << Key << "type" << Value << "error"; - if (rhs.GetContent().size() == 1) - out << Key << "content" << Value << YAML::SingleQuoted << rhs.GetContent().front().GetText(); - else - out << Key << "content" << Value << rhs.GetContent(); + if (rhs.GetContent().size() == 1) + out << Key << "content" << Value << YAML::SingleQuoted << rhs.GetContent().front().GetText(); + else + out << Key << "content" << Value << rhs.GetContent(); - if (rhs.IsConditional()) - out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition(); + if (rhs.IsConditional()) + out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition(); - out << EndMap; + out << EndMap; - return out; - } + return out; +} } diff --git a/src/backend/metadata/message.h b/src/backend/metadata/message.h index fff41f7e..b11327a1 100644 --- a/src/backend/metadata/message.h +++ b/src/backend/metadata/message.h @@ -21,145 +21,142 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA_MESSAGE__ -#define __LOOT_METADATA_MESSAGE__ - -#include "conditional_metadata.h" -#include "message_content.h" -#include "../helpers/language.h" +#ifndef LOOT_BACKEND_METADATA_MESSAGE +#define LOOT_BACKEND_METADATA_MESSAGE #include #include #include #include - #include +#include "backend/helpers/language.h" +#include "backend/metadata/conditional_metadata.h" +#include "backend/metadata/message_content.h" + namespace loot { - class Game; +class Game; - class Message : public ConditionalMetadata { - public: - enum struct Type : unsigned int { - say = 0, - warn = 1, - error = 2, - }; +class Message : public ConditionalMetadata { +public: + enum struct Type : unsigned int { + say = 0, + warn = 1, + error = 2, + }; - Message(); - Message(const Type type, const std::string& content, - const std::string& condition = ""); - Message(const Type type, const std::vector& content, - const std::string& condition = ""); + Message(); + Message(const Type type, const std::string& content, + const std::string& condition = ""); + Message(const Type type, const std::vector& content, + const std::string& condition = ""); - bool operator < (const Message& rhs) const; - bool operator == (const Message& rhs) const; + bool operator < (const Message& rhs) const; + bool operator == (const Message& rhs) const; - bool EvalCondition(Game& game, const Language::Code language); + bool EvalCondition(Game& game, const Language::Code language); - Type GetType() const; - std::vector GetContent() const; - MessageContent ChooseContent(const Language::Code language) const; - private: - Type _type; - std::vector _content; - }; + Type GetType() const; + std::vector GetContent() const; + MessageContent ChooseContent(const Language::Code language) const; +private: + Type type_; + std::vector content_; +}; } namespace YAML { - template<> - struct convert < loot::Message > { - static Node encode(const loot::Message& rhs) { - Node node; - node["content"] = rhs.GetContent(); +template<> +struct convert { + static Node encode(const loot::Message& rhs) { + Node node; + node["content"] = rhs.GetContent(); - if (rhs.GetType() == loot::Message::Type::say) - node["type"] = "say"; - else if (rhs.GetType() == loot::Message::Type::warn) - node["type"] = "warn"; - else - node["type"] = "error"; + if (rhs.GetType() == loot::Message::Type::say) + node["type"] = "say"; + else if (rhs.GetType() == loot::Message::Type::warn) + node["type"] = "warn"; + else + node["type"] = "error"; - if (rhs.IsConditional()) - node["condition"] = rhs.Condition(); + if (rhs.IsConditional()) + node["condition"] = rhs.Condition(); - return node; + return node; + } + + static bool decode(const Node& node, loot::Message& rhs) { + if (!node.IsMap()) + throw RepresentationException(node.Mark(), "bad conversion: 'message' object must be a map"); + if (!node["type"]) + throw RepresentationException(node.Mark(), "bad conversion: 'type' key missing from 'message' object"); + if (!node["content"]) + throw RepresentationException(node.Mark(), "bad conversion: 'content' key missing from 'message' object"); + + std::string type; + type = node["type"].as(); + + loot::Message::Type typeNo = loot::Message::Type::say; + if (boost::iequals(type, "warn")) + typeNo = loot::Message::Type::warn; + else if (boost::iequals(type, "error")) + typeNo = loot::Message::Type::error; + + std::vector content; + if (node["content"].IsSequence()) + content = node["content"].as< std::vector >(); + else { + content.push_back(loot::MessageContent(node["content"].as(), loot::Language::Code::english)); + } + + //Check now that at least one item in content is English if there are multiple items. + if (content.size() > 1) { + bool found = false; + for (const auto &mc : content) { + if (mc.GetLanguage() == loot::Language::Code::english) + found = true; + } + if (!found) + throw RepresentationException(node.Mark(), "bad conversion: multilingual messages must contain an English content string"); + } + + // Make any substitutions at this point. + if (node["subs"]) { + std::vector subs = node["subs"].as>(); + for (auto& mc : content) { + boost::format f(mc.GetText()); + + for (const auto& sub : subs) { + f = f % sub; } - static bool decode(const Node& node, loot::Message& rhs) { - if (!node.IsMap()) - throw RepresentationException(node.Mark(), "bad conversion: 'message' object must be a map"); - if (!node["type"]) - throw RepresentationException(node.Mark(), "bad conversion: 'type' key missing from 'message' object"); - if (!node["content"]) - throw RepresentationException(node.Mark(), "bad conversion: 'content' key missing from 'message' object"); - - std::string type; - type = node["type"].as(); - - loot::Message::Type typeNo = loot::Message::Type::say; - if (boost::iequals(type, "warn")) - typeNo = loot::Message::Type::warn; - else if (boost::iequals(type, "error")) - typeNo = loot::Message::Type::error; - - std::vector content; - if (node["content"].IsSequence()) - content = node["content"].as< std::vector >(); - else { - content.push_back(loot::MessageContent(node["content"].as(), loot::Language::Code::english)); - } - - //Check now that at least one item in content is English if there are multiple items. - if (content.size() > 1) { - bool found = false; - for (const auto &mc : content) { - if (mc.GetLanguage() == loot::Language::Code::english) - found = true; - } - if (!found) - throw RepresentationException(node.Mark(), "bad conversion: multilingual messages must contain an English content string"); - } - - // Make any substitutions at this point. - if (node["subs"]) { - std::vector subs = node["subs"].as>(); - for (auto& mc : content) { - boost::format f(mc.GetText()); - - for (const auto& sub : subs) { - f = f % sub; - } - - try { - mc = loot::MessageContent(f.str(), mc.GetLanguage()); - } - catch (boost::io::format_error& e) { - throw RepresentationException(node.Mark(), std::string("bad conversion: content substitution error: ") + e.what()); - } - } - } - - std::string condition; - if (node["condition"]) - condition = node["condition"].as(); - - rhs = loot::Message(typeNo, content, condition); - - // Test condition syntax. - try { - rhs.ParseCondition(); - } - catch (std::exception& e) { - throw RepresentationException(node.Mark(), std::string("bad conversion: invalid condition syntax: ") + e.what()); - } - - return true; + try { + mc = loot::MessageContent(f.str(), mc.GetLanguage()); + } catch (boost::io::format_error& e) { + throw RepresentationException(node.Mark(), std::string("bad conversion: content substitution error: ") + e.what()); } - }; + } + } - Emitter& operator << (Emitter& out, const loot::Message& rhs); + std::string condition; + if (node["condition"]) + condition = node["condition"].as(); + + rhs = loot::Message(typeNo, content, condition); + + // Test condition syntax. + try { + rhs.ParseCondition(); + } catch (std::exception& e) { + throw RepresentationException(node.Mark(), std::string("bad conversion: invalid condition syntax: ") + e.what()); + } + + return true; + } +}; + +Emitter& operator << (Emitter& out, const loot::Message& rhs); } #endif diff --git a/src/backend/metadata/message_content.cpp b/src/backend/metadata/message_content.cpp index ac62962b..edba16c1 100644 --- a/src/backend/metadata/message_content.cpp +++ b/src/backend/metadata/message_content.cpp @@ -22,45 +22,44 @@ . */ -#include "message_content.h" -#include "../helpers/language.h" +#include "backend/metadata/message_content.h" #include -using namespace std; +#include "backend/helpers/language.h" namespace loot { - MessageContent::MessageContent() : _language(Language::Code::english) {} +MessageContent::MessageContent() : language_(Language::Code::english) {} - MessageContent::MessageContent(const std::string& str, const Language::Code language) : _str(str), _language(language) {} +MessageContent::MessageContent(const std::string& text, const Language::Code language) : text_(text), language_(language) {} - std::string MessageContent::GetText() const { - return _str; - } +std::string MessageContent::GetText() const { + return text_; +} - Language::Code MessageContent::GetLanguage() const { - return _language; - } +Language::Code MessageContent::GetLanguage() const { + return language_; +} - bool MessageContent::operator < (const MessageContent& rhs) const { - return boost::ilexicographical_compare(_str, rhs.GetText()); - } +bool MessageContent::operator < (const MessageContent& rhs) const { + return boost::ilexicographical_compare(text_, rhs.GetText()); +} - bool MessageContent::operator == (const MessageContent& rhs) const { - return (boost::iequals(_str, rhs.GetText())); - } +bool MessageContent::operator == (const MessageContent& rhs) const { + return (boost::iequals(text_, rhs.GetText())); +} } namespace YAML { - Emitter& operator << (Emitter& out, const loot::MessageContent& rhs) { - out << BeginMap; +Emitter& operator << (Emitter& out, const loot::MessageContent& rhs) { + out << BeginMap; - out << Key << "lang" << Value << loot::Language(rhs.GetLanguage()).GetLocale(); + out << Key << "lang" << Value << loot::Language(rhs.GetLanguage()).GetLocale(); - out << Key << "str" << Value << YAML::SingleQuoted << rhs.GetText(); + out << Key << "str" << Value << YAML::SingleQuoted << rhs.GetText(); - out << EndMap; + out << EndMap; - return out; - } + return out; +} } diff --git a/src/backend/metadata/message_content.h b/src/backend/metadata/message_content.h index e003b247..7c658668 100644 --- a/src/backend/metadata/message_content.h +++ b/src/backend/metadata/message_content.h @@ -21,61 +21,61 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA_MESSAGE_CONTENT__ -#define __LOOT_METADATA_MESSAGE_CONTENT__ - -#include "../helpers/language.h" +#ifndef LOOT_BACKEND_METADATA_MESSAGE_CONTENT +#define LOOT_BACKEND_METADATA_MESSAGE_CONTENT #include #include +#include "backend/helpers/language.h" + namespace loot { - class MessageContent { - public: - MessageContent(); - MessageContent(const std::string& str, const Language::Code language); +class MessageContent { +public: + MessageContent(); + MessageContent(const std::string& text, const Language::Code language); - std::string GetText() const; - Language::Code GetLanguage() const; + std::string GetText() const; + Language::Code GetLanguage() const; - bool operator < (const MessageContent& rhs) const; - bool operator == (const MessageContent& rhs) const; - private: - std::string _str; - Language::Code _language; - }; + bool operator < (const MessageContent& rhs) const; + bool operator == (const MessageContent& rhs) const; +private: + std::string text_; + Language::Code language_; +}; } namespace YAML { - template<> - struct convert < loot::MessageContent > { - static Node encode(const loot::MessageContent& rhs) { - Node node; - node["str"] = rhs.GetText(); - node["lang"] = loot::Language(rhs.GetLanguage()).GetLocale(); +template<> +struct convert { + static Node encode(const loot::MessageContent& rhs) { + Node node; + node["str"] = rhs.GetText(); + node["lang"] = loot::Language(rhs.GetLanguage()).GetLocale(); - return node; - } + return node; + } - static bool decode(const Node& node, loot::MessageContent& rhs) { - if (!node.IsMap()) - throw RepresentationException(node.Mark(), "bad conversion: 'message content' object must be a map"); - if (!node["str"]) - throw RepresentationException(node.Mark(), "bad conversion: 'str' key missing from 'message content' object"); - if (!node["lang"]) - throw RepresentationException(node.Mark(), "bad conversion: 'lang' key missing from 'message content' object"); + static bool decode(const Node& node, loot::MessageContent& rhs) { + if (!node.IsMap()) + throw RepresentationException(node.Mark(), "bad conversion: 'message content' object must be a map"); + if (!node["str"]) + throw RepresentationException(node.Mark(), "bad conversion: 'str' key missing from 'message content' object"); + if (!node["lang"]) + throw RepresentationException(node.Mark(), "bad conversion: 'lang' key missing from 'message content' object"); - std::string str = node["str"].as(); - loot::Language::Code lang = loot::Language(node["lang"].as()).GetCode(); + std::string str = node["str"].as(); + loot::Language::Code lang = loot::Language(node["lang"].as()).GetCode(); - rhs = loot::MessageContent(str, lang); + rhs = loot::MessageContent(str, lang); - return true; - } - }; + return true; + } +}; - Emitter& operator << (Emitter& out, const loot::MessageContent& rhs); +Emitter& operator << (Emitter& out, const loot::MessageContent& rhs); } #endif diff --git a/src/backend/metadata/plugin_dirty_info.cpp b/src/backend/metadata/plugin_dirty_info.cpp index 5fc9a07e..7c134090 100644 --- a/src/backend/metadata/plugin_dirty_info.cpp +++ b/src/backend/metadata/plugin_dirty_info.cpp @@ -22,116 +22,112 @@ . */ -#include "plugin_dirty_info.h" +#include "backend/metadata/plugin_dirty_info.h" -#include "../game/game.h" -#include "../helpers/helpers.h" - -#include #include +#include -using namespace std; +#include "backend/game/game.h" +#include "backend/helpers/helpers.h" namespace loot { - PluginDirtyInfo::PluginDirtyInfo() : _crc(0), _itm(0), _ref(0), _nav(0) {} +PluginDirtyInfo::PluginDirtyInfo() : _crc(0), _itm(0), _ref(0), _nav(0) {} - PluginDirtyInfo::PluginDirtyInfo(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility) : _crc(crc), _itm(itm), _ref(ref), _nav(nav), _utility(utility) {} +PluginDirtyInfo::PluginDirtyInfo(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility) : _crc(crc), _itm(itm), _ref(ref), _nav(nav), _utility(utility) {} - bool PluginDirtyInfo::operator < (const PluginDirtyInfo& rhs) const { - return _crc < rhs.CRC(); +bool PluginDirtyInfo::operator < (const PluginDirtyInfo& rhs) const { + return _crc < rhs.CRC(); +} + +bool PluginDirtyInfo::operator == (const PluginDirtyInfo& rhs) const { + return _crc == rhs.CRC(); +} + +uint32_t PluginDirtyInfo::CRC() const { + return _crc; +} + +unsigned int PluginDirtyInfo::ITMs() const { + return _itm; +} + +unsigned int PluginDirtyInfo::DeletedRefs() const { + return _ref; +} + +unsigned int PluginDirtyInfo::DeletedNavmeshes() const { + return _nav; +} + +std::string PluginDirtyInfo::CleaningUtility() const { + return _utility; +} + +Message PluginDirtyInfo::AsMessage() const { + boost::format f; + if (this->_itm > 0 && this->_ref > 0 && this->_nav > 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records, %2% deleted references and %3% deleted navmeshes. Clean with %4%.")) % this->_itm % this->_ref % this->_nav % this->_utility; + else if (this->_itm == 0 && this->_ref == 0 && this->_nav == 0) + f = boost::format(boost::locale::translate("Clean with %1%.")) % this->_utility; + + else if (this->_itm == 0 && this->_ref > 0 && this->_nav > 0) + f = boost::format(boost::locale::translate("Contains %1% deleted references and %2% deleted navmeshes. Clean with %3%.")) % this->_ref % this->_nav % this->_utility; + else if (this->_itm == 0 && this->_ref == 0 && this->_nav > 0) + f = boost::format(boost::locale::translate("Contains %1% deleted navmeshes. Clean with %2%.")) % this->_nav % this->_utility; + else if (this->_itm == 0 && this->_ref > 0 && this->_nav == 0) + f = boost::format(boost::locale::translate("Contains %1% deleted references. Clean with %2%.")) % this->_ref % this->_utility; + + else if (this->_itm > 0 && this->_ref == 0 && this->_nav > 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted navmeshes. Clean with %3%.")) % this->_itm % this->_nav % this->_utility; + else if (this->_itm > 0 && this->_ref == 0 && this->_nav == 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records. Clean with %2%.")) % this->_itm % this->_utility; + + else if (this->_itm > 0 && this->_ref > 0 && this->_nav == 0) + f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted references. Clean with %3%.")) % this->_itm % this->_ref % this->_utility; + + return Message(Message::Type::warn, f.str()); +} + +bool PluginDirtyInfo::EvalCondition(Game& game, const std::string& pluginName) const { + if (pluginName.empty()) + return false; + +// First need to get plugin's CRC. + uint32_t crc = 0; + + // Get the CRC from the game plugin cache if possible. + try { + crc = game.GetPlugin(pluginName).Crc(); + } catch (...) {} + + // Otherwise calculate it from the file. + if (crc == 0) { + if (boost::filesystem::exists(game.DataPath() / pluginName)) { + crc = GetCrc32(game.DataPath() / pluginName); + } else if (boost::filesystem::exists(game.DataPath() / (pluginName + ".ghost"))) { + crc = GetCrc32(game.DataPath() / (pluginName + ".ghost")); } + } - bool PluginDirtyInfo::operator == (const PluginDirtyInfo& rhs) const { - return _crc == rhs.CRC(); - } - - uint32_t PluginDirtyInfo::CRC() const { - return _crc; - } - - unsigned int PluginDirtyInfo::ITMs() const { - return _itm; - } - - unsigned int PluginDirtyInfo::DeletedRefs() const { - return _ref; - } - - unsigned int PluginDirtyInfo::DeletedNavmeshes() const { - return _nav; - } - - std::string PluginDirtyInfo::CleaningUtility() const { - return _utility; - } - - Message PluginDirtyInfo::AsMessage() const { - boost::format f; - if (this->_itm > 0 && this->_ref > 0 && this->_nav > 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records, %2% deleted references and %3% deleted navmeshes. Clean with %4%.")) % this->_itm % this->_ref % this->_nav % this->_utility; - else if (this->_itm == 0 && this->_ref == 0 && this->_nav == 0) - f = boost::format(boost::locale::translate("Clean with %1%.")) % this->_utility; - - else if (this->_itm == 0 && this->_ref > 0 && this->_nav > 0) - f = boost::format(boost::locale::translate("Contains %1% deleted references and %2% deleted navmeshes. Clean with %3%.")) % this->_ref % this->_nav % this->_utility; - else if (this->_itm == 0 && this->_ref == 0 && this->_nav > 0) - f = boost::format(boost::locale::translate("Contains %1% deleted navmeshes. Clean with %2%.")) % this->_nav % this->_utility; - else if (this->_itm == 0 && this->_ref > 0 && this->_nav == 0) - f = boost::format(boost::locale::translate("Contains %1% deleted references. Clean with %2%.")) % this->_ref % this->_utility; - - else if (this->_itm > 0 && this->_ref == 0 && this->_nav > 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted navmeshes. Clean with %3%.")) % this->_itm % this->_nav % this->_utility; - else if (this->_itm > 0 && this->_ref == 0 && this->_nav == 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records. Clean with %2%.")) % this->_itm % this->_utility; - - else if (this->_itm > 0 && this->_ref > 0 && this->_nav == 0) - f = boost::format(boost::locale::translate("Contains %1% ITM records and %2% deleted references. Clean with %3%.")) % this->_itm % this->_ref % this->_utility; - - return Message(Message::Type::warn, f.str()); - } - - bool PluginDirtyInfo::EvalCondition(Game& game, const std::string& pluginName) const { - if (pluginName.empty()) - return false; - - // First need to get plugin's CRC. - uint32_t crc = 0; - - // Get the CRC from the game plugin cache if possible. - try { - crc = game.GetPlugin(pluginName).Crc(); - } - catch (...) {} - - // Otherwise calculate it from the file. - if (crc == 0) { - if (boost::filesystem::exists(game.DataPath() / pluginName)) { - crc = GetCrc32(game.DataPath() / pluginName); - } - else if (boost::filesystem::exists(game.DataPath() / (pluginName + ".ghost"))) { - crc = GetCrc32(game.DataPath() / (pluginName + ".ghost")); - } - } - - return _crc == crc; - } + return _crc == crc; +} } namespace YAML { - Emitter& operator << (Emitter& out, const loot::PluginDirtyInfo& rhs) { - out << BeginMap - << Key << "crc" << Value << Hex << rhs.CRC() << Dec - << Key << "util" << Value << YAML::SingleQuoted << rhs.CleaningUtility(); +Emitter& operator << (Emitter& out, const loot::PluginDirtyInfo& rhs) { + out << BeginMap + << Key << "crc" << Value << Hex << rhs.CRC() << Dec + << Key << "util" << Value << YAML::SingleQuoted << rhs.CleaningUtility(); - if (rhs.ITMs() > 0) - out << Key << "itm" << Value << rhs.ITMs(); - if (rhs.DeletedRefs() > 0) - out << Key << "udr" << Value << rhs.DeletedRefs(); - if (rhs.DeletedNavmeshes() > 0) - out << Key << "nav" << Value << rhs.DeletedNavmeshes(); + if (rhs.ITMs() > 0) + out << Key << "itm" << Value << rhs.ITMs(); + if (rhs.DeletedRefs() > 0) + out << Key << "udr" << Value << rhs.DeletedRefs(); + if (rhs.DeletedNavmeshes() > 0) + out << Key << "nav" << Value << rhs.DeletedNavmeshes(); - out << EndMap; + out << EndMap; - return out; - } + return out; +} } diff --git a/src/backend/metadata/plugin_dirty_info.h b/src/backend/metadata/plugin_dirty_info.h index 382ec390..bc229efb 100644 --- a/src/backend/metadata/plugin_dirty_info.h +++ b/src/backend/metadata/plugin_dirty_info.h @@ -21,90 +21,90 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA_PLUGIN_DIRTY_INFO__ -#define __LOOT_METADATA_PLUGIN_DIRTY_INFO__ - -#include "message.h" +#ifndef LOOT_BACKEND_METADATA_PLUGIN_DIRTY_INFO +#define LOOT_BACKEND_METADATA_PLUGIN_DIRTY_INFO #include #include #include +#include "backend/metadata/message.h" + namespace loot { - class Game; +class Game; - class PluginDirtyInfo { - public: - PluginDirtyInfo(); - PluginDirtyInfo(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility); +class PluginDirtyInfo { +public: + PluginDirtyInfo(); + PluginDirtyInfo(uint32_t crc, unsigned int itm, unsigned int ref, unsigned int nav, const std::string& utility); - bool operator < (const PluginDirtyInfo& rhs) const; - bool operator == (const PluginDirtyInfo& rhs) const; + bool operator < (const PluginDirtyInfo& rhs) const; + bool operator == (const PluginDirtyInfo& rhs) const; - uint32_t CRC() const; - unsigned int ITMs() const; - unsigned int DeletedRefs() const; - unsigned int DeletedNavmeshes() const; - std::string CleaningUtility() const; + uint32_t CRC() const; + unsigned int ITMs() const; + unsigned int DeletedRefs() const; + unsigned int DeletedNavmeshes() const; + std::string CleaningUtility() const; - Message AsMessage() const; + Message AsMessage() const; - bool EvalCondition(Game& game, const std::string& pluginName) const; - private: - uint32_t _crc; - unsigned int _itm; - unsigned int _ref; - unsigned int _nav; - std::string _utility; - }; + bool EvalCondition(Game& game, const std::string& pluginName) const; +private: + uint32_t _crc; + unsigned int _itm; + unsigned int _ref; + unsigned int _nav; + std::string _utility; +}; } namespace YAML { - template<> - struct convert < loot::PluginDirtyInfo > { - static Node encode(const loot::PluginDirtyInfo& rhs) { - Node node; - node["crc"] = rhs.CRC(); - node["util"] = rhs.CleaningUtility(); +template<> +struct convert { + static Node encode(const loot::PluginDirtyInfo& rhs) { + Node node; + node["crc"] = rhs.CRC(); + node["util"] = rhs.CleaningUtility(); - if (rhs.ITMs() > 0) - node["itm"] = rhs.ITMs(); - if (rhs.DeletedRefs() > 0) - node["udr"] = rhs.DeletedRefs(); - if (rhs.DeletedNavmeshes() > 0) - node["nav"] = rhs.DeletedNavmeshes(); + if (rhs.ITMs() > 0) + node["itm"] = rhs.ITMs(); + if (rhs.DeletedRefs() > 0) + node["udr"] = rhs.DeletedRefs(); + if (rhs.DeletedNavmeshes() > 0) + node["nav"] = rhs.DeletedNavmeshes(); - return node; - } + return node; + } - static bool decode(const Node& node, loot::PluginDirtyInfo& rhs) { - if (!node.IsMap()) - throw RepresentationException(node.Mark(), "bad conversion: 'dirty info' object must be a map"); - if (!node["crc"]) - throw RepresentationException(node.Mark(), "bad conversion: 'crc' key missing from 'dirty info' object"); - if (!node["util"]) - throw RepresentationException(node.Mark(), "bad conversion: 'util' key missing from 'dirty info' object"); + static bool decode(const Node& node, loot::PluginDirtyInfo& rhs) { + if (!node.IsMap()) + throw RepresentationException(node.Mark(), "bad conversion: 'dirty info' object must be a map"); + if (!node["crc"]) + throw RepresentationException(node.Mark(), "bad conversion: 'crc' key missing from 'dirty info' object"); + if (!node["util"]) + throw RepresentationException(node.Mark(), "bad conversion: 'util' key missing from 'dirty info' object"); - uint32_t crc = node["crc"].as(); - int itm = 0, ref = 0, nav = 0; + uint32_t crc = node["crc"].as(); + int itm = 0, ref = 0, nav = 0; - if (node["itm"]) - itm = node["itm"].as(); - if (node["udr"]) - ref = node["udr"].as(); - if (node["nav"]) - nav = node["nav"].as(); + if (node["itm"]) + itm = node["itm"].as(); + if (node["udr"]) + ref = node["udr"].as(); + if (node["nav"]) + nav = node["nav"].as(); - std::string utility = node["util"].as(); + std::string utility = node["util"].as(); - rhs = loot::PluginDirtyInfo(crc, itm, ref, nav, utility); + rhs = loot::PluginDirtyInfo(crc, itm, ref, nav, utility); - return true; - } - }; + return true; + } +}; - Emitter& operator << (Emitter& out, const loot::PluginDirtyInfo& rhs); +Emitter& operator << (Emitter& out, const loot::PluginDirtyInfo& rhs); } #endif diff --git a/src/backend/metadata/plugin_metadata.cpp b/src/backend/metadata/plugin_metadata.cpp index 6cf4e4ec..55d40984 100644 --- a/src/backend/metadata/plugin_metadata.cpp +++ b/src/backend/metadata/plugin_metadata.cpp @@ -23,436 +23,446 @@ */ #include "plugin_metadata.h" -#include "../game/game.h" -#include "../helpers/helpers.h" -#include "../error.h" + +#include #include #include -#include #include #include -#include +#include -using namespace std; +#include "backend/error.h" +#include "backend/game/game.h" +#include "backend/helpers/helpers.h" + +using std::inserter; +using std::list; +using std::regex; +using std::regex_match; +using std::set; namespace loot { - PluginMetadata::PluginMetadata() : enabled(true), _isPriorityExplicit(false), isPriorityGlobal(false), priority(0) {} +PluginMetadata::PluginMetadata() : enabled_(true), isPriorityExplicit_(false), isPriorityGlobal_(false), priority_(0) {} - PluginMetadata::PluginMetadata(const std::string& n) : name(n), enabled(true), _isPriorityExplicit(false), isPriorityGlobal(false), priority(0) { - //If the name passed ends in '.ghost', that should be trimmed. - if (boost::iends_with(name, ".ghost")) - name = name.substr(0, name.length() - 6); - } - - void PluginMetadata::MergeMetadata(const PluginMetadata& plugin) { - BOOST_LOG_TRIVIAL(trace) << "Merging metadata for: " << name; - if (plugin.HasNameOnly()) - return; - - // For 'enabled' and 'priority' metadata, use the given plugin's values, - // but if the 'priority' user value is not explicit, ignore it. - enabled = plugin.Enabled(); - if (plugin.IsPriorityExplicit()) { - Priority(plugin.Priority()); - SetPriorityGlobal(plugin.IsPriorityGlobal()); - _isPriorityExplicit = true; - } - - // Merge the following. If any files in the source already exist in the - // destination, they will be skipped. Files have display strings and - // condition strings which aren't considered when comparing them, so - // will be lost if the plugin being merged in has additional data in - // these strings. - loadAfter.insert(begin(plugin.loadAfter), end(plugin.loadAfter)); - requirements.insert(begin(plugin.requirements), end(plugin.requirements)); - incompatibilities.insert(begin(plugin.incompatibilities), end(plugin.incompatibilities)); - - // Merge Bash Tags too. Conditions are ignored during comparison, but - // if a tag is added and removed, both instances will be in the set. - tags.insert(begin(plugin.tags), end(plugin.tags)); - - // Messages are in an ordered list, and should be fully merged. - messages.insert(end(messages), begin(plugin.messages), end(plugin.messages)); - - _dirtyInfo.insert(begin(plugin._dirtyInfo), end(plugin._dirtyInfo)); - _locations.insert(begin(plugin._locations), end(plugin._locations)); - - return; - } - - PluginMetadata PluginMetadata::DiffMetadata(const PluginMetadata& plugin) const { - BOOST_LOG_TRIVIAL(trace) << "Calculating metadata difference for: " << name; - PluginMetadata p(*this); - - if (Priority() == plugin.Priority() && IsPriorityGlobal() == plugin.IsPriorityGlobal()) { - p.Priority(0); - p.SetPriorityGlobal(false); - p.SetPriorityExplicit(false); - } - - //Compare this plugin against the given plugin. - set filesDiff; - set_symmetric_difference(begin(loadAfter), - end(loadAfter), - begin(plugin.loadAfter), - end(plugin.loadAfter), - inserter(filesDiff, begin(filesDiff))); - p.LoadAfter(filesDiff); - - filesDiff.clear(); - set_symmetric_difference(begin(requirements), - end(requirements), - begin(plugin.requirements), - end(plugin.requirements), - inserter(filesDiff, begin(filesDiff))); - p.Reqs(filesDiff); - - filesDiff.clear(); - set_symmetric_difference(begin(incompatibilities), - end(incompatibilities), - begin(plugin.incompatibilities), - end(plugin.incompatibilities), - inserter(filesDiff, begin(filesDiff))); - p.Incs(filesDiff); - - list msgs1 = plugin.Messages(); - list msgs2 = messages; - msgs1.sort(); - msgs2.sort(); - list mDiff; - set_symmetric_difference(begin(msgs2), - end(msgs2), - begin(msgs1), - end(msgs1), - inserter(mDiff, begin(mDiff))); - p.Messages(mDiff); - - set tagDiff; - set_symmetric_difference(begin(tags), - end(tags), - begin(plugin.tags), - end(plugin.tags), - inserter(tagDiff, begin(tagDiff))); - p.Tags(tagDiff); - - set dirtDiff; - set_symmetric_difference(begin(_dirtyInfo), - end(_dirtyInfo), - begin(plugin._dirtyInfo), - end(plugin._dirtyInfo), - inserter(dirtDiff, begin(dirtDiff))); - p.DirtyInfo(dirtDiff); - - set locationsDiff; - set_symmetric_difference(begin(_locations), - end(_locations), - begin(plugin._locations), - end(plugin._locations), - inserter(locationsDiff, begin(locationsDiff))); - p.Locations(locationsDiff); - - return p; - } - - PluginMetadata PluginMetadata::NewMetadata(const PluginMetadata& plugin) const { - BOOST_LOG_TRIVIAL(trace) << "Comparing new metadata for: " << name; - PluginMetadata p(*this); - - //Compare this plugin against the given plugin. - set filesDiff; - set_difference(begin(loadAfter), - end(loadAfter), - begin(plugin.loadAfter), - end(plugin.loadAfter), - inserter(filesDiff, begin(filesDiff))); - p.LoadAfter(filesDiff); - - filesDiff.clear(); - set_difference(begin(requirements), - end(requirements), - begin(plugin.requirements), - end(plugin.requirements), - inserter(filesDiff, begin(filesDiff))); - p.Reqs(filesDiff); - - filesDiff.clear(); - set_difference(begin(incompatibilities), - end(incompatibilities), - begin(plugin.incompatibilities), - end(plugin.incompatibilities), - inserter(filesDiff, begin(filesDiff))); - p.Incs(filesDiff); - - list msgs1 = plugin.Messages(); - list msgs2 = messages; - msgs1.sort(); - msgs2.sort(); - list mDiff; - set_difference(begin(msgs2), - end(msgs2), - begin(msgs1), - end(msgs1), - inserter(mDiff, begin(mDiff))); - p.Messages(mDiff); - - set tagDiff; - set_difference(begin(tags), - end(tags), - begin(plugin.tags), - end(plugin.tags), - inserter(tagDiff, begin(tagDiff))); - p.Tags(tagDiff); - - set dirtDiff; - set_difference(begin(_dirtyInfo), - end(_dirtyInfo), - begin(plugin._dirtyInfo), - end(plugin._dirtyInfo), - inserter(dirtDiff, begin(dirtDiff))); - p.DirtyInfo(dirtDiff); - - set locationsDiff; - set_difference(begin(_locations), - end(_locations), - begin(plugin._locations), - end(plugin._locations), - inserter(locationsDiff, begin(locationsDiff))); - p.Locations(locationsDiff); - - return p; - } - - std::string PluginMetadata::Name() const { - return name; - } - - bool PluginMetadata::Enabled() const { - return enabled; - } - - int PluginMetadata::Priority() const { - return priority; - } - - bool PluginMetadata::IsPriorityExplicit() const { - return priority != 0 || _isPriorityExplicit; - } - - bool PluginMetadata::IsPriorityGlobal() const { - return isPriorityGlobal; - } - - std::set PluginMetadata::LoadAfter() const { - return loadAfter; - } - - std::set PluginMetadata::Reqs() const { - return requirements; - } - - std::set PluginMetadata::Incs() const { - return incompatibilities; - } - - std::list PluginMetadata::Messages() const { - return messages; - } - - std::set PluginMetadata::Tags() const { - return tags; - } - - std::set PluginMetadata::DirtyInfo() const { - return _dirtyInfo; - } - - std::set PluginMetadata::Locations() const { - return _locations; - } - - void PluginMetadata::Enabled(const bool e) { - enabled = e; - } - - void PluginMetadata::Priority(const int p) { - if (abs(p) >= yamlGlobalPriorityDivisor) - throw Error(Error::Code::invalid_args, "Cannot set priority that has an absolute value greater than or equal to " + to_string(yamlGlobalPriorityDivisor)); - - priority = p; - } - - void PluginMetadata::SetPriorityExplicit(bool state) { - _isPriorityExplicit = state; - } - - void PluginMetadata::SetPriorityGlobal(bool state) { - isPriorityGlobal = state; - } - - void PluginMetadata::LoadAfter(const std::set& l) { - loadAfter = l; - } - - void PluginMetadata::Reqs(const std::set& r) { - requirements = r; - } - - void PluginMetadata::Incs(const std::set& i) { - incompatibilities = i; - } - - void PluginMetadata::Messages(const std::list& m) { - messages = m; - } - - void PluginMetadata::Tags(const std::set& t) { - tags = t; - } - - void PluginMetadata::DirtyInfo(const std::set& dirtyInfo) { - _dirtyInfo = dirtyInfo; - } - - void PluginMetadata::Locations(const std::set& locations) { - _locations = locations; - } - - PluginMetadata& PluginMetadata::EvalAllConditions(Game& game, const Language::Code language) { - for (auto it = loadAfter.begin(); it != loadAfter.end();) { - if (!it->EvalCondition(game)) - loadAfter.erase(it++); - else - ++it; - } - - for (auto it = requirements.begin(); it != requirements.end();) { - if (!it->EvalCondition(game)) - requirements.erase(it++); - else - ++it; - } - - for (auto it = incompatibilities.begin(); it != incompatibilities.end();) { - if (!it->EvalCondition(game)) - incompatibilities.erase(it++); - else - ++it; - } - - for (auto it = messages.begin(); it != messages.end();) { - if (!it->EvalCondition(game, language)) - it = messages.erase(it); - else - ++it; - } - - for (auto it = tags.begin(); it != tags.end();) { - if (!it->EvalCondition(game)) - tags.erase(it++); - else - ++it; - } - - if (IsRegexPlugin()) // Remove any dirty metadata from a regex plugin. - _dirtyInfo.clear(); - else { - for (auto it = _dirtyInfo.begin(); it != _dirtyInfo.end();) { - if (!it->EvalCondition(game, name)) - _dirtyInfo.erase(it++); - else - ++it; - } - } - - return *this; - } - - bool PluginMetadata::HasNameOnly() const { - return !IsPriorityExplicit() && loadAfter.empty() && requirements.empty() && incompatibilities.empty() && messages.empty() && tags.empty() && _dirtyInfo.empty() && _locations.empty(); - } - - bool PluginMetadata::IsRegexPlugin() const { - // Treat as regex if the plugin filename contains any of ":\*?|" as - // they are not valid Windows filename characters, but have meaning - // in regexes. - return strpbrk(name.c_str(), ":\\*?|") != nullptr; - } - - bool PluginMetadata::operator == (const PluginMetadata& rhs) const { - if (IsRegexPlugin() == rhs.IsRegexPlugin()) - return boost::iequals(name, rhs.Name()); - - if (IsRegexPlugin()) - return regex_match(rhs.Name(), regex(name, regex::ECMAScript | regex::icase)); - else - return regex_match(name, regex(rhs.Name(), regex::ECMAScript | regex::icase)); - } - - bool PluginMetadata::operator != (const PluginMetadata& rhs) const { - return !(*this == rhs); - } - - bool PluginMetadata::operator == (const std::string& rhs) const { - if (IsRegexPlugin()) - return regex_match(PluginMetadata(rhs).Name(), regex(name, regex::ECMAScript | regex::icase)); - else - return boost::iequals(name, PluginMetadata(rhs).Name()); - } - - bool PluginMetadata::operator != (const std::string& rhs) const { - return !(*this == rhs); - } - - int PluginMetadata::GetYamlPriorityValue() const { - int priorityValue = Priority(); - if (IsPriorityGlobal()) { - if (priorityValue < 0) - priorityValue -= loot::yamlGlobalPriorityDivisor; - else - priorityValue += loot::yamlGlobalPriorityDivisor; - } - return priorityValue; +PluginMetadata::PluginMetadata(const std::string& n) : name_(n), enabled_(true), isPriorityExplicit_(false), isPriorityGlobal_(false), priority_(0) { + //If the name passed ends in '.ghost', that should be trimmed. + if (boost::iends_with(name_, ".ghost")) + name_ = name_.substr(0, name_.length() - 6); +} + +void PluginMetadata::MergeMetadata(const PluginMetadata& plugin) { + BOOST_LOG_TRIVIAL(trace) << "Merging metadata for: " << name_; + if (plugin.HasNameOnly()) + return; + +// For 'enabled' and 'priority' metadata, use the given plugin's values, +// but if the 'priority' user value is not explicit, ignore it. + enabled_ = plugin.Enabled(); + if (plugin.IsPriorityExplicit()) { + Priority(plugin.Priority()); + SetPriorityGlobal(plugin.IsPriorityGlobal()); + isPriorityExplicit_ = true; + } + + // Merge the following. If any files in the source already exist in the + // destination, they will be skipped. Files have display strings and + // condition strings which aren't considered when comparing them, so + // will be lost if the plugin being merged in has additional data in + // these strings. + loadAfter_.insert(begin(plugin.loadAfter_), end(plugin.loadAfter_)); + requirements_.insert(begin(plugin.requirements_), end(plugin.requirements_)); + incompatibilities_.insert(begin(plugin.incompatibilities_), end(plugin.incompatibilities_)); + + // Merge Bash Tags too. Conditions are ignored during comparison, but + // if a tag is added and removed, both instances will be in the set. + tags_.insert(begin(plugin.tags_), end(plugin.tags_)); + + // Messages are in an ordered list, and should be fully merged. + messages_.insert(end(messages_), begin(plugin.messages_), end(plugin.messages_)); + + dirtyInfo_.insert(begin(plugin.dirtyInfo_), end(plugin.dirtyInfo_)); + locations_.insert(begin(plugin.locations_), end(plugin.locations_)); + + return; +} + +PluginMetadata PluginMetadata::DiffMetadata(const PluginMetadata& plugin) const { + using std::set_symmetric_difference; + + BOOST_LOG_TRIVIAL(trace) << "Calculating metadata difference for: " << name_; + PluginMetadata p(*this); + + if (Priority() == plugin.Priority() && IsPriorityGlobal() == plugin.IsPriorityGlobal()) { + p.Priority(0); + p.SetPriorityGlobal(false); + p.SetPriorityExplicit(false); + } + + //Compare this plugin against the given plugin. + set filesDiff; + set_symmetric_difference(begin(loadAfter_), + end(loadAfter_), + begin(plugin.loadAfter_), + end(plugin.loadAfter_), + inserter(filesDiff, begin(filesDiff))); + p.LoadAfter(filesDiff); + + filesDiff.clear(); + set_symmetric_difference(begin(requirements_), + end(requirements_), + begin(plugin.requirements_), + end(plugin.requirements_), + inserter(filesDiff, begin(filesDiff))); + p.Reqs(filesDiff); + + filesDiff.clear(); + set_symmetric_difference(begin(incompatibilities_), + end(incompatibilities_), + begin(plugin.incompatibilities_), + end(plugin.incompatibilities_), + inserter(filesDiff, begin(filesDiff))); + p.Incs(filesDiff); + + list msgs1 = plugin.Messages(); + list msgs2 = messages_; + msgs1.sort(); + msgs2.sort(); + list mDiff; + set_symmetric_difference(begin(msgs2), + end(msgs2), + begin(msgs1), + end(msgs1), + inserter(mDiff, begin(mDiff))); + p.Messages(mDiff); + + set tagDiff; + set_symmetric_difference(begin(tags_), + end(tags_), + begin(plugin.tags_), + end(plugin.tags_), + inserter(tagDiff, begin(tagDiff))); + p.Tags(tagDiff); + + set dirtDiff; + set_symmetric_difference(begin(dirtyInfo_), + end(dirtyInfo_), + begin(plugin.dirtyInfo_), + end(plugin.dirtyInfo_), + inserter(dirtDiff, begin(dirtDiff))); + p.DirtyInfo(dirtDiff); + + set locationsDiff; + set_symmetric_difference(begin(locations_), + end(locations_), + begin(plugin.locations_), + end(plugin.locations_), + inserter(locationsDiff, begin(locationsDiff))); + p.Locations(locationsDiff); + + return p; +} + +PluginMetadata PluginMetadata::NewMetadata(const PluginMetadata& plugin) const { + using std::set_difference; + + BOOST_LOG_TRIVIAL(trace) << "Comparing new metadata for: " << name_; + PluginMetadata p(*this); + + //Compare this plugin against the given plugin. + set filesDiff; + set_difference(begin(loadAfter_), + end(loadAfter_), + begin(plugin.loadAfter_), + end(plugin.loadAfter_), + inserter(filesDiff, begin(filesDiff))); + p.LoadAfter(filesDiff); + + filesDiff.clear(); + set_difference(begin(requirements_), + end(requirements_), + begin(plugin.requirements_), + end(plugin.requirements_), + inserter(filesDiff, begin(filesDiff))); + p.Reqs(filesDiff); + + filesDiff.clear(); + set_difference(begin(incompatibilities_), + end(incompatibilities_), + begin(plugin.incompatibilities_), + end(plugin.incompatibilities_), + inserter(filesDiff, begin(filesDiff))); + p.Incs(filesDiff); + + list msgs1 = plugin.Messages(); + list msgs2 = messages_; + msgs1.sort(); + msgs2.sort(); + list mDiff; + set_difference(begin(msgs2), + end(msgs2), + begin(msgs1), + end(msgs1), + inserter(mDiff, begin(mDiff))); + p.Messages(mDiff); + + set tagDiff; + set_difference(begin(tags_), + end(tags_), + begin(plugin.tags_), + end(plugin.tags_), + inserter(tagDiff, begin(tagDiff))); + p.Tags(tagDiff); + + set dirtDiff; + set_difference(begin(dirtyInfo_), + end(dirtyInfo_), + begin(plugin.dirtyInfo_), + end(plugin.dirtyInfo_), + inserter(dirtDiff, begin(dirtDiff))); + p.DirtyInfo(dirtDiff); + + set locationsDiff; + set_difference(begin(locations_), + end(locations_), + begin(plugin.locations_), + end(plugin.locations_), + inserter(locationsDiff, begin(locationsDiff))); + p.Locations(locationsDiff); + + return p; +} + +std::string PluginMetadata::Name() const { + return name_; +} + +bool PluginMetadata::Enabled() const { + return enabled_; +} + +int PluginMetadata::Priority() const { + return priority_; +} + +bool PluginMetadata::IsPriorityExplicit() const { + return priority_ != 0 || isPriorityExplicit_; +} + +bool PluginMetadata::IsPriorityGlobal() const { + return isPriorityGlobal_; +} + +std::set PluginMetadata::LoadAfter() const { + return loadAfter_; +} + +std::set PluginMetadata::Reqs() const { + return requirements_; +} + +std::set PluginMetadata::Incs() const { + return incompatibilities_; +} + +std::list PluginMetadata::Messages() const { + return messages_; +} + +std::set PluginMetadata::Tags() const { + return tags_; +} + +std::set PluginMetadata::DirtyInfo() const { + return dirtyInfo_; +} + +std::set PluginMetadata::Locations() const { + return locations_; +} + +void PluginMetadata::Enabled(const bool e) { + enabled_ = e; +} + +void PluginMetadata::Priority(const int p) { + if (abs(p) >= yamlGlobalPriorityDivisor) + throw Error(Error::Code::invalid_args, "Cannot set priority that has an absolute value greater than or equal to " + std::to_string(yamlGlobalPriorityDivisor)); + + priority_ = p; +} + +void PluginMetadata::SetPriorityExplicit(bool state) { + isPriorityExplicit_ = state; +} + +void PluginMetadata::SetPriorityGlobal(bool state) { + isPriorityGlobal_ = state; +} + +void PluginMetadata::LoadAfter(const std::set& l) { + loadAfter_ = l; +} + +void PluginMetadata::Reqs(const std::set& r) { + requirements_ = r; +} + +void PluginMetadata::Incs(const std::set& i) { + incompatibilities_ = i; +} + +void PluginMetadata::Messages(const std::list& m) { + messages_ = m; +} + +void PluginMetadata::Tags(const std::set& t) { + tags_ = t; +} + +void PluginMetadata::DirtyInfo(const std::set& dirtyInfo) { + dirtyInfo_ = dirtyInfo; +} + +void PluginMetadata::Locations(const std::set& locations) { + locations_ = locations; +} + +PluginMetadata& PluginMetadata::EvalAllConditions(Game& game, const Language::Code language) { + for (auto it = loadAfter_.begin(); it != loadAfter_.end();) { + if (!it->EvalCondition(game)) + loadAfter_.erase(it++); + else + ++it; + } + + for (auto it = requirements_.begin(); it != requirements_.end();) { + if (!it->EvalCondition(game)) + requirements_.erase(it++); + else + ++it; + } + + for (auto it = incompatibilities_.begin(); it != incompatibilities_.end();) { + if (!it->EvalCondition(game)) + incompatibilities_.erase(it++); + else + ++it; + } + + for (auto it = messages_.begin(); it != messages_.end();) { + if (!it->EvalCondition(game, language)) + it = messages_.erase(it); + else + ++it; + } + + for (auto it = tags_.begin(); it != tags_.end();) { + if (!it->EvalCondition(game)) + tags_.erase(it++); + else + ++it; + } + + if (IsRegexPlugin()) // Remove any dirty metadata from a regex plugin. + dirtyInfo_.clear(); + else { + for (auto it = dirtyInfo_.begin(); it != dirtyInfo_.end();) { + if (!it->EvalCondition(game, name_)) + dirtyInfo_.erase(it++); + else + ++it; } + } + + return *this; +} + +bool PluginMetadata::HasNameOnly() const { + return !IsPriorityExplicit() && loadAfter_.empty() && requirements_.empty() && incompatibilities_.empty() && messages_.empty() && tags_.empty() && dirtyInfo_.empty() && locations_.empty(); +} + +bool PluginMetadata::IsRegexPlugin() const { + // Treat as regex if the plugin filename contains any of ":\*?|" as + // they are not valid Windows filename characters, but have meaning + // in regexes. + return strpbrk(name_.c_str(), ":\\*?|") != nullptr; +} + +bool PluginMetadata::operator == (const PluginMetadata& rhs) const { + if (IsRegexPlugin() == rhs.IsRegexPlugin()) + return boost::iequals(name_, rhs.Name()); + + if (IsRegexPlugin()) + return regex_match(rhs.Name(), regex(name_, regex::ECMAScript | regex::icase)); + else + return regex_match(name_, regex(rhs.Name(), regex::ECMAScript | regex::icase)); +} + +bool PluginMetadata::operator != (const PluginMetadata& rhs) const { + return !(*this == rhs); +} + +bool PluginMetadata::operator == (const std::string& rhs) const { + if (IsRegexPlugin()) + return regex_match(PluginMetadata(rhs).Name(), regex(name_, regex::ECMAScript | regex::icase)); + else + return boost::iequals(name_, PluginMetadata(rhs).Name()); +} + +bool PluginMetadata::operator != (const std::string& rhs) const { + return !(*this == rhs); +} + +int PluginMetadata::GetYamlPriorityValue() const { + int priorityValue = Priority(); + if (IsPriorityGlobal()) { + if (priorityValue < 0) + priorityValue -= yamlGlobalPriorityDivisor; + else + priorityValue += yamlGlobalPriorityDivisor; + } + return priorityValue; +} } namespace YAML { - Emitter& operator << (Emitter& out, const loot::PluginMetadata& rhs) { - if (!rhs.HasNameOnly()) { - out << BeginMap - << Key << "name" << Value << YAML::SingleQuoted << rhs.Name(); +Emitter& operator << (Emitter& out, const loot::PluginMetadata& rhs) { + if (!rhs.HasNameOnly()) { + out << BeginMap + << Key << "name" << Value << YAML::SingleQuoted << rhs.Name(); - if (rhs.IsPriorityExplicit()) { - out << Key << "priority" << Value << rhs.GetYamlPriorityValue(); - } - - if (!rhs.Enabled()) - out << Key << "enabled" << Value << rhs.Enabled(); - - if (!rhs.LoadAfter().empty()) - out << Key << "after" << Value << rhs.LoadAfter(); - - if (!rhs.Reqs().empty()) - out << Key << "req" << Value << rhs.Reqs(); - - if (!rhs.Incs().empty()) - out << Key << "inc" << Value << rhs.Incs(); - - if (!rhs.Messages().empty()) - out << Key << "msg" << Value << rhs.Messages(); - - if (!rhs.Tags().empty()) - out << Key << "tag" << Value << rhs.Tags(); - - if (!rhs.DirtyInfo().empty()) - out << Key << "dirty" << Value << rhs.DirtyInfo(); - - if (!rhs.Locations().empty()) - out << Key << "url" << Value << rhs.Locations(); - - out << EndMap; - } - - return out; + if (rhs.IsPriorityExplicit()) { + out << Key << "priority" << Value << rhs.GetYamlPriorityValue(); } + + if (!rhs.Enabled()) + out << Key << "enabled" << Value << rhs.Enabled(); + + if (!rhs.LoadAfter().empty()) + out << Key << "after" << Value << rhs.LoadAfter(); + + if (!rhs.Reqs().empty()) + out << Key << "req" << Value << rhs.Reqs(); + + if (!rhs.Incs().empty()) + out << Key << "inc" << Value << rhs.Incs(); + + if (!rhs.Messages().empty()) + out << Key << "msg" << Value << rhs.Messages(); + + if (!rhs.Tags().empty()) + out << Key << "tag" << Value << rhs.Tags(); + + if (!rhs.DirtyInfo().empty()) + out << Key << "dirty" << Value << rhs.DirtyInfo(); + + if (!rhs.Locations().empty()) + out << Key << "url" << Value << rhs.Locations(); + + out << EndMap; + } + + return out; +} } diff --git a/src/backend/metadata/plugin_metadata.h b/src/backend/metadata/plugin_metadata.h index 43a40ed5..f6f2546f 100644 --- a/src/backend/metadata/plugin_metadata.h +++ b/src/backend/metadata/plugin_metadata.h @@ -21,198 +21,196 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA_PLUGIN_METADATA__ -#define __LOOT_METADATA_PLUGIN_METADATA__ - -#include "file.h" -#include "location.h" -#include "message.h" -#include "plugin_dirty_info.h" -#include "tag.h" -#include "../helpers/yaml_set_helpers.h" +#ifndef LOOT_BACKEND_METADATA_PLUGIN_METADATA +#define LOOT_BACKEND_METADATA_PLUGIN_METADATA #include +#include +#include +#include #include #include -#include -#include -#include #include - #include +#include "backend/helpers/yaml_set_helpers.h" +#include "backend/metadata/file.h" +#include "backend/metadata/location.h" +#include "backend/metadata/message.h" +#include "backend/metadata/plugin_dirty_info.h" +#include "backend/metadata/tag.h" + namespace loot { - const int yamlGlobalPriorityDivisor = 1000000; +class Game; - class Game; +const int yamlGlobalPriorityDivisor = 1000000; - class PluginMetadata { - public: - PluginMetadata(); - PluginMetadata(const std::string& name); +class PluginMetadata { +public: + PluginMetadata(); + PluginMetadata(const std::string& name); - //Merges from the given plugin into this one, unless there is already equal metadata present. - //For 'enabled' and 'priority' metadata, use the given plugin's values, but if the 'priority' user value is zero, ignore it. - void MergeMetadata(const PluginMetadata& plugin); + //Merges from the given plugin into this one, unless there is already equal metadata present. + //For 'enabled' and 'priority' metadata, use the given plugin's values, but if the 'priority' user value is zero, ignore it. + void MergeMetadata(const PluginMetadata& plugin); - //Returns the difference in metadata between the two plugins. - //For 'enabled', use this plugin's value. - //For 'priority', use 0 if the two plugin priorities are equal, and make it not explicit. Otherwise use this plugin's value. - PluginMetadata DiffMetadata(const PluginMetadata& plugin) const; + //Returns the difference in metadata between the two plugins. + //For 'enabled', use this plugin's value. + //For 'priority', use 0 if the two plugin priorities are equal, and make it not explicit. Otherwise use this plugin's value. + PluginMetadata DiffMetadata(const PluginMetadata& plugin) const; - // Returns metadata in this plugin not in the given plugin. - //For 'enabled', use this plugin's value. - //For 'priority', use 0 if the two plugin priorities are equal, and make it not explicit. Otherwise use this plugin's value. - PluginMetadata NewMetadata(const PluginMetadata& plugin) const; + // Returns metadata in this plugin not in the given plugin. + //For 'enabled', use this plugin's value. + //For 'priority', use 0 if the two plugin priorities are equal, and make it not explicit. Otherwise use this plugin's value. + PluginMetadata NewMetadata(const PluginMetadata& plugin) const; - std::string Name() const; - bool Enabled() const; - int Priority() const; - bool IsPriorityExplicit() const; - bool IsPriorityGlobal() const; - std::set LoadAfter() const; - std::set Reqs() const; - std::set Incs() const; - std::list Messages() const; - std::set Tags() const; - std::set DirtyInfo() const; - std::set Locations() const; + std::string Name() const; + bool Enabled() const; + int Priority() const; + bool IsPriorityExplicit() const; + bool IsPriorityGlobal() const; + std::set LoadAfter() const; + std::set Reqs() const; + std::set Incs() const; + std::list Messages() const; + std::set Tags() const; + std::set DirtyInfo() const; + std::set Locations() const; - void Enabled(const bool enabled); - void Priority(const int priority); - void SetPriorityExplicit(bool state); - void SetPriorityGlobal(bool state); - void LoadAfter(const std::set& after); - void Reqs(const std::set& reqs); - void Incs(const std::set& incs); - void Messages(const std::list& messages); - void Tags(const std::set& tags); - void DirtyInfo(const std::set& info); - void Locations(const std::set& locations); + void Enabled(const bool enabled); + void Priority(const int priority); + void SetPriorityExplicit(bool state); + void SetPriorityGlobal(bool state); + void LoadAfter(const std::set& after); + void Reqs(const std::set& reqs); + void Incs(const std::set& incs); + void Messages(const std::list& messages); + void Tags(const std::set& tags); + void DirtyInfo(const std::set& info); + void Locations(const std::set& locations); - PluginMetadata& EvalAllConditions(Game& game, const Language::Code language); - bool HasNameOnly() const; - bool IsRegexPlugin() const; + PluginMetadata& EvalAllConditions(Game& game, const Language::Code language); + bool HasNameOnly() const; + bool IsRegexPlugin() const; - //Compare name strings. - bool operator == (const PluginMetadata& rhs) const; - bool operator != (const PluginMetadata& rhs) const; + //Compare name strings. + bool operator == (const PluginMetadata& rhs) const; + bool operator != (const PluginMetadata& rhs) const; - //Compare name string. - bool operator == (const std::string& rhs) const; - bool operator != (const std::string& rhs) const; + //Compare name string. + bool operator == (const std::string& rhs) const; + bool operator != (const std::string& rhs) const; - int GetYamlPriorityValue() const; - private: - std::string name; - bool enabled; //Default to true. - int priority; //Default to 0 : >0 is lower down in load order, <0 is higher up. - bool _isPriorityExplicit; //If false and priority is 0, then priority was not explicitly set as such. - bool isPriorityGlobal; - std::set loadAfter; - std::set requirements; - std::set incompatibilities; - std::set _dirtyInfo; - std::set _locations; - protected: - std::list messages; - std::set tags; - }; + int GetYamlPriorityValue() const; +protected: + std::list messages_; + std::set tags_; +private: + std::string name_; + bool enabled_; //Default to true. + int priority_; //Default to 0 : >0 is lower down in load order, <0 is higher up. + bool isPriorityExplicit_; //If false and priority is 0, then priority was not explicitly set as such. + bool isPriorityGlobal_; + std::set loadAfter_; + std::set requirements_; + std::set incompatibilities_; + std::set dirtyInfo_; + std::set locations_; +}; } namespace std { - template<> - struct hash < loot::PluginMetadata > { - size_t operator() (const loot::PluginMetadata& plugin) const { - return hash()(boost::locale::to_lower(plugin.Name())); - } - }; +template<> +struct hash { + size_t operator() (const loot::PluginMetadata& plugin) const { + return hash()(boost::locale::to_lower(plugin.Name())); + } +}; } namespace YAML { - template<> - struct convert < loot::PluginMetadata > { - static Node encode(const loot::PluginMetadata& rhs) { - Node node; - node["name"] = rhs.Name(); +template<> +struct convert { + static Node encode(const loot::PluginMetadata& rhs) { + Node node; + node["name"] = rhs.Name(); - if (!rhs.Enabled()) - node["enabled"] = rhs.Enabled(); + if (!rhs.Enabled()) + node["enabled"] = rhs.Enabled(); - if (rhs.IsPriorityExplicit()) - node["priority"] = rhs.GetYamlPriorityValue(); + if (rhs.IsPriorityExplicit()) + node["priority"] = rhs.GetYamlPriorityValue(); - if (!rhs.LoadAfter().empty()) - node["after"] = rhs.LoadAfter(); - if (!rhs.Reqs().empty()) - node["req"] = rhs.Reqs(); - if (!rhs.Incs().empty()) - node["inc"] = rhs.Incs(); - if (!rhs.Messages().empty()) - node["msg"] = rhs.Messages(); - if (!rhs.Tags().empty()) - node["tag"] = rhs.Tags(); - if (!rhs.DirtyInfo().empty()) - node["dirty"] = rhs.DirtyInfo(); - if (!rhs.Locations().empty()) - node["url"] = rhs.Locations(); + if (!rhs.LoadAfter().empty()) + node["after"] = rhs.LoadAfter(); + if (!rhs.Reqs().empty()) + node["req"] = rhs.Reqs(); + if (!rhs.Incs().empty()) + node["inc"] = rhs.Incs(); + if (!rhs.Messages().empty()) + node["msg"] = rhs.Messages(); + if (!rhs.Tags().empty()) + node["tag"] = rhs.Tags(); + if (!rhs.DirtyInfo().empty()) + node["dirty"] = rhs.DirtyInfo(); + if (!rhs.Locations().empty()) + node["url"] = rhs.Locations(); - return node; - } + return node; + } - static bool decode(const Node& node, loot::PluginMetadata& rhs) { - if (!node.IsMap()) - throw RepresentationException(node.Mark(), "bad conversion: 'plugin metadata' object must be a map"); - if (!node["name"]) - throw RepresentationException(node.Mark(), "bad conversion: 'name' key missing from 'plugin metadata' object"); + static bool decode(const Node& node, loot::PluginMetadata& rhs) { + if (!node.IsMap()) + throw RepresentationException(node.Mark(), "bad conversion: 'plugin metadata' object must be a map"); + if (!node["name"]) + throw RepresentationException(node.Mark(), "bad conversion: 'name' key missing from 'plugin metadata' object"); - rhs = loot::PluginMetadata(node["name"].as()); + rhs = loot::PluginMetadata(node["name"].as()); - // Test for valid regex. - if (rhs.IsRegexPlugin()) { - try { - std::regex(rhs.Name(), std::regex::ECMAScript | std::regex::icase); - } - catch (std::regex_error& e) { - throw RepresentationException(node.Mark(), std::string("bad conversion: invalid regex in 'name' key: ") + e.what()); - } - } + // Test for valid regex. + if (rhs.IsRegexPlugin()) { + try { + std::regex(rhs.Name(), std::regex::ECMAScript | std::regex::icase); + } catch (std::regex_error& e) { + throw RepresentationException(node.Mark(), std::string("bad conversion: invalid regex in 'name' key: ") + e.what()); + } + } - if (node["enabled"]) - rhs.Enabled(node["enabled"].as()); + if (node["enabled"]) + rhs.Enabled(node["enabled"].as()); - if (node["priority"]) { - int priority = node["priority"].as(); - rhs.Priority(priority % loot::yamlGlobalPriorityDivisor); - rhs.SetPriorityExplicit(true); - rhs.SetPriorityGlobal(abs(priority) >= loot::yamlGlobalPriorityDivisor); - } + if (node["priority"]) { + int priority = node["priority"].as(); + rhs.Priority(priority % loot::yamlGlobalPriorityDivisor); + rhs.SetPriorityExplicit(true); + rhs.SetPriorityGlobal(abs(priority) >= loot::yamlGlobalPriorityDivisor); + } - if (node["after"]) - rhs.LoadAfter(node["after"].as< std::set >()); - if (node["req"]) - rhs.Reqs(node["req"].as< std::set >()); - if (node["inc"]) - rhs.Incs(node["inc"].as< std::set >()); - if (node["msg"]) - rhs.Messages(node["msg"].as< std::list >()); - if (node["tag"]) - rhs.Tags(node["tag"].as< std::set >()); - if (node["dirty"]) { - if (rhs.IsRegexPlugin()) - throw RepresentationException(node.Mark(), "bad conversion: 'dirty' key must not be present in a regex 'plugin metadata' object"); - else - rhs.DirtyInfo(node["dirty"].as< std::set >()); - } - if (node["url"]) - rhs.Locations(node["url"].as< std::set >()); + if (node["after"]) + rhs.LoadAfter(node["after"].as< std::set >()); + if (node["req"]) + rhs.Reqs(node["req"].as< std::set >()); + if (node["inc"]) + rhs.Incs(node["inc"].as< std::set >()); + if (node["msg"]) + rhs.Messages(node["msg"].as< std::list >()); + if (node["tag"]) + rhs.Tags(node["tag"].as< std::set >()); + if (node["dirty"]) { + if (rhs.IsRegexPlugin()) + throw RepresentationException(node.Mark(), "bad conversion: 'dirty' key must not be present in a regex 'plugin metadata' object"); + else + rhs.DirtyInfo(node["dirty"].as< std::set >()); + } + if (node["url"]) + rhs.Locations(node["url"].as< std::set >()); - return true; - } - }; + return true; + } +}; - Emitter& operator << (Emitter& out, const loot::PluginMetadata& rhs); +Emitter& operator << (Emitter& out, const loot::PluginMetadata& rhs); } #endif diff --git a/src/backend/metadata/tag.cpp b/src/backend/metadata/tag.cpp index 853cbc39..d13f4f1b 100644 --- a/src/backend/metadata/tag.cpp +++ b/src/backend/metadata/tag.cpp @@ -22,56 +22,53 @@ . */ -#include "tag.h" +#include "backend/metadata/tag.h" #include -using namespace std; - namespace loot { - Tag::Tag() : addTag(true) {} +Tag::Tag() : addTag_(true) {} - Tag::Tag(const string& tag, const bool isAddition, const string& condition) : _name(tag), addTag(isAddition), ConditionalMetadata(condition) {} +Tag::Tag(const std::string& tag, const bool isAddition, const std::string& condition) : name_(tag), addTag_(isAddition), ConditionalMetadata(condition) {} - bool Tag::operator < (const Tag& rhs) const { - if (addTag != rhs.IsAddition()) - return (addTag && !rhs.IsAddition()); - else - return boost::ilexicographical_compare(Name(), rhs.Name()); - } +bool Tag::operator < (const Tag& rhs) const { + if (addTag_ != rhs.IsAddition()) + return (addTag_ && !rhs.IsAddition()); + else + return boost::ilexicographical_compare(Name(), rhs.Name()); +} - bool Tag::operator == (const Tag& rhs) const { - return (addTag == rhs.IsAddition() && boost::iequals(Name(), rhs.Name())); - } +bool Tag::operator == (const Tag& rhs) const { + return (addTag_ == rhs.IsAddition() && boost::iequals(Name(), rhs.Name())); +} - bool Tag::IsAddition() const { - return addTag; - } +bool Tag::IsAddition() const { + return addTag_; +} - std::string Tag::Name() const { - return _name; - } +std::string Tag::Name() const { + return name_; +} } namespace YAML { - Emitter& operator << (Emitter& out, const loot::Tag& rhs) { - if (!rhs.IsConditional()) { - if (rhs.IsAddition()) - out << rhs.Name(); - else - out << ('-' + rhs.Name()); - } - else { - out << BeginMap; - if (rhs.IsAddition()) - out << Key << "name" << Value << rhs.Name(); - else - out << Key << "name" << Value << ('-' + rhs.Name()); +Emitter& operator << (Emitter& out, const loot::Tag& rhs) { + if (!rhs.IsConditional()) { + if (rhs.IsAddition()) + out << rhs.Name(); + else + out << ('-' + rhs.Name()); + } else { + out << BeginMap; + if (rhs.IsAddition()) + out << Key << "name" << Value << rhs.Name(); + else + out << Key << "name" << Value << ('-' + rhs.Name()); - out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition() - << EndMap; - } + out << Key << "condition" << Value << YAML::SingleQuoted << rhs.Condition() + << EndMap; + } - return out; - } + return out; +} } diff --git a/src/backend/metadata/tag.h b/src/backend/metadata/tag.h index 676aa078..756975e9 100644 --- a/src/backend/metadata/tag.h +++ b/src/backend/metadata/tag.h @@ -21,80 +21,78 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_METADATA_TAG__ -#define __LOOT_METADATA_TAG__ - -#include "conditional_metadata.h" +#ifndef LOOT_BACKEND_METADATA_TAG +#define LOOT_BACKEND_METADATA_TAG #include #include +#include "backend/metadata/conditional_metadata.h" + namespace loot { - class Tag : public ConditionalMetadata { - public: - Tag(); - Tag(const std::string& tag, const bool isAddition = true, const std::string& condition = ""); +class Tag : public ConditionalMetadata { +public: + Tag(); + Tag(const std::string& tag, const bool isAddition = true, const std::string& condition = ""); - bool operator < (const Tag& rhs) const; - bool operator == (const Tag& rhs) const; + bool operator < (const Tag& rhs) const; + bool operator == (const Tag& rhs) const; - bool IsAddition() const; - std::string Name() const; - private: - std::string _name; - bool addTag; - }; + bool IsAddition() const; + std::string Name() const; +private: + std::string name_; + bool addTag_; +}; } namespace YAML { - template<> - struct convert < loot::Tag > { - static Node encode(const loot::Tag& rhs) { - Node node; - if (rhs.IsConditional()) - node["condition"] = rhs.Condition(); - if (rhs.IsAddition()) - node["name"] = rhs.Name(); - else - node["name"] = "-" + rhs.Name(); - return node; - } +template<> +struct convert { + static Node encode(const loot::Tag& rhs) { + Node node; + if (rhs.IsConditional()) + node["condition"] = rhs.Condition(); + if (rhs.IsAddition()) + node["name"] = rhs.Name(); + else + node["name"] = "-" + rhs.Name(); + return node; + } - static bool decode(const Node& node, loot::Tag& rhs) { - if (!node.IsMap() && !node.IsScalar()) - throw RepresentationException(node.Mark(), "bad conversion: 'tag' object must be a map or scalar"); + static bool decode(const Node& node, loot::Tag& rhs) { + if (!node.IsMap() && !node.IsScalar()) + throw RepresentationException(node.Mark(), "bad conversion: 'tag' object must be a map or scalar"); - std::string condition, tag; - if (node.IsMap()) { - if (!node["name"]) - throw RepresentationException(node.Mark(), "bad conversion: 'name' key missing from 'tag' map object"); + std::string condition, tag; + if (node.IsMap()) { + if (!node["name"]) + throw RepresentationException(node.Mark(), "bad conversion: 'name' key missing from 'tag' map object"); - tag = node["name"].as(); - if (node["condition"]) - condition = node["condition"].as(); - } - else - tag = node.as(); + tag = node["name"].as(); + if (node["condition"]) + condition = node["condition"].as(); + } else + tag = node.as(); - if (tag[0] == '-') - rhs = loot::Tag(tag.substr(1), false, condition); - else - rhs = loot::Tag(tag, true, condition); + if (tag[0] == '-') + rhs = loot::Tag(tag.substr(1), false, condition); + else + rhs = loot::Tag(tag, true, condition); - // Test condition syntax. - try { - rhs.ParseCondition(); - } - catch (std::exception& e) { - throw RepresentationException(node.Mark(), std::string("bad conversion: invalid condition syntax: ") + e.what()); - } + // Test condition syntax. + try { + rhs.ParseCondition(); + } catch (std::exception& e) { + throw RepresentationException(node.Mark(), std::string("bad conversion: invalid condition syntax: ") + e.what()); + } - return true; - } - }; + return true; + } +}; - Emitter& operator << (Emitter& out, const loot::Tag& rhs); +Emitter& operator << (Emitter& out, const loot::Tag& rhs); } #endif diff --git a/src/backend/metadata_list.cpp b/src/backend/metadata_list.cpp index 748d0c94..8773f466 100644 --- a/src/backend/metadata_list.cpp +++ b/src/backend/metadata_list.cpp @@ -22,143 +22,145 @@ . */ -#include "metadata_list.h" -#include "error.h" +#include "backend/metadata_list.h" #include #include #include -using namespace std; +#include "backend/error.h" +#include "backend/game/game.h" + +using std::list; namespace loot { - void MetadataList::Load(const boost::filesystem::path& filepath) { - clear(); +void MetadataList::Load(const boost::filesystem::path& filepath) { + Clear(); - BOOST_LOG_TRIVIAL(debug) << "Loading file: " << filepath; + BOOST_LOG_TRIVIAL(debug) << "Loading file: " << filepath; - boost::filesystem::ifstream in(filepath); - if (!in.good()) - throw Error(Error::Code::path_read_fail, "Cannot open " + filepath.string()); + boost::filesystem::ifstream in(filepath); + if (!in.good()) + throw Error(Error::Code::path_read_fail, "Cannot open " + filepath.string()); - YAML::Node metadataList = YAML::Load(in); - in.close(); + YAML::Node metadataList = YAML::Load(in); + in.close(); - if (metadataList["plugins"]) { - for (const auto& node : metadataList["plugins"]) { - PluginMetadata plugin(node.as()); - if (plugin.IsRegexPlugin()) - regexPlugins.push_back(plugin); - else { - if (!plugins.insert(plugin).second) - throw Error(Error::Code::path_read_fail, "More than one entry exists for \"" + plugin.Name() + "\""); - } - } - } - if (metadataList["globals"]) - messages = metadataList["globals"].as>(); - - if (metadataList["bash_tags"]) - bashTags_ = metadataList["bash_tags"].as>(); - - BOOST_LOG_TRIVIAL(debug) << "File loaded successfully."; + if (metadataList["plugins"]) { + for (const auto& node : metadataList["plugins"]) { + PluginMetadata plugin(node.as()); + if (plugin.IsRegexPlugin()) + regexPlugins_.push_back(plugin); + else { + if (!plugins_.insert(plugin).second) + throw Error(Error::Code::path_read_fail, "More than one entry exists for \"" + plugin.Name() + "\""); + } } + } + if (metadataList["globals"]) + messages_ = metadataList["globals"].as>(); - void MetadataList::Save(const boost::filesystem::path& filepath) { - BOOST_LOG_TRIVIAL(trace) << "Saving metadata list to: " << filepath; - YAML::Emitter yout; - yout.SetIndent(2); - yout << YAML::BeginMap - << YAML::Key << "bash_tags" << YAML::Value << bashTags_ - << YAML::Key << "plugins" << YAML::Value << Plugins() - << YAML::Key << "globals" << YAML::Value << messages - << YAML::EndMap; + if (metadataList["bash_tags"]) + bashTags_ = metadataList["bash_tags"].as>(); - boost::filesystem::ofstream uout(filepath); - uout << yout.c_str(); - uout.close(); - } - - void MetadataList::clear() { - bashTags_.clear(); - plugins.clear(); - regexPlugins.clear(); - messages.clear(); - } - - std::list MetadataList::Plugins() const { - list pluginList(plugins.begin(), plugins.end()); - - pluginList.insert(pluginList.end(), regexPlugins.begin(), regexPlugins.end()); - - return pluginList; - } - - std::list MetadataList::Messages() const { - return messages; - } - - std::set MetadataList::BashTags() const { - return bashTags_; - } - - // Merges multiple matching regex entries if any are found. - PluginMetadata MetadataList::FindPlugin(const PluginMetadata& plugin) const { - PluginMetadata match(plugin.Name()); - - auto it = plugins.find(plugin); - - if (it != plugins.end()) - match = *it; - - // Now we want to also match possibly multiple regex entries. - auto regIt = find(regexPlugins.begin(), regexPlugins.end(), plugin); - while (regIt != regexPlugins.end()) { - match.MergeMetadata(*regIt); - - regIt = find(++regIt, regexPlugins.end(), plugin); - } - - return match; - } - - void MetadataList::AddPlugin(const PluginMetadata& plugin) { - if (plugin.IsRegexPlugin()) - regexPlugins.push_back(plugin); - else { - if (!plugins.insert(plugin).second) - throw Error(Error::Code::invalid_args, "Cannot add \"" + plugin.Name() + "\" to the metadata list as another entry already exists."); - } - } - - // Doesn't erase matching regex entries, because they might also - // be required for other plugins. - void MetadataList::ErasePlugin(const PluginMetadata& plugin) { - auto it = plugins.find(plugin); - - if (it != plugins.end()) { - plugins.erase(it); - return; - } - } - - void MetadataList::AppendMessage(const Message& message) { - messages.push_back(message); - } - - void MetadataList::EvalAllConditions(Game& game, const Language::Code language) { - unordered_set replacementSet; - for (auto &plugin : plugins) { - PluginMetadata p(plugin); - p.EvalAllConditions(game, language); - replacementSet.insert(p); - } - plugins = replacementSet; - for (auto &plugin : regexPlugins) { - plugin.EvalAllConditions(game, language); - } - for (auto &message : messages) { - message.EvalCondition(game, language); - } - } + BOOST_LOG_TRIVIAL(debug) << "File loaded successfully."; +} + +void MetadataList::Save(const boost::filesystem::path& filepath) { + BOOST_LOG_TRIVIAL(trace) << "Saving metadata list to: " << filepath; + YAML::Emitter yout; + yout.SetIndent(2); + yout << YAML::BeginMap + << YAML::Key << "bash_tags" << YAML::Value << bashTags_ + << YAML::Key << "plugins" << YAML::Value << Plugins() + << YAML::Key << "globals" << YAML::Value << messages_ + << YAML::EndMap; + + boost::filesystem::ofstream uout(filepath); + uout << yout.c_str(); + uout.close(); +} + +void MetadataList::Clear() { + bashTags_.clear(); + plugins_.clear(); + regexPlugins_.clear(); + messages_.clear(); +} + +std::list MetadataList::Plugins() const { + list pluginList(plugins_.begin(), plugins_.end()); + + pluginList.insert(pluginList.end(), regexPlugins_.begin(), regexPlugins_.end()); + + return pluginList; +} + +std::list MetadataList::Messages() const { + return messages_; +} + +std::set MetadataList::BashTags() const { + return bashTags_; +} + +// Merges multiple matching regex entries if any are found. +PluginMetadata MetadataList::FindPlugin(const PluginMetadata& plugin) const { + PluginMetadata match(plugin.Name()); + + auto it = plugins_.find(plugin); + + if (it != plugins_.end()) + match = *it; + +// Now we want to also match possibly multiple regex entries. + auto regIt = find(regexPlugins_.begin(), regexPlugins_.end(), plugin); + while (regIt != regexPlugins_.end()) { + match.MergeMetadata(*regIt); + + regIt = find(++regIt, regexPlugins_.end(), plugin); + } + + return match; +} + +void MetadataList::AddPlugin(const PluginMetadata& plugin) { + if (plugin.IsRegexPlugin()) + regexPlugins_.push_back(plugin); + else { + if (!plugins_.insert(plugin).second) + throw Error(Error::Code::invalid_args, "Cannot add \"" + plugin.Name() + "\" to the metadata list as another entry already exists."); + } +} + +// Doesn't erase matching regex entries, because they might also +// be required for other plugins. +void MetadataList::ErasePlugin(const PluginMetadata& plugin) { + auto it = plugins_.find(plugin); + + if (it != plugins_.end()) { + plugins_.erase(it); + return; + } +} + +void MetadataList::AppendMessage(const Message& message) { + messages_.push_back(message); +} + +void MetadataList::EvalAllConditions(Game& game, const Language::Code language) { + std::unordered_set replacementSet; + for (auto &plugin : plugins_) { + PluginMetadata p(plugin); + p.EvalAllConditions(game, language); + replacementSet.insert(p); + } + plugins_ = replacementSet; + for (auto &plugin : regexPlugins_) { + plugin.EvalAllConditions(game, language); + } + for (auto &message : messages_) { + message.EvalCondition(game, language); + } +} } diff --git a/src/backend/metadata_list.h b/src/backend/metadata_list.h index dbe27b35..76c83740 100644 --- a/src/backend/metadata_list.h +++ b/src/backend/metadata_list.h @@ -22,59 +22,49 @@ . */ -#ifndef __LOOT_METADATA_LIST__ -#define __LOOT_METADATA_LIST__ - -#include "metadata/plugin_metadata.h" +#ifndef LOOT_BACKEND_METADATA_LIST +#define LOOT_BACKEND_METADATA_LIST #include -#include #include +#include #include +#include "backend/metadata/plugin_metadata.h" + namespace loot { - class Game; +class Game; - /* Each Game object should store the config details specific to that game. - It should also store the plugin and masterlist data for that game. - Plugin data should be stored as an unordered hashset, the elements of which are - referenced by ordered lists and other structures. - Masterlist / userlist data should be stored as structures which hold plugin and - global message lists. - Each game should have functions to load this plugin and masterlist / userlist - data. Plugin data should be loaded as header-only and as full data. - */ +class MetadataList { +public: + void Load(const boost::filesystem::path& filepath); + void Save(const boost::filesystem::path& filepath); + void Clear(); - class MetadataList { - public: - void Load(const boost::filesystem::path& filepath); - void Save(const boost::filesystem::path& filepath); - void clear(); + std::list Plugins() const; + std::list Messages() const; + std::set BashTags() const; - std::list Plugins() const; - std::list Messages() const; - std::set BashTags() const; + // Merges multiple matching regex entries if any are found. + PluginMetadata FindPlugin(const PluginMetadata& plugin) const; + void AddPlugin(const PluginMetadata& plugin); - // Merges multiple matching regex entries if any are found. - PluginMetadata FindPlugin(const PluginMetadata& plugin) const; - void AddPlugin(const PluginMetadata& plugin); + // Doesn't erase matching regex entries, because they might also + // be required for other plugins. + void ErasePlugin(const PluginMetadata& plugin); - // Doesn't erase matching regex entries, because they might also - // be required for other plugins. - void ErasePlugin(const PluginMetadata& plugin); + void AppendMessage(const Message& message); - void AppendMessage(const Message& message); + // Eval plugin conditions. + void EvalAllConditions(Game& game, const Language::Code language); - // Eval plugin conditions. - void EvalAllConditions(Game& game, const Language::Code language); - - protected: - std::set bashTags_; - std::unordered_set plugins; - std::list regexPlugins; - std::list messages; - }; +protected: + std::set bashTags_; + std::unordered_set plugins_; + std::list regexPlugins_; + std::list messages_; +}; } #endif diff --git a/src/backend/plugin/plugin.cpp b/src/backend/plugin/plugin.cpp index a07ea38b..f3cc4512 100644 --- a/src/backend/plugin/plugin.cpp +++ b/src/backend/plugin/plugin.cpp @@ -22,225 +22,225 @@ . */ -#include "plugin.h" -#include "../game/game.h" -#include "../helpers/helpers.h" +#include "backend/plugin/plugin.h" + +#include #include #include #include #include #include -#include -using namespace std; +#include "backend/game/game.h" +#include "backend/helpers/helpers.h" + using libespm::FormId; +using std::set; +using std::string; namespace loot { - Plugin::Plugin(const Game& game, const std::string& name, const bool headerOnly) : - PluginMetadata(name), - libespm::Plugin(game.LibespmId()), - _isEmpty(true), - _isActive(false), - _loadsArchive(false), - crc(0), - numOverrideRecords(0) { - try { - boost::filesystem::path filepath = game.DataPath() / Name(); +Plugin::Plugin(const Game& game, const std::string& name, const bool headerOnly) : + PluginMetadata(name), + libespm::Plugin(game.LibespmId()), + isEmpty_(true), + isActive_(false), + loadsArchive_(false), + crc_(0), + numOverrideRecords_(0) { + try { + boost::filesystem::path filepath = game.DataPath() / Name(); - // In case the plugin is ghosted. - if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) - filepath += ".ghost"; + // In case the plugin is ghosted. + if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) + filepath += ".ghost"; - load(filepath, headerOnly); + load(filepath, headerOnly); - _isEmpty = getRecordAndGroupCount() == 0; + isEmpty_ = getRecordAndGroupCount() == 0; - if (!headerOnly) { - BOOST_LOG_TRIVIAL(trace) << Name() << ": Caching CRC value."; - crc = GetCrc32(filepath); - } + if (!headerOnly) { + BOOST_LOG_TRIVIAL(trace) << Name() << ": Caching CRC value."; + crc_ = GetCrc32(filepath); + } - BOOST_LOG_TRIVIAL(trace) << Name() << ": Counting override FormIDs."; - for (const auto& formID : getFormIds()) { - if (!boost::iequals(formID.getPluginName(), Name())) - ++numOverrideRecords; - } + BOOST_LOG_TRIVIAL(trace) << Name() << ": Counting override FormIDs."; + for (const auto& formID : getFormIds()) { + if (!boost::iequals(formID.getPluginName(), Name())) + ++numOverrideRecords_; + } - //Also read Bash Tags applied and version string in description. - string text = getDescription(); - BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Attempting to extract Bash Tags from the description."; - size_t pos1 = text.find("{{BASH:"); - if (pos1 != string::npos && pos1 + 7 != text.length()) { - pos1 += 7; + //Also read Bash Tags applied and version string in description. + string text = getDescription(); + BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Attempting to extract Bash Tags from the description."; + size_t pos1 = text.find("{{BASH:"); + if (pos1 != string::npos && pos1 + 7 != text.length()) { + pos1 += 7; - size_t pos2 = text.find("}}", pos1); - if (pos2 != string::npos && pos1 != pos2) { - text = text.substr(pos1, pos2 - pos1); + size_t pos2 = text.find("}}", pos1); + if (pos2 != string::npos && pos1 != pos2) { + text = text.substr(pos1, pos2 - pos1); - vector bashTags; - boost::split(bashTags, text, boost::is_any_of(",")); + std::vector bashTags; + boost::split(bashTags, text, boost::is_any_of(",")); - for (auto &tag : bashTags) { - boost::trim(tag); - BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Extracted Bash Tag: " << tag; - tags.insert(Tag(tag)); - } - } - } - // Get whether the plugin is active or not. - _isActive = game.LoadOrderHandler::IsPluginActive(Name()); - - // Get whether the plugin loads an archive (BSA/BA2) or not. - const string archiveExtension = game.GetArchiveFileExtension(); - - if (game.Type() == GameType::tes5) { - // Skyrim plugins only load BSAs that exactly match their basename. - _loadsArchive = boost::filesystem::exists(game.DataPath() / (Name().substr(0, Name().length() - 4) + archiveExtension)); - } - else if (game.Type() != GameType::tes4 || boost::iends_with(Name(), ".esp")) { - //Oblivion .esp files and FO3, FNV, FO4 plugins can load archives which begin with the plugin basename. - string basename = Name().substr(0, Name().length() - 4); - for (boost::filesystem::directory_iterator it(game.DataPath()); it != boost::filesystem::directory_iterator(); ++it) { - if (boost::iequals(it->path().extension().string(), archiveExtension) && boost::istarts_with(it->path().filename().string(), basename)) { - _loadsArchive = true; - break; - } - } - } + for (auto &tag : bashTags) { + boost::trim(tag); + BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Extracted Bash Tag: " << tag; + tags_.insert(Tag(tag)); } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Cannot read plugin file \"" << name << "\". Details: " << e.what(); - messages.push_back(loot::Message(loot::Message::Type::error, (boost::format(boost::locale::translate("Cannot read \"%1%\". Details: %2%")) % name % e.what()).str())); + } + } + // Get whether the plugin is active or not. + isActive_ = game.LoadOrderHandler::IsPluginActive(Name()); + + // Get whether the plugin loads an archive (BSA/BA2) or not. + const string archiveExtension = game.GetArchiveFileExtension(); + + if (game.Type() == GameType::tes5) { + // Skyrim plugins only load BSAs that exactly match their basename. + loadsArchive_ = boost::filesystem::exists(game.DataPath() / (Name().substr(0, Name().length() - 4) + archiveExtension)); + } else if (game.Type() != GameType::tes4 || boost::iends_with(Name(), ".esp")) { + //Oblivion .esp files and FO3, FNV, FO4 plugins can load archives which begin with the plugin basename. + string basename = Name().substr(0, Name().length() - 4); + for (boost::filesystem::directory_iterator it(game.DataPath()); it != boost::filesystem::directory_iterator(); ++it) { + if (boost::iequals(it->path().extension().string(), archiveExtension) && boost::istarts_with(it->path().filename().string(), basename)) { + loadsArchive_ = true; + break; } - - BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Plugin loading complete."; + } } + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Cannot read plugin file \"" << name << "\". Details: " << e.what(); + messages_.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("Cannot read \"%1%\". Details: %2%")) % name % e.what()).str())); + } - bool Plugin::DoFormIDsOverlap(const Plugin& plugin) const { - //Basically std::set_intersection except with an early exit instead of an append to results. - set formIds(getFormIds()); - set otherFormIds(plugin.getFormIds()); - auto i = begin(formIds); - auto j = begin(otherFormIds); - auto iend = end(formIds); - auto jend = end(otherFormIds); - - while (i != iend && j != jend) { - if (*i < *j) - ++i; - else if (*j < *i) - ++j; - else - return true; + BOOST_LOG_TRIVIAL(trace) << Name() << ": " << "Plugin loading complete."; +} + +bool Plugin::DoFormIDsOverlap(const Plugin& plugin) const { + //Basically std::set_intersection except with an early exit instead of an append to results. + set formIds(getFormIds()); + set otherFormIds(plugin.getFormIds()); + auto i = begin(formIds); + auto j = begin(otherFormIds); + auto iend = end(formIds); + auto jend = end(otherFormIds); + + while (i != iend && j != jend) { + if (*i < *j) + ++i; + else if (*j < *i) + ++j; + else + return true; + } + + return false; +} + +size_t Plugin::NumOverrideFormIDs() const { + return numOverrideRecords_; +} + +std::set Plugin::OverlapFormIDs(const Plugin& plugin) const { + set formIds(getFormIds()); + set otherFormIds(plugin.getFormIds()); + set overlap; + + set_intersection(begin(formIds), + end(formIds), + begin(otherFormIds), + end(otherFormIds), + inserter(overlap, end(overlap))); + + return overlap; +} + +bool Plugin::IsEmpty() const { + return isEmpty_; +} + +bool Plugin::IsValid(const std::string& filename, const Game& game) { + BOOST_LOG_TRIVIAL(trace) << "Checking to see if \"" << filename << "\" is a valid plugin."; + + //If the filename passed ends in '.ghost', that should be trimmed. + std::string name; + if (boost::iends_with(filename, ".ghost")) + name = filename.substr(0, filename.length() - 6); + else + name = filename; + +// Check that the file has a valid extension. + if (!boost::iends_with(name, ".esm") && !boost::iends_with(name, ".esp")) + return false; + +// Add the ".ghost" file extension if the plugin is ghosted. + boost::filesystem::path filepath = game.DataPath() / name; + if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) + filepath += ".ghost"; + + if (libespm::Plugin::isValid(filepath, game.LibespmId(), true)) + return true; + + BOOST_LOG_TRIVIAL(warning) << "The .es(p|m) file \"" << filename << "\" is not a valid plugin."; + return false; +} + +bool Plugin::operator < (const Plugin & rhs) const { + return boost::ilexicographical_compare(Name(), rhs.Name());; +} + +bool Plugin::IsActive() const { + return isActive_; +} + +uint32_t Plugin::Crc() const { + return crc_; +} + +bool Plugin::CheckInstallValidity(const Game& game) { + BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to " << Name() << "'s data."; + if (IsActive()) { + auto pluginExists = [](const Game& game, const std::string& file) { + return boost::filesystem::exists(game.DataPath() / file) + || ((boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) && boost::filesystem::exists(game.DataPath() / (file + ".ghost"))); + }; + if (tags_.find(Tag("Filter")) == tags_.end()) { + for (const auto &master : getMasters()) { + if (!pluginExists(game, master)) { + BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << master << "\", but it is missing."; + messages_.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be installed, but it is missing.")) % master).str())); + } else if (!game.IsPluginActive(master)) { + BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << master << "\", but it is inactive."; + messages_.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be active, but it is inactive.")) % master).str())); } - - return false; - } - - size_t Plugin::NumOverrideFormIDs() const { - return numOverrideRecords; - } - - std::set Plugin::OverlapFormIDs(const Plugin& plugin) const { - set formIds(getFormIds()); - set otherFormIds(plugin.getFormIds()); - set overlap; - - set_intersection(begin(formIds), - end(formIds), - begin(otherFormIds), - end(otherFormIds), - inserter(overlap, end(overlap))); - - return overlap; - } - - bool Plugin::IsEmpty() const { - return _isEmpty; - } - - bool Plugin::IsValid(const std::string& filename, const Game& game) { - BOOST_LOG_TRIVIAL(trace) << "Checking to see if \"" << filename << "\" is a valid plugin."; - - //If the filename passed ends in '.ghost', that should be trimmed. - std::string name; - if (boost::iends_with(filename, ".ghost")) - name = filename.substr(0, filename.length() - 6); - else - name = filename; - - // Check that the file has a valid extension. - if (!boost::iends_with(name, ".esm") && !boost::iends_with(name, ".esp")) - return false; - - // Add the ".ghost" file extension if the plugin is ghosted. - boost::filesystem::path filepath = game.DataPath() / name; - if (!boost::filesystem::exists(filepath) && boost::filesystem::exists(filepath.string() + ".ghost")) - filepath += ".ghost"; - - if (libespm::Plugin::isValid(filepath, game.LibespmId(), true)) - return true; - - BOOST_LOG_TRIVIAL(warning) << "The .es(p|m) file \"" << filename << "\" is not a valid plugin."; - return false; - } - - bool Plugin::operator < (const Plugin & rhs) const { - return boost::ilexicographical_compare(Name(), rhs.Name());; + } } - bool Plugin::IsActive() const { - return _isActive; + for (const auto &req : Reqs()) { + if (!pluginExists(game, req.Name())) { + BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << req.Name() << "\", but it is missing."; + messages_.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be installed, but it is missing.")) % req.Name()).str())); + } } - - uint32_t Plugin::Crc() const { - return crc; + for (const auto &inc : Incs()) { + if (pluginExists(game, inc.Name()) && game.IsPluginActive(inc.Name())) { + BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" is incompatible with \"" << inc.Name() << "\", but both are present."; + messages_.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("This plugin is incompatible with \"%1%\", but both are present.")) % inc.Name()).str())); + } } - - bool Plugin::CheckInstallValidity(const Game& game) { - BOOST_LOG_TRIVIAL(trace) << "Checking that the current install is valid according to " << Name() << "'s data."; - if (IsActive()) { - auto pluginExists = [](const Game& game, const std::string& file) { - return boost::filesystem::exists(game.DataPath() / file) - || ((boost::iends_with(file, ".esp") || boost::iends_with(file, ".esm")) && boost::filesystem::exists(game.DataPath() / (file + ".ghost"))); - }; - if (tags.find(Tag("Filter")) == tags.end()) { - for (const auto &master : getMasters()) { - if (!pluginExists(game, master)) { - BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << master << "\", but it is missing."; - messages.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be installed, but it is missing.")) % master).str())); - } - else if (!game.IsPluginActive(master)) { - BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << master << "\", but it is inactive."; - messages.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be active, but it is inactive.")) % master).str())); - } - } - } - - for (const auto &req : Reqs()) { - if (!pluginExists(game, req.Name())) { - BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" requires \"" << req.Name() << "\", but it is missing."; - messages.push_back(loot::Message(Message::Type::error, (boost::format(boost::locale::translate("This plugin requires \"%1%\" to be installed, but it is missing.")) % req.Name()).str())); - } - } - for (const auto &inc : Incs()) { - if (pluginExists(game, inc.Name()) && game.IsPluginActive(inc.Name())) { - BOOST_LOG_TRIVIAL(error) << "\"" << Name() << "\" is incompatible with \"" << inc.Name() << "\", but both are present."; - messages.push_back(loot::Message(Message::Type::error, (boost::format(boost::locale::translate("This plugin is incompatible with \"%1%\", but both are present.")) % inc.Name()).str())); - } - } - } + } - // Also generate dirty messages. - for (const auto &element : DirtyInfo()) { - messages.push_back(element.AsMessage()); - } + // Also generate dirty messages. + for (const auto &element : DirtyInfo()) { + messages_.push_back(element.AsMessage()); + } - return !DirtyInfo().empty(); - } + return !DirtyInfo().empty(); +} - bool Plugin::LoadsArchive() const { - return _loadsArchive; - } +bool Plugin::LoadsArchive() const { + return loadsArchive_; +} } diff --git a/src/backend/plugin/plugin.h b/src/backend/plugin/plugin.h index a5c4c992..40f42546 100644 --- a/src/backend/plugin/plugin.h +++ b/src/backend/plugin/plugin.h @@ -21,68 +21,67 @@ along with LOOT. If not, see . */ -#ifndef __LOOT_PLUGIN__ -#define __LOOT_PLUGIN__ - -#include "../metadata/plugin_metadata.h" +#ifndef LOOT_BACKEND_PLUGIN_PLUGIN +#define LOOT_BACKEND_PLUGIN_PLUGIN #include -#include -#include #include #include +#include +#include #include - #include +#include "backend/metadata/plugin_metadata.h" + namespace loot { - class Game; +class Game; - class Plugin : public PluginMetadata, private libespm::Plugin { - public: - Plugin(const Game& game, const std::string& name, const bool headerOnly); +class Plugin : public PluginMetadata, private libespm::Plugin { +public: + Plugin(const Game& game, const std::string& name, const bool headerOnly); - using libespm::Plugin::getDescription; - using libespm::Plugin::getFormIds; - using libespm::Plugin::getMasters; - using libespm::Plugin::isMasterFile; + using libespm::Plugin::getDescription; + using libespm::Plugin::getFormIds; + using libespm::Plugin::getMasters; + using libespm::Plugin::isMasterFile; - bool IsEmpty() const; - uint32_t Crc() const; - size_t NumOverrideFormIDs() const; + bool IsEmpty() const; + uint32_t Crc() const; + size_t NumOverrideFormIDs() const; - bool LoadsArchive() const; - bool IsActive() const; + bool LoadsArchive() const; + bool IsActive() const; - //Load ordering functions. - bool DoFormIDsOverlap(const Plugin& plugin) const; - std::set OverlapFormIDs(const Plugin& plugin) const; + //Load ordering functions. + bool DoFormIDsOverlap(const Plugin& plugin) const; + std::set OverlapFormIDs(const Plugin& plugin) const; - //Validity checks. - bool CheckInstallValidity(const Game& game); //Checks that reqs and masters are all present, and that no incs are present. Returns true if the plugin is dirty. - static bool IsValid(const std::string& filename, const Game& game); + //Validity checks. + bool CheckInstallValidity(const Game& game); //Checks that reqs and masters are all present, and that no incs are present. Returns true if the plugin is dirty. + static bool IsValid(const std::string& filename, const Game& game); - bool operator < (const Plugin& rhs) const; - private: - bool _isEmpty; // Does the plugin contain any records other than the TES4 header? - bool _isActive; - bool _loadsArchive; - std::string version; //Obtained from description field. - uint32_t crc; + bool operator < (const Plugin& rhs) const; +private: + bool isEmpty_; // Does the plugin contain any records other than the TES4 header? + bool isActive_; + bool loadsArchive_; + std::string version_; //Obtained from description field. + uint32_t crc_; - //Useful caches. - size_t numOverrideRecords; - }; + //Useful caches. + size_t numOverrideRecords_; +}; } namespace std { - template<> - struct hash < loot::Plugin > { - size_t operator() (const loot::Plugin& plugin) const { - return hash()(boost::locale::to_lower(plugin.Name())); - } - }; +template<> +struct hash { + size_t operator() (const loot::Plugin& plugin) const { + return hash()(boost::locale::to_lower(plugin.Name())); + } +}; } #endif diff --git a/src/backend/plugin/plugin_sorter.cpp b/src/backend/plugin/plugin_sorter.cpp index 9031d428..999f0a79 100644 --- a/src/backend/plugin/plugin_sorter.cpp +++ b/src/backend/plugin/plugin_sorter.cpp @@ -23,477 +23,472 @@ */ #include "plugin_sorter.h" -#include "backend/game/game.h" -#include "backend/error.h" -#include "backend/helpers/helpers.h" #include #include -#include +#include #include #include #include #include -#include +#include -using namespace std; +#include "backend/error.h" +#include "backend/game/game.h" +#include "backend/helpers/helpers.h" + +using std::list; +using std::string; namespace loot { - typedef boost::graph_traits::vertex_iterator vertex_it; - typedef boost::graph_traits::edge_descriptor edge_t; - typedef boost::graph_traits::edge_iterator edge_it; +typedef boost::graph_traits::vertex_iterator vertex_it; +typedef boost::graph_traits::edge_descriptor edge_t; +typedef boost::graph_traits::edge_iterator edge_it; - class CycleDetector : public boost::dfs_visitor<> { - public: - inline void tree_edge(edge_t edge, const PluginGraph& graph) { - const vertex_t source = boost::source(edge, graph); - const string name = graph[source].Name(); +class CycleDetector : public boost::dfs_visitor<> { +public: + void tree_edge(edge_t edge, const PluginGraph& graph) { + const vertex_t source = boost::source(edge, graph); + const string name = graph[source].Name(); - // Check if the plugin already exists in the recorded trail. - auto it = find(begin(trail), end(trail), name); + // Check if the plugin already exists in the recorded trail. + auto it = find(begin(trail), end(trail), name); - if (it != end(trail)) { - // Erase everything from this position onwards, as it doesn't - // contribute to a forward-cycle. - trail.erase(it, end(trail)); - } - - trail.push_back(name); - } - - inline void back_edge(edge_t edge, const PluginGraph& graph) { - vertex_t source = boost::source(edge, graph); - vertex_t target = boost::target(edge, graph); - - trail.push_back(graph[source].Name()); - string backCycle; - auto it = find(begin(trail), end(trail), graph[target].Name()); - for (it; it != end(trail); ++it) { - backCycle += *it + ", "; - } - backCycle.erase(backCycle.length() - 2); - - BOOST_LOG_TRIVIAL(error) << "Cyclic interaction detected between plugins \"" << graph[source].Name() << "\" and \"" << graph[target].Name() << "\". Back cycle: " << backCycle; - - throw loot::Error(loot::Error::Code::sorting_error, (boost::format(boost::locale::translate("Cyclic interaction detected between plugins \"%1%\" and \"%2%\". Back cycle: %3%")) % graph[source].Name() % graph[target].Name() % backCycle).str()); - } - - private: - std::list trail; - }; - - class PathDetector : public boost::bfs_visitor<> { - public: - PathDetector(vertex_t vertex) : target(vertex) {} - - inline void discover_vertex(vertex_t vertex, const PluginGraph& graph) { - if (vertex == target) - throw Error(Error::Code::ok, "Found a path."); - } - - private: - vertex_t target; - }; - - std::list PluginSorter::Sort(Game& game, const Language::Code language) { - // Clear existing data. - graph.clear(); - indexMap.clear(); - oldLoadOrder.clear(); - - // Clear any existing game-specific messages, as these only relate to - // state that has been changed by sorting. - game.ClearMessages(); - - addPluginVertices(game, language); - - // If there aren't any vertices, exit early, because sorting assumes - // there is at least one plugin. - if (boost::num_vertices(graph) == 0) - return std::list(); - - // Get the existing load order. - oldLoadOrder = game.GetLoadOrder(); - BOOST_LOG_TRIVIAL(info) << "Fetched existing load order: "; - for (const auto &plugin : oldLoadOrder) - BOOST_LOG_TRIVIAL(info) << plugin; - - //Now add the interactions between plugins to the graph as edges. - BOOST_LOG_TRIVIAL(info) << "Adding edges to plugin graph."; - BOOST_LOG_TRIVIAL(debug) << "Adding non-overlap edges."; - AddSpecificEdges(); - - PropagatePriorities(); - - BOOST_LOG_TRIVIAL(debug) << "Adding priority edges."; - AddPriorityEdges(); - - BOOST_LOG_TRIVIAL(debug) << "Adding overlap edges."; - AddOverlapEdges(); - - BOOST_LOG_TRIVIAL(debug) << "Adding tie-break edges."; - AddTieBreakEdges(); - - BOOST_LOG_TRIVIAL(info) << "Checking to see if the graph is cyclic."; - CheckForCycles(); - - //Now we can sort. - BOOST_LOG_TRIVIAL(info) << "Performing a topological sort."; - list sortedVertices; - boost::topological_sort(graph, std::front_inserter(sortedVertices), boost::vertex_index_map(vertexIndexMap)); - - // Check that the sorted path is Hamiltonian (ie. unique). - for (auto it = sortedVertices.begin(); it != sortedVertices.end(); ++it) { - if (next(it) != sortedVertices.end() && !boost::edge(*it, *next(it), graph).second) { - BOOST_LOG_TRIVIAL(error) << "The calculated load order is not unique. No edge exists between" - << graph[*it].Name() << " and " << graph[*next(it)].Name() << "."; - } - } - - // Output a plugin list using the sorted vertices. - BOOST_LOG_TRIVIAL(info) << "Calculated order: "; - list plugins; - for (const auto &vertex : sortedVertices) { - BOOST_LOG_TRIVIAL(info) << '\t' << graph[vertex].Name(); - plugins.push_back(graph[vertex]); - } - - game.SetLoadOrderSorted(true); - - return plugins; + if (it != end(trail)) { + // Erase everything from this position onwards, as it doesn't + // contribute to a forward-cycle. + trail.erase(it, end(trail)); } - void PluginSorter::addPluginVertices(Game& game, const Language::Code language) { - BOOST_LOG_TRIVIAL(info) << "Merging masterlist, userlist into plugin list, evaluating conditions and checking for install validity."; + trail.push_back(name); + } - // The resolution of tie-breaks in the plugin graph may be dependent - // on the order in which vertices are iterated over, as an earlier tie - // break resolution may cause a potential later tie break to instead - // cause a cycle. Vertices are stored in a std::list and added to the - // list using push_back(). - // Plugins are stored in an unordered map, so simply iterating over - // its elements is not guarunteed to produce a consistent vertex order. - // MSVC 2013 and GCC 5.0 have been shown to produce consistent - // iteration orders that differ, and while MSVC 2013's order seems to - // be independent on the order in which the unordered map was filled - // (being lexicographical), GCC 5.0's unordered map iteration order is - // dependent on its insertion order. - // Given that, the order of vertex creation should be made consistent - // in order to produce consistent sorting results. While MSVC 2013 - // doesn't strictly need this, there is no guaruntee that this - // unspecified behaviour will remain in future compiler updates, so - // implement it generally. + void back_edge(edge_t edge, const PluginGraph& graph) { + vertex_t source = boost::source(edge, graph); + vertex_t target = boost::target(edge, graph); - // Using a set of plugin names followed by finding the matching key - // in the unordered map, as it's probably faster than copying the - // full plugin objects then sorting them. - for (const auto &plugin : game.GetPlugins()) { - vertex_t v = boost::add_vertex(plugin, graph); - BOOST_LOG_TRIVIAL(trace) << "Merging for plugin \"" << graph[v].Name() << "\""; - - //Check if there is a plugin entry in the masterlist. This will also find matching regex entries. - BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; - graph[v].MergeMetadata(game.GetMasterlist().FindPlugin(graph[v])); - - //Check if there is a plugin entry in the userlist. This will also find matching regex entries. - PluginMetadata ulistPlugin = game.GetUserlist().FindPlugin(graph[v]); - - if (!ulistPlugin.HasNameOnly() && ulistPlugin.Enabled()) { - BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; - graph[v].MergeMetadata(ulistPlugin); - } - - //Now that items are merged, evaluate any conditions they have. - BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; - try { - graph[v].EvalAllConditions(game, language); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "\"" << graph[v].Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); - list messages(graph[v].Messages()); - messages.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % graph[v].Name() % e.what()).str())); - graph[v].Messages(messages); - } - - //Also check install validity. - graph[v].CheckInstallValidity(game); - } - - // Prebuild an index map, which std::list-based VertexList graphs don't have. - vertexIndexMap = vertex_map_t(indexMap); - size_t i = 0; - BGL_FORALL_VERTICES(v, graph, PluginGraph) - put(vertexIndexMap, v, i++); + trail.push_back(graph[source].Name()); + string backCycle; + auto it = find(begin(trail), end(trail), graph[target].Name()); + for (it; it != end(trail); ++it) { + backCycle += *it + ", "; } + backCycle.erase(backCycle.length() - 2); - bool PluginSorter::GetVertexByName(const std::string& name, vertex_t& vertexOut) const { - for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph))) { - if (boost::iequals(graph[vertex].Name(), name)) { - vertexOut = vertex; - return true; - } - } + BOOST_LOG_TRIVIAL(error) << "Cyclic interaction detected between plugins \"" << graph[source].Name() << "\" and \"" << graph[target].Name() << "\". Back cycle: " << backCycle; - return false; + throw loot::Error(loot::Error::Code::sorting_error, (boost::format(boost::locale::translate("Cyclic interaction detected between plugins \"%1%\" and \"%2%\". Back cycle: %3%")) % graph[source].Name() % graph[target].Name() % backCycle).str()); + } + +private: + list trail; +}; + +class PathDetector : public boost::bfs_visitor<> { +public: + PathDetector(vertex_t vertex) : target(vertex) {} + + inline void discover_vertex(vertex_t vertex, const PluginGraph& graph) { + if (vertex == target) + throw Error(Error::Code::ok, "Found a path."); + } + +private: + vertex_t target; +}; + +std::list PluginSorter::Sort(Game& game, const Language::Code language) { + // Clear existing data. + graph_.clear(); + indexMap_.clear(); + oldLoadOrder_.clear(); + + // Clear any existing game-specific messages, as these only relate to + // state that has been changed by sorting. + game.ClearMessages(); + + AddPluginVertices(game, language); + + // If there aren't any vertices, exit early, because sorting assumes + // there is at least one plugin. + if (boost::num_vertices(graph_) == 0) + return list(); + +// Get the existing load order. + oldLoadOrder_ = game.GetLoadOrder(); + BOOST_LOG_TRIVIAL(info) << "Fetched existing load order: "; + for (const auto &plugin : oldLoadOrder_) + BOOST_LOG_TRIVIAL(info) << plugin; + +//Now add the interactions between plugins to the graph as edges. + BOOST_LOG_TRIVIAL(info) << "Adding edges to plugin graph."; + BOOST_LOG_TRIVIAL(debug) << "Adding non-overlap edges."; + AddSpecificEdges(); + + PropagatePriorities(); + + BOOST_LOG_TRIVIAL(debug) << "Adding priority edges."; + AddPriorityEdges(); + + BOOST_LOG_TRIVIAL(debug) << "Adding overlap edges."; + AddOverlapEdges(); + + BOOST_LOG_TRIVIAL(debug) << "Adding tie-break edges."; + AddTieBreakEdges(); + + BOOST_LOG_TRIVIAL(info) << "Checking to see if the graph is cyclic."; + CheckForCycles(); + + //Now we can sort. + BOOST_LOG_TRIVIAL(info) << "Performing a topological sort."; + list sortedVertices; + boost::topological_sort(graph_, std::front_inserter(sortedVertices), boost::vertex_index_map(vertexIndexMap_)); + + // Check that the sorted path is Hamiltonian (ie. unique). + for (auto it = sortedVertices.begin(); it != sortedVertices.end(); ++it) { + if (next(it) != sortedVertices.end() && !boost::edge(*it, *next(it), graph_).second) { + BOOST_LOG_TRIVIAL(error) << "The calculated load order is not unique. No edge exists between" + << graph_[*it].Name() << " and " << graph_[*next(it)].Name() << "."; } + } - void PluginSorter::CheckForCycles() const { - boost::depth_first_search(graph, visitor(CycleDetector()).vertex_index_map(vertexIndexMap)); - } + // Output a plugin list using the sorted vertices. + BOOST_LOG_TRIVIAL(info) << "Calculated order: "; + list plugins; + for (const auto &vertex : sortedVertices) { + BOOST_LOG_TRIVIAL(info) << '\t' << graph_[vertex].Name(); + plugins.push_back(graph_[vertex]); + } - bool PluginSorter::EdgeCreatesCycle(const vertex_t& fromVertex, const vertex_t& toVertex) const { - try { - boost::breadth_first_search(graph, toVertex, visitor(PathDetector(fromVertex)).vertex_index_map(vertexIndexMap)); - } - catch (Error& e) { - if (e.code() == Error::Code::ok) - return true; - } - return false; - } + game.SetLoadOrderSorted(true); - void PluginSorter::PropagatePriorities() { - /* If a plugin has a priority value > 0, that value should be - inherited by all plugins that have edges coming from that - plugin, ie. those that load after it, unless the plugin being - compared itself has a larger value. */ - - // Find all vertices with priorities > 0. - std::vector positivePriorityVertices; - vertex_it vit, vitend; - tie(vit, vitend) = boost::vertices(graph); - std::copy_if(vit, - vitend, - std::back_inserter(positivePriorityVertices), - [&](const vertex_t& vertex) { - return graph[vertex].Priority() > 0; - }); - - // To reduce the number of priorities that will need setting, - // sort the vertices in order of decreasing priority. - std::sort(begin(positivePriorityVertices), - end(positivePriorityVertices), - [&](const vertex_t& lhs, const vertex_t& rhs) { - return graph[lhs].Priority() > graph[rhs].Priority(); - }); - - // Create a color map. - std::vector colorVec(num_vertices(graph)); - boost::iterator_property_map colorMap(&colorVec.front(), vertexIndexMap); - - // Now loop over the vertices. For each one, do a depth-first - // search, setting priorities until an equal or larger value is - // encountered. - for (const vertex_t& vertex : positivePriorityVertices) { - BOOST_LOG_TRIVIAL(trace) << "Doing DFS for " << graph[vertex].Name() << " which has priority " << graph[vertex].Priority(); - boost::dfs_visitor<> visitor; - boost::depth_first_visit(graph, - vertex, - visitor, - colorMap, - [&vertex](const vertex_t& currentVertex, const PluginGraph& graph) { - if (graph[currentVertex].Priority() < graph[vertex].Priority()) { - BOOST_LOG_TRIVIAL(trace) << "Overriding priority for " << graph[currentVertex].Name() << " from " << graph[currentVertex].Priority() << " to " << graph[vertex].Priority(); - // const_cast is necessary because depth_first_search - // takes a const graph. - const_cast(graph)[currentVertex].Priority(graph[vertex].Priority()); - - return false; - } - - return currentVertex != vertex - && graph[currentVertex].Priority() >= graph[vertex].Priority(); - }); - } + return plugins; +} + +void PluginSorter::AddPluginVertices(Game& game, const Language::Code language) { + BOOST_LOG_TRIVIAL(info) << "Merging masterlist, userlist into plugin list, evaluating conditions and checking for install validity."; + + // The resolution of tie-breaks in the plugin graph may be dependent + // on the order in which vertices are iterated over, as an earlier tie + // break resolution may cause a potential later tie break to instead + // cause a cycle. Vertices are stored in a std::list and added to the + // list using push_back(). + // Plugins are stored in an unordered map, so simply iterating over + // its elements is not guarunteed to produce a consistent vertex order. + // MSVC 2013 and GCC 5.0 have been shown to produce consistent + // iteration orders that differ, and while MSVC 2013's order seems to + // be independent on the order in which the unordered map was filled + // (being lexicographical), GCC 5.0's unordered map iteration order is + // dependent on its insertion order. + // Given that, the order of vertex creation should be made consistent + // in order to produce consistent sorting results. While MSVC 2013 + // doesn't strictly need this, there is no guaruntee that this + // unspecified behaviour will remain in future compiler updates, so + // implement it generally. + + // Using a set of plugin names followed by finding the matching key + // in the unordered map, as it's probably faster than copying the + // full plugin objects then sorting them. + for (const auto &plugin : game.GetPlugins()) { + vertex_t v = boost::add_vertex(plugin, graph_); + BOOST_LOG_TRIVIAL(trace) << "Merging for plugin \"" << graph_[v].Name() << "\""; + + //Check if there is a plugin entry in the masterlist. This will also find matching regex entries. + BOOST_LOG_TRIVIAL(trace) << "Merging masterlist data down to plugin list data."; + graph_[v].MergeMetadata(game.GetMasterlist().FindPlugin(graph_[v])); + + //Check if there is a plugin entry in the userlist. This will also find matching regex entries. + PluginMetadata ulistPlugin = game.GetUserlist().FindPlugin(graph_[v]); + + if (!ulistPlugin.HasNameOnly() && ulistPlugin.Enabled()) { + BOOST_LOG_TRIVIAL(trace) << "Merging userlist data down to plugin list data."; + graph_[v].MergeMetadata(ulistPlugin); } - void PluginSorter::addEdge(const vertex_t& fromVertex, const vertex_t& toVertex) { - if (!boost::edge(fromVertex, toVertex, graph).second) { - BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph[fromVertex].Name() << "\" to \"" << graph[toVertex].Name() << "\"."; + //Now that items are merged, evaluate any conditions they have. + BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; + try { + graph_[v].EvalAllConditions(game, language); + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "\"" << graph_[v].Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); + list messages(graph_[v].Messages()); + messages.push_back(Message(Message::Type::error, (boost::format(boost::locale::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % graph_[v].Name() % e.what()).str())); + graph_[v].Messages(messages); + } - boost::add_edge(fromVertex, toVertex, graph); - } + //Also check install validity. + graph_[v].CheckInstallValidity(game); + } + + // Prebuild an index map, which std::list-based VertexList graphs don't have. + vertexIndexMap_ = vertex_map_t(indexMap_); + size_t i = 0; + BGL_FORALL_VERTICES(v, graph_, PluginGraph) + put(vertexIndexMap_, v, i++); +} + +bool PluginSorter::GetVertexByName(const std::string& name, vertex_t& vertexOut) const { + for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) { + if (boost::iequals(graph_[vertex].Name(), name)) { + vertexOut = vertex; + return true; + } + } + + return false; +} + +void PluginSorter::CheckForCycles() const { + boost::depth_first_search(graph_, visitor(CycleDetector()).vertex_index_map(vertexIndexMap_)); +} + +bool PluginSorter::EdgeCreatesCycle(const vertex_t& fromVertex, const vertex_t& toVertex) const { + try { + boost::breadth_first_search(graph_, toVertex, visitor(PathDetector(fromVertex)).vertex_index_map(vertexIndexMap_)); + } catch (Error& e) { + if (e.code() == Error::Code::ok) + return true; + } + return false; +} + +void PluginSorter::PropagatePriorities() { + /* If a plugin has a priority value > 0, that value should be + inherited by all plugins that have edges coming from that + plugin, ie. those that load after it, unless the plugin being + compared itself has a larger value. */ + + // Find all vertices with priorities > 0. + std::vector positivePriorityVertices; + vertex_it vit, vitend; + tie(vit, vitend) = boost::vertices(graph_); + std::copy_if(vit, + vitend, + std::back_inserter(positivePriorityVertices), + [&](const vertex_t& vertex) { + return graph_[vertex].Priority() > 0; + }); + + // To reduce the number of priorities that will need setting, + // sort the vertices in order of decreasing priority. + std::sort(begin(positivePriorityVertices), + end(positivePriorityVertices), + [&](const vertex_t& lhs, const vertex_t& rhs) { + return graph_[lhs].Priority() > graph_[rhs].Priority(); + }); + + // Create a color map. + std::vector colorVec(num_vertices(graph_)); + boost::iterator_property_map colorMap(&colorVec.front(), vertexIndexMap_); + + // Now loop over the vertices. For each one, do a depth-first + // search, setting priorities until an equal or larger value is + // encountered. + for (const vertex_t& vertex : positivePriorityVertices) { + BOOST_LOG_TRIVIAL(trace) << "Doing DFS for " << graph_[vertex].Name() << " which has priority " << graph_[vertex].Priority(); + boost::dfs_visitor<> visitor; + boost::depth_first_visit(graph_, + vertex, + visitor, + colorMap, + [&vertex](const vertex_t& currentVertex, const PluginGraph& graph) { + if (graph[currentVertex].Priority() < graph[vertex].Priority()) { + BOOST_LOG_TRIVIAL(trace) << "Overriding priority for " << graph[currentVertex].Name() << " from " << graph[currentVertex].Priority() << " to " << graph[vertex].Priority(); + // const_cast is necessary because depth_first_search + // takes a const graph. + const_cast(graph)[currentVertex].Priority(graph[vertex].Priority()); + + return false; + } + + return currentVertex != vertex + && graph[currentVertex].Priority() >= graph[vertex].Priority(); + }); + } +} + +void PluginSorter::AddEdge(const vertex_t& fromVertex, const vertex_t& toVertex) { + if (!boost::edge(fromVertex, toVertex, graph_).second) { + BOOST_LOG_TRIVIAL(trace) << "Adding edge from \"" << graph_[fromVertex].Name() << "\" to \"" << graph_[toVertex].Name() << "\"."; + + boost::add_edge(fromVertex, toVertex, graph_); + } +} + +void PluginSorter::AddSpecificEdges() { + //Add edges for all relationships that aren't overlaps or priority differences. + vertex_it vit, vitend; + for (tie(vit, vitend) = boost::vertices(graph_); vit != vitend; ++vit) { + BOOST_LOG_TRIVIAL(trace) << "Adding specific edges to vertex for \"" << graph_[*vit].Name() << "\"."; + + BOOST_LOG_TRIVIAL(trace) << "Adding edges for master flag differences."; + for (vertex_it vit2 = vit; vit2 != vitend; ++vit2) { + if (graph_[*vit].isMasterFile() == graph_[*vit2].isMasterFile()) + continue; + + vertex_t vertex, parentVertex; + if (graph_[*vit2].isMasterFile()) { + parentVertex = *vit2; + vertex = *vit; + } else { + parentVertex = *vit; + vertex = *vit2; + } + + AddEdge(parentVertex, vertex); } - void PluginSorter::AddSpecificEdges() { - //Add edges for all relationships that aren't overlaps or priority differences. - vertex_it vit, vitend; - for (tie(vit, vitend) = boost::vertices(graph); vit != vitend; ++vit) { - BOOST_LOG_TRIVIAL(trace) << "Adding specific edges to vertex for \"" << graph[*vit].Name() << "\"."; - - BOOST_LOG_TRIVIAL(trace) << "Adding edges for master flag differences."; - for (vertex_it vit2 = vit; vit2 != vitend; ++vit2) { - if (graph[*vit].isMasterFile() == graph[*vit2].isMasterFile()) - continue; - - vertex_t vertex, parentVertex; - if (graph[*vit2].isMasterFile()) { - parentVertex = *vit2; - vertex = *vit; - } - else { - parentVertex = *vit; - vertex = *vit2; - } - - addEdge(parentVertex, vertex); - } - - vertex_t parentVertex; - BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for masters."; - for (const auto &master : graph[*vit].getMasters()) { - if (GetVertexByName(master, parentVertex)) - addEdge(parentVertex, *vit); - } - - BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for requirements."; - for (const auto &file : graph[*vit].Reqs()) { - if (GetVertexByName(file.Name(), parentVertex)) - addEdge(parentVertex, *vit); - } - - BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for 'load after's."; - for (const auto &file : graph[*vit].LoadAfter()) { - if (GetVertexByName(file.Name(), parentVertex)) - addEdge(parentVertex, *vit); - } - } + vertex_t parentVertex; + BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for masters."; + for (const auto &master : graph_[*vit].getMasters()) { + if (GetVertexByName(master, parentVertex)) + AddEdge(parentVertex, *vit); } - void PluginSorter::AddPriorityEdges() { - for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph))) { - BOOST_LOG_TRIVIAL(trace) << "Adding priority difference edges to vertex for \"" << graph[vertex].Name() << "\"."; - // If the plugin does not have a global priority and doesn't load - // an archive and has no override records, skip it. Plugins without - // override records can only conflict with plugins that override - // the records they add, so any edge necessary will be added when - // evaluating that plugin. - if (!graph[vertex].IsPriorityGlobal() && graph[vertex].NumOverrideFormIDs() == 0 && !graph[vertex].LoadsArchive()) - continue; - - for (const auto& otherVertex : boost::make_iterator_range(boost::vertices(graph))) { - // If the plugins have equal priority, or have non-global - // priorities but don't conflict, don't add a priority edge. - if (graph[vertex].Priority() == graph[otherVertex].Priority() - || !graph[vertex].IsPriorityGlobal() && !graph[otherVertex].IsPriorityGlobal() && !graph[vertex].DoFormIDsOverlap(graph[otherVertex])) { - continue; - } - - vertex_t toVertex, fromVertex; - if (graph[vertex].Priority() < graph[otherVertex].Priority()) { - fromVertex = vertex; - toVertex = otherVertex; - } - else { - fromVertex = otherVertex; - toVertex = vertex; - } - - if (!EdgeCreatesCycle(fromVertex, toVertex)) - addEdge(fromVertex, toVertex); - } - } + BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for requirements."; + for (const auto &file : graph_[*vit].Reqs()) { + if (GetVertexByName(file.Name(), parentVertex)) + AddEdge(parentVertex, *vit); } - void PluginSorter::AddOverlapEdges() { - for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph))) { - BOOST_LOG_TRIVIAL(trace) << "Adding overlap edges to vertex for \"" << graph[vertex].Name() << "\"."; - - if (graph[vertex].NumOverrideFormIDs() == 0) { - BOOST_LOG_TRIVIAL(trace) << "Skipping vertex for \"" << graph[vertex].Name() << "\": the plugin contains no override records."; - continue; - } - - for (const auto& otherVertex : boost::make_iterator_range(boost::vertices(graph))) { - if (vertex == otherVertex || - boost::edge(vertex, otherVertex, graph).second || - boost::edge(otherVertex, vertex, graph).second || - graph[vertex].NumOverrideFormIDs() == graph[otherVertex].NumOverrideFormIDs() || - !graph[vertex].DoFormIDsOverlap(graph[otherVertex])) { - continue; - } - - vertex_t toVertex, fromVertex; - if (graph[vertex].NumOverrideFormIDs() > graph[otherVertex].NumOverrideFormIDs()) { - fromVertex = vertex; - toVertex = otherVertex; - } - else { - fromVertex = otherVertex; - toVertex = vertex; - } - - if (!EdgeCreatesCycle(fromVertex, toVertex)) - addEdge(fromVertex, toVertex); - } - } + BOOST_LOG_TRIVIAL(trace) << "Adding in-edges for 'load after's."; + for (const auto &file : graph_[*vit].LoadAfter()) { + if (GetVertexByName(file.Name(), parentVertex)) + AddEdge(parentVertex, *vit); + } + } +} + +void PluginSorter::AddPriorityEdges() { + for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) { + BOOST_LOG_TRIVIAL(trace) << "Adding priority difference edges to vertex for \"" << graph_[vertex].Name() << "\"."; + // If the plugin does not have a global priority and doesn't load + // an archive and has no override records, skip it. Plugins without + // override records can only conflict with plugins that override + // the records they add, so any edge necessary will be added when + // evaluating that plugin. + if (!graph_[vertex].IsPriorityGlobal() && graph_[vertex].NumOverrideFormIDs() == 0 && !graph_[vertex].LoadsArchive()) + continue; + + for (const auto& otherVertex : boost::make_iterator_range(boost::vertices(graph_))) { + // If the plugins have equal priority, or have non-global + // priorities but don't conflict, don't add a priority edge. + if (graph_[vertex].Priority() == graph_[otherVertex].Priority() + || !graph_[vertex].IsPriorityGlobal() && !graph_[otherVertex].IsPriorityGlobal() && !graph_[vertex].DoFormIDsOverlap(graph_[otherVertex])) { + continue; + } + + vertex_t toVertex, fromVertex; + if (graph_[vertex].Priority() < graph_[otherVertex].Priority()) { + fromVertex = vertex; + toVertex = otherVertex; + } else { + fromVertex = otherVertex; + toVertex = vertex; + } + + if (!EdgeCreatesCycle(fromVertex, toVertex)) + AddEdge(fromVertex, toVertex); } + } +} - int PluginSorter::plugincmp(const std::string& plugin1, const std::string& plugin2) const { - auto it1 = find(begin(oldLoadOrder), end(oldLoadOrder), plugin1); - auto it2 = find(begin(oldLoadOrder), end(oldLoadOrder), plugin2); - - if (it1 != end(oldLoadOrder) && it2 == end(oldLoadOrder)) - return -1; - else if (it1 == end(oldLoadOrder) && it2 != end(oldLoadOrder)) - return 1; - else if (it1 != end(oldLoadOrder) && it2 != end(oldLoadOrder)) { - if (distance(begin(oldLoadOrder), it1) < distance(begin(oldLoadOrder), it2)) - return -1; - else - return 1; - } - else { - // Neither plugin has a load order position. Need to use another - // comparison to get an ordering. - - // Compare plugin basenames. - string name1 = boost::locale::to_lower(plugin1); - name1 = name1.substr(0, name1.length() - 4); - string name2 = boost::locale::to_lower(plugin2); - name2 = name2.substr(0, name2.length() - 4); - - if (name1 < name2) - return -1; - else if (name2 < name1) - return 1; - else { - // Could be a .esp and .esm plugin with the same basename, - // compare whole filenames. - if (plugin1 < plugin2) - return -1; - else - return 1; - } - } - return 0; +void PluginSorter::AddOverlapEdges() { + for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) { + BOOST_LOG_TRIVIAL(trace) << "Adding overlap edges to vertex for \"" << graph_[vertex].Name() << "\"."; + + if (graph_[vertex].NumOverrideFormIDs() == 0) { + BOOST_LOG_TRIVIAL(trace) << "Skipping vertex for \"" << graph_[vertex].Name() << "\": the plugin contains no override records."; + continue; } - void PluginSorter::AddTieBreakEdges() { - // In order for the sort to be performed stably, there must be only one possible result. - // This can be enforced by adding edges between all vertices that aren't already linked. - // Use existing load order to decide the direction of these edges. - for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph))) { - BOOST_LOG_TRIVIAL(trace) << "Adding tie-break edges to vertex for \"" << graph[vertex].Name() << "\"."; - - for (const auto& otherVertex : boost::make_iterator_range(boost::vertices(graph))) { - if (vertex == otherVertex || boost::edge(vertex, otherVertex, graph).second || boost::edge(otherVertex, vertex, graph).second) - continue; - - vertex_t toVertex, fromVertex; - if (plugincmp(graph[vertex].Name(), graph[otherVertex].Name()) < 0) { - fromVertex = vertex; - toVertex = otherVertex; - } - else { - fromVertex = otherVertex; - toVertex = vertex; - } - - if (!EdgeCreatesCycle(fromVertex, toVertex)) - addEdge(fromVertex, toVertex); - } - } + for (const auto& otherVertex : boost::make_iterator_range(boost::vertices(graph_))) { + if (vertex == otherVertex || + boost::edge(vertex, otherVertex, graph_).second || + boost::edge(otherVertex, vertex, graph_).second || + graph_[vertex].NumOverrideFormIDs() == graph_[otherVertex].NumOverrideFormIDs() || + !graph_[vertex].DoFormIDsOverlap(graph_[otherVertex])) { + continue; + } + + vertex_t toVertex, fromVertex; + if (graph_[vertex].NumOverrideFormIDs() > graph_[otherVertex].NumOverrideFormIDs()) { + fromVertex = vertex; + toVertex = otherVertex; + } else { + fromVertex = otherVertex; + toVertex = vertex; + } + + if (!EdgeCreatesCycle(fromVertex, toVertex)) + AddEdge(fromVertex, toVertex); + } + } +} + +int PluginSorter::ComparePlugins(const std::string& plugin1, const std::string& plugin2) const { + auto it1 = find(begin(oldLoadOrder_), end(oldLoadOrder_), plugin1); + auto it2 = find(begin(oldLoadOrder_), end(oldLoadOrder_), plugin2); + + if (it1 != end(oldLoadOrder_) && it2 == end(oldLoadOrder_)) + return -1; + else if (it1 == end(oldLoadOrder_) && it2 != end(oldLoadOrder_)) + return 1; + else if (it1 != end(oldLoadOrder_) && it2 != end(oldLoadOrder_)) { + if (distance(begin(oldLoadOrder_), it1) < distance(begin(oldLoadOrder_), it2)) + return -1; + else + return 1; + } else { + // Neither plugin has a load order position. Need to use another + // comparison to get an ordering. + + // Compare plugin basenames. + string name1 = boost::locale::to_lower(plugin1); + name1 = name1.substr(0, name1.length() - 4); + string name2 = boost::locale::to_lower(plugin2); + name2 = name2.substr(0, name2.length() - 4); + + if (name1 < name2) + return -1; + else if (name2 < name1) + return 1; + else { + // Could be a .esp and .esm plugin with the same basename, + // compare whole filenames. + if (plugin1 < plugin2) + return -1; + else + return 1; + } + } + return 0; +} + +void PluginSorter::AddTieBreakEdges() { + // In order for the sort to be performed stably, there must be only one possible result. + // This can be enforced by adding edges between all vertices that aren't already linked. + // Use existing load order to decide the direction of these edges. + for (const auto& vertex : boost::make_iterator_range(boost::vertices(graph_))) { + BOOST_LOG_TRIVIAL(trace) << "Adding tie-break edges to vertex for \"" << graph_[vertex].Name() << "\"."; + + for (const auto& otherVertex : boost::make_iterator_range(boost::vertices(graph_))) { + if (vertex == otherVertex || boost::edge(vertex, otherVertex, graph_).second || boost::edge(otherVertex, vertex, graph_).second) + continue; + + vertex_t toVertex, fromVertex; + if (ComparePlugins(graph_[vertex].Name(), graph_[otherVertex].Name()) < 0) { + fromVertex = vertex; + toVertex = otherVertex; + } else { + fromVertex = otherVertex; + toVertex = vertex; + } + + if (!EdgeCreatesCycle(fromVertex, toVertex)) + AddEdge(fromVertex, toVertex); } + } +} } diff --git a/src/backend/plugin/plugin_sorter.h b/src/backend/plugin/plugin_sorter.h index 62fbecf3..1f03410b 100644 --- a/src/backend/plugin/plugin_sorter.h +++ b/src/backend/plugin/plugin_sorter.h @@ -22,48 +22,47 @@ . */ -#ifndef LOOT_BACKEND_PLUGIN_SORTER -#define LOOT_BACKEND_PLUGIN_SORTER - -#include "plugin.h" +#ifndef LOOT_BACKEND_PLUGIN_PLUGIN_SORTER +#define LOOT_BACKEND_PLUGIN_PLUGIN_SORTER #include -#include #include +#include + +#include "backend/game/game.h" +#include "backend/plugin/plugin.h" namespace loot { - typedef boost::adjacency_list PluginGraph; - typedef boost::graph_traits::vertex_descriptor vertex_t; - typedef boost::associative_property_map> vertex_map_t; +typedef boost::adjacency_list PluginGraph; +typedef boost::graph_traits::vertex_descriptor vertex_t; +typedef boost::associative_property_map> vertex_map_t; - class Game; +class PluginSorter { +public: + std::list Sort(Game& game, const Language::Code language); +private: + bool GetVertexByName(const std::string& name, vertex_t& vertex) const; + void CheckForCycles() const; + bool EdgeCreatesCycle(const vertex_t& u, const vertex_t& v) const; - class PluginSorter { - public: - std::list Sort(Game& game, const Language::Code language); - private: - PluginGraph graph; - std::map indexMap; - vertex_map_t vertexIndexMap; - std::list oldLoadOrder; + int ComparePlugins(const std::string& plugin1, const std::string& plugin2) const; - bool GetVertexByName(const std::string& name, vertex_t& vertex) const; - void CheckForCycles() const; - bool EdgeCreatesCycle(const vertex_t& u, const vertex_t& v) const; + void PropagatePriorities(); - int plugincmp(const std::string& plugin1, const std::string& plugin2) const; + void AddPluginVertices(Game& game, const Language::Code language); + void AddSpecificEdges(); + void AddPriorityEdges(); + void AddOverlapEdges(); + void AddTieBreakEdges(); - void PropagatePriorities(); + void AddEdge(const vertex_t& fromVertex, const vertex_t& toVertex); - void addPluginVertices(Game& game, const Language::Code language); - void AddSpecificEdges(); - void AddPriorityEdges(); - void AddOverlapEdges(); - void AddTieBreakEdges(); - - void addEdge(const vertex_t& fromVertex, const vertex_t& toVertex); - }; + PluginGraph graph_; + std::map indexMap_; + vertex_map_t vertexIndexMap_; + std::list oldLoadOrder_; +}; } #endif diff --git a/src/gui/loot_app.cpp b/src/gui/loot_app.cpp index b8ff24fa..016fe3fa 100644 --- a/src/gui/loot_app.cpp +++ b/src/gui/loot_app.cpp @@ -22,121 +22,110 @@ . */ -#include "loot_app.h" -#include "loot_handler.h" -#include "loot_scheme_handler_factory.h" - -#include "../backend/app/loot_paths.h" -#include "../backend/helpers/helpers.h" -#include "../backend/helpers/language.h" +#include "gui/loot_app.h" +#include #include #include -#include -#include -#include - -#include - -using namespace std; -using boost::locale::translate; -using boost::format; - -namespace fs = boost::filesystem; +#include "backend/app/loot_paths.h" +#include "backend/helpers/helpers.h" +#include "backend/helpers/language.h" +#include "gui/loot_handler.h" +#include "gui/loot_scheme_handler_factory.h" namespace loot { - LootApp::LootApp() { - LootPaths::initialise(); - } +LootApp::LootApp() { + LootPaths::initialise(); +} - void LootApp::Initialise(const std::string& commandLineGameArg) { - lootState_.Init(commandLineGameArg); - } +void LootApp::Initialise(const std::string& commandLineGameArg) { + lootState_.init(commandLineGameArg); +} - void LootApp::OnBeforeCommandLineProcessing(const CefString& process_type, - CefRefPtr command_line) { - if (process_type.empty()) { - // Browser process, OK to modify the command line. +void LootApp::OnBeforeCommandLineProcessing(const CefString& process_type, + CefRefPtr command_line) { + if (process_type.empty()) { + // Browser process, OK to modify the command line. - // Disable spell checking. - command_line->AppendSwitch("--disable-spell-checking"); - command_line->AppendSwitch("--disable-extensions"); - } - } + // Disable spell checking. + command_line->AppendSwitch("--disable-spell-checking"); + command_line->AppendSwitch("--disable-extensions"); + } +} - CefRefPtr LootApp::GetBrowserProcessHandler() { - return this; - } +CefRefPtr LootApp::GetBrowserProcessHandler() { + return this; +} - CefRefPtr LootApp::GetRenderProcessHandler() { - return this; - } +CefRefPtr LootApp::GetRenderProcessHandler() { + return this; +} - void LootApp::OnRegisterCustomSchemes(CefRefPtr registrar) { - // Register "loot" as a standard scheme. - registrar->AddCustomScheme("loot", true, false, false); - } +void LootApp::OnRegisterCustomSchemes(CefRefPtr registrar) { + // Register "loot" as a standard scheme. + registrar->AddCustomScheme("loot", true, false, false); +} - void LootApp::OnContextInitialized() { - //Make sure this is running in the UI thread. - assert(CefCurrentlyOn(TID_UI)); +void LootApp::OnContextInitialized() { + //Make sure this is running in the UI thread. + assert(CefCurrentlyOn(TID_UI)); - // Information used when creating the native window. - CefWindowInfo window_info; + // Information used when creating the native window. + CefWindowInfo window_info; #ifdef _WIN32 // On Windows we need to specify certain flags that will be passed to CreateWindowEx(). - window_info.SetAsPopup(NULL, "LOOT"); + window_info.SetAsPopup(NULL, "LOOT"); #endif // Set the handler for browser-level callbacks. - CefRefPtr handler(new LootHandler(lootState_)); + CefRefPtr handler(new LootHandler(lootState_)); - // Register the custom "loot" scheme handlers. - CefRegisterSchemeHandlerFactory("loot", "l10n", new LootSchemeHandlerFactory()); + // Register the custom "loot" scheme handlers. + CefRegisterSchemeHandlerFactory("loot", "l10n", new LootSchemeHandlerFactory()); - // Specify CEF browser settings here. - CefBrowserSettings browser_settings; + // Specify CEF browser settings here. + CefBrowserSettings browser_settings; - // Need to set the global locale for this process so that messages will - // be translated. - BOOST_LOG_TRIVIAL(debug) << "Initialising language settings in UI thread."; - if (lootState_.getLanguage().GetCode() != Language::Code::english) { - boost::locale::generator gen; - gen.add_messages_path(LootPaths::getL10nPath().string()); - gen.add_messages_domain("loot"); + // Need to set the global locale for this process so that messages will + // be translated. + BOOST_LOG_TRIVIAL(debug) << "Initialising language settings in UI thread."; + if (lootState_.getLanguage().GetCode() != Language::Code::english) { + boost::locale::generator gen; + gen.add_messages_path(LootPaths::getL10nPath().string()); + gen.add_messages_domain("loot"); - BOOST_LOG_TRIVIAL(debug) << "Selected language: " << lootState_.getLanguage().GetName(); - locale::global(gen(lootState_.getLanguage().GetLocale() + ".UTF-8")); - boost::filesystem::path::imbue(locale()); - } + BOOST_LOG_TRIVIAL(debug) << "Selected language: " << lootState_.getLanguage().GetName(); + std::locale::global(gen(lootState_.getLanguage().GetLocale() + ".UTF-8")); + boost::filesystem::path::imbue(std::locale()); + } - // Set URL to load. Ignore any command line values. - std::string url = ToFileURL(LootPaths::getUIIndexPath()); + // Set URL to load. Ignore any command line values. + std::string url = ToFileURL(LootPaths::getUIIndexPath()); - // Create the first browser window. - CefBrowserHost::CreateBrowser(window_info, handler.get(), url, browser_settings, NULL); - } + // Create the first browser window. + CefBrowserHost::CreateBrowser(window_info, handler.get(), url, browser_settings, NULL); +} - void LootApp::OnWebKitInitialized() { - // Create the renderer-side router for query handling. - CefMessageRouterConfig config; - message_router_ = CefMessageRouterRendererSide::Create(config); - } +void LootApp::OnWebKitInitialized() { + // Create the renderer-side router for query handling. + CefMessageRouterConfig config; + message_router_ = CefMessageRouterRendererSide::Create(config); +} - bool LootApp::OnProcessMessageReceived( - CefRefPtr browser, - CefProcessId source_process, - CefRefPtr message) { - // Handle IPC messages from the browser process... - return message_router_->OnProcessMessageReceived(browser, source_process, message); - } +bool LootApp::OnProcessMessageReceived( + CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) { + // Handle IPC messages from the browser process... + return message_router_->OnProcessMessageReceived(browser, source_process, message); +} - void LootApp::OnContextCreated(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr context) { - // Register javascript functions. - message_router_->OnContextCreated(browser, frame, context); - } +void LootApp::OnContextCreated(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) { + // Register javascript functions. + message_router_->OnContextCreated(browser, frame, context); +} } diff --git a/src/gui/loot_app.h b/src/gui/loot_app.h index b3486ae8..046e0454 100644 --- a/src/gui/loot_app.h +++ b/src/gui/loot_app.h @@ -22,49 +22,49 @@ . */ -#ifndef __LOOT_GUI_LOOT_APP__ -#define __LOOT_GUI_LOOT_APP__ +#ifndef LOOT_GUI_LOOT_APP +#define LOOT_GUI_LOOT_APP + +#include +#include +#include #include "backend/app/loot_state.h" -#include -#include -#include - namespace loot { - class LootApp : public CefApp, - public CefBrowserProcessHandler, - public CefRenderProcessHandler { - public: - LootApp(); - void Initialise(const std::string& commandLineGameArg); +class LootApp : public CefApp, + public CefBrowserProcessHandler, + public CefRenderProcessHandler { +public: + LootApp(); + void Initialise(const std::string& commandLineGameArg); - // Override CefApp methods. - virtual void OnBeforeCommandLineProcessing(const CefString& process_type, - CefRefPtr command_line); - virtual CefRefPtr GetBrowserProcessHandler() OVERRIDE; - virtual CefRefPtr GetRenderProcessHandler() OVERRIDE; - virtual void OnRegisterCustomSchemes(CefRefPtr registrar) OVERRIDE; + // Override CefApp methods. + virtual void OnBeforeCommandLineProcessing(const CefString& process_type, + CefRefPtr command_line); + virtual CefRefPtr GetBrowserProcessHandler() OVERRIDE; + virtual CefRefPtr GetRenderProcessHandler() OVERRIDE; + virtual void OnRegisterCustomSchemes(CefRefPtr registrar) OVERRIDE; - // Override CefBrowserProcessHandler methods. - virtual void OnContextInitialized() OVERRIDE; - virtual void OnWebKitInitialized() OVERRIDE; + // Override CefBrowserProcessHandler methods. + virtual void OnContextInitialized() OVERRIDE; + virtual void OnWebKitInitialized() OVERRIDE; - // Override CefRenderProcessHandler methods. - virtual bool OnProcessMessageReceived(CefRefPtr browser, - CefProcessId source_process, - CefRefPtr message) OVERRIDE; + // Override CefRenderProcessHandler methods. + virtual bool OnProcessMessageReceived(CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) OVERRIDE; - private: - LootState lootState_; - CefRefPtr message_router_; +private: + virtual void OnContextCreated(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr context) OVERRIDE; - virtual void OnContextCreated(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr context) OVERRIDE; + LootState lootState_; + CefRefPtr message_router_; - IMPLEMENT_REFCOUNTING(LootApp); - }; + IMPLEMENT_REFCOUNTING(LootApp); +}; } #endif diff --git a/src/gui/loot_handler.cpp b/src/gui/loot_handler.cpp index 44aab80b..2a32ad70 100644 --- a/src/gui/loot_handler.cpp +++ b/src/gui/loot_handler.cpp @@ -22,240 +22,225 @@ . */ -#include "loot_handler.h" -#include "query_handler.h" -#include "resource.h" -#include "loot_app.h" - -#include "../backend/error.h" -#include "../backend/app/loot_paths.h" -#include "../backend/helpers/helpers.h" -#include "../backend/helpers/json.h" - -#include -#include -#include -#include - -#include -#include -#include -#include +#include "gui/loot_handler.h" +#include #include #include -#include -using namespace std; +#include +#include +#include +#include +#include +#include -using boost::format; - -namespace fs = boost::filesystem; -namespace loc = boost::locale; +#include "backend/app/loot_paths.h" +#include "backend/helpers/helpers.h" +#include "gui/query_handler.h" +#include "gui/resource.h" namespace loot { - LootHandler::LootHandler(LootState& lootState) : _lootState(lootState) {} +LootHandler::LootHandler(LootState& lootState) : lootState_(lootState) {} - // CefClient methods - //------------------ +// CefClient methods +//------------------ - CefRefPtr LootHandler::GetDisplayHandler() { - return this; - } +CefRefPtr LootHandler::GetDisplayHandler() { + return this; +} - CefRefPtr LootHandler::GetLifeSpanHandler() { - return this; - } +CefRefPtr LootHandler::GetLifeSpanHandler() { + return this; +} - CefRefPtr LootHandler::GetLoadHandler() { - return this; - } +CefRefPtr LootHandler::GetLoadHandler() { + return this; +} - bool LootHandler::OnProcessMessageReceived(CefRefPtr browser, - CefProcessId source_process, - CefRefPtr message) { - return browser_side_router_->OnProcessMessageReceived(browser, source_process, message); - } +bool LootHandler::OnProcessMessageReceived(CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) { + return browser_side_router_->OnProcessMessageReceived(browser, source_process, message); +} - // CefLifeSpanHandler methods - //--------------------------- +// CefLifeSpanHandler methods +//--------------------------- - void LootHandler::OnAfterCreated(CefRefPtr browser) { - assert(CefCurrentlyOn(TID_UI)); +void LootHandler::OnAfterCreated(CefRefPtr browser) { + assert(CefCurrentlyOn(TID_UI)); #ifdef _WIN32 // Set the title bar icon. - HWND hWnd = browser->GetHost()->GetWindowHandle(); - HANDLE hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(MAINICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); - HANDLE hIconSm = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(MAINICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); - SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); - SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); + HWND hWnd = browser->GetHost()->GetWindowHandle(); + HANDLE hIcon = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(MAINICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); + HANDLE hIconSm = LoadImage(GetModuleHandle(NULL), MAKEINTRESOURCE(MAINICON), IMAGE_ICON, 0, 0, LR_DEFAULTSIZE); + SendMessage(hWnd, WM_SETICON, ICON_BIG, (LPARAM)hIcon); + SendMessage(hWnd, WM_SETICON, ICON_SMALL, (LPARAM)hIconSm); - // Set the window title. - SetWindowText(hWnd, L"LOOT"); + // Set the window title. + SetWindowText(hWnd, L"LOOT"); #endif // Set window size & position. - if (_lootState.isWindowPositionStored()) { + if (lootState_.isWindowPositionStored()) { #ifdef _WIN32 - RECT rc; - rc.left = _lootState.getWindowPosition().left; - rc.top = _lootState.getWindowPosition().top; - rc.right = _lootState.getWindowPosition().right; - rc.bottom = _lootState.getWindowPosition().bottom; + RECT rc; + rc.left = lootState_.getWindowPosition().left; + rc.top = lootState_.getWindowPosition().top; + rc.right = lootState_.getWindowPosition().right; + rc.bottom = lootState_.getWindowPosition().bottom; - // Fit the saved window size/position to the current monitor setup. + // Fit the saved window size/position to the current monitor setup. - // Get the nearest monitor to the saved size/pos. - HMONITOR hMonitor; - hMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); + // Get the nearest monitor to the saved size/pos. + HMONITOR hMonitor; + hMonitor = MonitorFromRect(&rc, MONITOR_DEFAULTTONEAREST); - // Get the rect for the monitor's working area. - MONITORINFO mi; - mi.cbSize = sizeof(mi); - GetMonitorInfo(hMonitor, &mi); + // Get the rect for the monitor's working area. + MONITORINFO mi; + mi.cbSize = sizeof(mi); + GetMonitorInfo(hMonitor, &mi); - // Clip the saved rect to fit inside the monitor rect. - int width = rc.right - rc.left; - int height = rc.bottom - rc.top; - rc.left = max(mi.rcWork.left, min(mi.rcWork.right - width, rc.left)); - rc.top = max(mi.rcWork.top, min(mi.rcWork.bottom - height, rc.top)); - rc.right = rc.left + width; - rc.bottom = rc.top + height; + // Clip the saved rect to fit inside the monitor rect. + int width = rc.right - rc.left; + int height = rc.bottom - rc.top; + rc.left = max(mi.rcWork.left, min(mi.rcWork.right - width, rc.left)); + rc.top = max(mi.rcWork.top, min(mi.rcWork.bottom - height, rc.top)); + rc.right = rc.left + width; + rc.bottom = rc.top + height; - SetWindowPos(hWnd, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); + SetWindowPos(hWnd, HWND_TOP, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_SHOWWINDOW); #endif - } - else { + } else { #ifdef _WIN32 // High DPI support doesn't seem to scale window content correctly // unless the window is resized, so if no size info is recorded, // just set its current size + 1. - RECT rc; - GetWindowRect(browser->GetHost()->GetWindowHandle(), &rc); - SetWindowPos(browser->GetHost()->GetWindowHandle(), HWND_TOP, rc.left, rc.top, rc.right - rc.left + 1, rc.bottom - rc.top + 1, SWP_SHOWWINDOW); + RECT rc; + GetWindowRect(browser->GetHost()->GetWindowHandle(), &rc); + SetWindowPos(browser->GetHost()->GetWindowHandle(), HWND_TOP, rc.left, rc.top, rc.right - rc.left + 1, rc.bottom - rc.top + 1, SWP_SHOWWINDOW); #endif - } + } - // Add to the list of existing browsers. - browser_list_.push_back(browser); + // Add to the list of existing browsers. + browser_list_.push_back(browser); - // Create a message router. - CefMessageRouterConfig config; - browser_side_router_ = CefMessageRouterBrowserSide::Create(config); + // Create a message router. + CefMessageRouterConfig config; + browser_side_router_ = CefMessageRouterBrowserSide::Create(config); - browser_side_router_->AddHandler(new QueryHandler(_lootState), false); - } + browser_side_router_->AddHandler(new QueryHandler(lootState_), false); +} - bool LootHandler::DoClose(CefRefPtr browser) { - assert(CefCurrentlyOn(TID_UI)); +bool LootHandler::DoClose(CefRefPtr browser) { + assert(CefCurrentlyOn(TID_UI)); - // Check if unapplied changes exist. - if (_lootState.hasUnappliedChanges()) { - browser->GetMainFrame()->ExecuteJavaScript("onQuit();", browser->GetMainFrame()->GetURL(), 0); - return true; - } + // Check if unapplied changes exist. + if (lootState_.hasUnappliedChanges()) { + browser->GetMainFrame()->ExecuteJavaScript("onQuit();", browser->GetMainFrame()->GetURL(), 0); + return true; + } - // Allow the close. For windowed browsers this will result in the OS close - // event being sent. - return false; - } + // Allow the close. For windowed browsers this will result in the OS close + // event being sent. + return false; +} - void LootHandler::OnBeforeClose(CefRefPtr browser) { - assert(CefCurrentlyOn(TID_UI)); +void LootHandler::OnBeforeClose(CefRefPtr browser) { + assert(CefCurrentlyOn(TID_UI)); #ifdef _WIN32 - RECT rc; - GetWindowRect(browser->GetHost()->GetWindowHandle(), &rc); + RECT rc; + GetWindowRect(browser->GetHost()->GetWindowHandle(), &rc); - LootSettings::WindowPosition position; - position.top = rc.top; - position.bottom = rc.bottom; - position.left = rc.left; - position.right = rc.right; - _lootState.storeWindowPosition(position); + LootSettings::WindowPosition position; + position.top = rc.top; + position.bottom = rc.bottom; + position.left = rc.left; + position.right = rc.right; + lootState_.storeWindowPosition(position); #endif - try { - _lootState.save(LootPaths::getSettingsPath()); - } - catch (std::exception &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to save LOOT's settings. Error: " << e.what(); - } + try { + lootState_.save(LootPaths::getSettingsPath()); + } catch (std::exception &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to save LOOT's settings. Error: " << e.what(); + } - // Cancel any javascript callbacks. - browser_side_router_->OnBeforeClose(browser); + // Cancel any javascript callbacks. + browser_side_router_->OnBeforeClose(browser); - // Remove from the list of existing browsers. - for (BrowserList::iterator bit = browser_list_.begin(); bit != browser_list_.end(); ++bit) { - if ((*bit)->IsSame(browser)) { - browser_list_.erase(bit); - break; - } - } - - if (browser_list_.empty()) { - // All browser windows have closed. Quit the application message loop. - CefQuitMessageLoop(); - } + // Remove from the list of existing browsers. + for (BrowserList::iterator bit = browser_list_.begin(); bit != browser_list_.end(); ++bit) { + if ((*bit)->IsSame(browser)) { + browser_list_.erase(bit); + break; } + } - // CefLoadHandler methods - //----------------------- - - void LootHandler::OnLoadError(CefRefPtr browser, - CefRefPtr frame, - ErrorCode errorCode, - const CefString& errorText, - const CefString& failedUrl) { - assert(CefCurrentlyOn(TID_UI)); - - // Don't display an error for downloaded files. - if (errorCode == ERR_ABORTED) - return; - - // Display a load error message. - std::stringstream ss; - ss << "" - << "

Failed to load URL " << std::string(failedUrl) - << " with error " << std::string(errorText) << " (" << errorCode - << ").

"; - - frame->LoadString(ss.str(), failedUrl); - } - - // CefRequestHandler methods - //-------------------------- - - CefRefPtr LootHandler::GetRequestHandler() { - return this; - } - - bool LootHandler::OnBeforeBrowse(CefRefPtr< CefBrowser > browser, - CefRefPtr< CefFrame > frame, - CefRefPtr< CefRequest > request, - bool is_redirect) { - BOOST_LOG_TRIVIAL(trace) << "Attempting to open link: " << request->GetURL().ToString(); - BOOST_LOG_TRIVIAL(trace) << "Comparing with URL: " << ToFileURL(LootPaths::getUIIndexPath()); - - if (boost::iequals(request->GetURL().ToString(), ToFileURL(LootPaths::getUIIndexPath()))) { - BOOST_LOG_TRIVIAL(trace) << "Link is to LOOT page, allowing CEF's default handling."; - return false; - } - - BOOST_LOG_TRIVIAL(info) << "Opening link in Windows' default handler."; - OpenInDefaultApplication(fs::path(request->GetURL().ToString())); - - return true; - } - - CefRequestHandler::ReturnValue LootHandler::OnBeforeResourceLoad(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr request, - CefRefPtr callback) { - if (boost::starts_with(request->GetURL().ToString(), "http")) - return RV_CANCEL; - - return RV_CONTINUE; - } + if (browser_list_.empty()) { + // All browser windows have closed. Quit the application message loop. + CefQuitMessageLoop(); + } +} + +// CefLoadHandler methods +//----------------------- + +void LootHandler::OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) { + assert(CefCurrentlyOn(TID_UI)); + + // Don't display an error for downloaded files. + if (errorCode == ERR_ABORTED) + return; + +// Display a load error message. + std::stringstream ss; + ss << "" + << "

Failed to load URL " << std::string(failedUrl) + << " with error " << std::string(errorText) << " (" << errorCode + << ").

"; + + frame->LoadString(ss.str(), failedUrl); +} + +// CefRequestHandler methods +//-------------------------- + +CefRefPtr LootHandler::GetRequestHandler() { + return this; +} + +bool LootHandler::OnBeforeBrowse(CefRefPtr< CefBrowser > browser, + CefRefPtr< CefFrame > frame, + CefRefPtr< CefRequest > request, + bool is_redirect) { + BOOST_LOG_TRIVIAL(trace) << "Attempting to open link: " << request->GetURL().ToString(); + BOOST_LOG_TRIVIAL(trace) << "Comparing with URL: " << ToFileURL(LootPaths::getUIIndexPath()); + + if (boost::iequals(request->GetURL().ToString(), ToFileURL(LootPaths::getUIIndexPath()))) { + BOOST_LOG_TRIVIAL(trace) << "Link is to LOOT page, allowing CEF's default handling."; + return false; + } + + BOOST_LOG_TRIVIAL(info) << "Opening link in Windows' default handler."; + OpenInDefaultApplication(boost::filesystem::path(request->GetURL().ToString())); + + return true; +} + +CefRequestHandler::ReturnValue LootHandler::OnBeforeResourceLoad(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr callback) { + if (boost::starts_with(request->GetURL().ToString(), "http")) + return RV_CANCEL; + + return RV_CONTINUE; +} } diff --git a/src/gui/loot_handler.h b/src/gui/loot_handler.h index 9cd9e25e..f07437e7 100644 --- a/src/gui/loot_handler.h +++ b/src/gui/loot_handler.h @@ -22,76 +22,76 @@ . */ -#ifndef __LOOT_GUI_LOOT_HANDLER__ -#define __LOOT_GUI_LOOT_HANDLER__ +#ifndef LOOT_GUI_LOOT_HANDLER +#define LOOT_GUI_LOOT_HANDLER -#include "backend/app/loot_state.h" +#include #include #include -#include +#include "backend/app/loot_state.h" namespace loot { - class LootHandler : public CefClient, - public CefDisplayHandler, - public CefLifeSpanHandler, - public CefLoadHandler, - public CefRequestHandler { - public: - LootHandler(LootState& lootState); +class LootHandler : public CefClient, + public CefDisplayHandler, + public CefLifeSpanHandler, + public CefLoadHandler, + public CefRequestHandler { +public: + LootHandler(LootState& lootState); - // CefClient methods - //------------------ - virtual CefRefPtr GetDisplayHandler() OVERRIDE; - virtual CefRefPtr GetLifeSpanHandler() OVERRIDE; - virtual CefRefPtr GetLoadHandler() OVERRIDE; + // CefClient methods + //------------------ + virtual CefRefPtr GetDisplayHandler() OVERRIDE; + virtual CefRefPtr GetLifeSpanHandler() OVERRIDE; + virtual CefRefPtr GetLoadHandler() OVERRIDE; - virtual bool OnProcessMessageReceived(CefRefPtr browser, - CefProcessId source_process, - CefRefPtr message) OVERRIDE; + virtual bool OnProcessMessageReceived(CefRefPtr browser, + CefProcessId source_process, + CefRefPtr message) OVERRIDE; - // CefLifeSpanHandler methods - //--------------------------- - virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE; - virtual bool DoClose(CefRefPtr browser) OVERRIDE; - virtual void OnBeforeClose(CefRefPtr browser) OVERRIDE; + // CefLifeSpanHandler methods + //--------------------------- + virtual void OnAfterCreated(CefRefPtr browser) OVERRIDE; + virtual bool DoClose(CefRefPtr browser) OVERRIDE; + virtual void OnBeforeClose(CefRefPtr browser) OVERRIDE; - // CefLoadHandler methods - //----------------------- - virtual void OnLoadError(CefRefPtr browser, - CefRefPtr frame, - ErrorCode errorCode, - const CefString& errorText, - const CefString& failedUrl) OVERRIDE; + // CefLoadHandler methods + //----------------------- + virtual void OnLoadError(CefRefPtr browser, + CefRefPtr frame, + ErrorCode errorCode, + const CefString& errorText, + const CefString& failedUrl) OVERRIDE; - // CefRequestHandler methods - //-------------------------- + // CefRequestHandler methods + //-------------------------- - virtual CefRefPtr GetRequestHandler() OVERRIDE; + virtual CefRefPtr GetRequestHandler() OVERRIDE; - virtual bool OnBeforeBrowse(CefRefPtr< CefBrowser > browser, - CefRefPtr< CefFrame > frame, - CefRefPtr< CefRequest > request, - bool is_redirect) OVERRIDE; + virtual bool OnBeforeBrowse(CefRefPtr< CefBrowser > browser, + CefRefPtr< CefFrame > frame, + CefRefPtr< CefRequest > request, + bool is_redirect) OVERRIDE; - virtual CefRequestHandler::ReturnValue OnBeforeResourceLoad(CefRefPtr browser, - CefRefPtr frame, - CefRefPtr request, - CefRefPtr callback) OVERRIDE; - + virtual CefRequestHandler::ReturnValue OnBeforeResourceLoad(CefRefPtr browser, + CefRefPtr frame, + CefRefPtr request, + CefRefPtr callback) OVERRIDE; - private: - // List of existing browser windows. Only accessed on the CEF UI thread. - typedef std::list > BrowserList; +private: + typedef std::list> BrowserList; - BrowserList browser_list_; - CefRefPtr browser_side_router_; - LootState& _lootState; + // List of existing browser windows. Only accessed on the CEF UI thread. + BrowserList browser_list_; + CefRefPtr browser_side_router_; - // Include the default reference counting implementation. - IMPLEMENT_REFCOUNTING(LootHandler); - }; + LootState& lootState_; + + // Include the default reference counting implementation. + IMPLEMENT_REFCOUNTING(LootHandler); +}; } #endif diff --git a/src/gui/loot_scheme_handler_factory.cpp b/src/gui/loot_scheme_handler_factory.cpp index 60edeeaf..2764500d 100644 --- a/src/gui/loot_scheme_handler_factory.cpp +++ b/src/gui/loot_scheme_handler_factory.cpp @@ -39,32 +39,31 @@ namespace loot { // LootSchemeHandlerFactory /////////////////////////////// - CefRefPtr LootSchemeHandlerFactory::Create(CefRefPtr browser, - CefRefPtr frame, - const CefString& scheme_name, - CefRefPtr request) { - BOOST_LOG_TRIVIAL(trace) << "Handling custom scheme: " << string(request->GetURL()); +CefRefPtr LootSchemeHandlerFactory::Create(CefRefPtr browser, + CefRefPtr frame, + const CefString& scheme_name, + CefRefPtr request) { + BOOST_LOG_TRIVIAL(trace) << "Handling custom scheme: " << string(request->GetURL()); - // Get the path from the custom URL, which is of the form - // loot://l10n/ - string file = (LootPaths::getL10nPath() / request->GetURL().ToString().substr(12)).string(); + // Get the path from the custom URL, which is of the form + // loot://l10n/ + string file = (LootPaths::getL10nPath() / request->GetURL().ToString().substr(12)).string(); - CefResponse::HeaderMap headers; - headers.emplace("Access-Control-Allow-Origin", "*"); + CefResponse::HeaderMap headers; + headers.emplace("Access-Control-Allow-Origin", "*"); - if (boost::filesystem::exists(file)) { - // Load the file into a CEF stream. - CefRefPtr stream = CefStreamReader::CreateForFile(file); - BOOST_LOG_TRIVIAL(trace) << "Loaded file: " << file; + if (boost::filesystem::exists(file)) { + // Load the file into a CEF stream. + CefRefPtr stream = CefStreamReader::CreateForFile(file); + BOOST_LOG_TRIVIAL(trace) << "Loaded file: " << file; - return new CefStreamResourceHandler(200, "OK", "application/octet-stream", headers, stream); - } - else { - BOOST_LOG_TRIVIAL(trace) << "File " << file << " not found, sending 404."; + return new CefStreamResourceHandler(200, "OK", "application/octet-stream", headers, stream); + } else { + BOOST_LOG_TRIVIAL(trace) << "File " << file << " not found, sending 404."; - const string error404 = "File not found."; - CefRefPtr stream = CefStreamReader::CreateForData((void*)error404.c_str(), error404.size()); - return new CefStreamResourceHandler(404, "Not Found", "application/octet-stream", headers, stream); - } - } + const string error404 = "File not found."; + CefRefPtr stream = CefStreamReader::CreateForData((void*)error404.c_str(), error404.size()); + return new CefStreamResourceHandler(404, "Not Found", "application/octet-stream", headers, stream); + } +} } diff --git a/src/gui/loot_scheme_handler_factory.h b/src/gui/loot_scheme_handler_factory.h index bc3ba1ff..29f2325a 100644 --- a/src/gui/loot_scheme_handler_factory.h +++ b/src/gui/loot_scheme_handler_factory.h @@ -29,16 +29,16 @@ along with LOOT. If not, see #include namespace loot { - class LootSchemeHandlerFactory : public CefSchemeHandlerFactory { - public: - virtual CefRefPtr Create(CefRefPtr browser, - CefRefPtr frame, - const CefString& scheme_name, - CefRefPtr request) - OVERRIDE; +class LootSchemeHandlerFactory : public CefSchemeHandlerFactory { +public: + virtual CefRefPtr Create(CefRefPtr browser, + CefRefPtr frame, + const CefString& scheme_name, + CefRefPtr request) + OVERRIDE; - IMPLEMENT_REFCOUNTING(LootSchemeHandlerFactory); - }; + IMPLEMENT_REFCOUNTING(LootSchemeHandlerFactory); +}; } #endif diff --git a/src/gui/main.cpp b/src/gui/main.cpp index 89b9f711..5dbb6899 100644 --- a/src/gui/main.cpp +++ b/src/gui/main.cpp @@ -22,64 +22,57 @@ . */ -#include "loot_app.h" -#include "backend/app/loot_paths.h" - -#ifdef _WIN32 -#include -#include -#else -#include -#include "include/base/cef_logging.h" -#endif - #include #include #include -#include #include -#include -#include #include +#include +#include +#include -namespace fs = boost::filesystem; +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif -using namespace std; -using namespace loot; -using boost::locale::translate; -using boost::format; +#include "backend/app/loot_paths.h" +#include "gui/loot_app.h" CefSettings GetCefSettings() { - CefSettings cef_settings; + CefSettings cef_settings; - //Enable CEF command line args. - cef_settings.command_line_args_disabled = false; + //Enable CEF command line args. + cef_settings.command_line_args_disabled = false; - // Set CEF logging. - CefString(&cef_settings.log_file).FromString((LootPaths::getLootDataPath() / "CEFDebugLog.txt").string()); + // Set CEF logging. + CefString(&cef_settings.log_file).FromString((loot::LootPaths::getLootDataPath() / "CEFDebugLog.txt").string()); - // Load locale pack files from LOOT's l10n path. - CefString(&cef_settings.locales_dir_path).FromString(LootPaths::getL10nPath().string()); + // Load locale pack files from LOOT's l10n path. + CefString(&cef_settings.locales_dir_path).FromString(loot::LootPaths::getL10nPath().string()); - return cef_settings; + return cef_settings; } #ifndef _WIN32 namespace { - int XErrorHandlerImpl(Display *display, XErrorEvent *event) { - LOG(WARNING) - << "X error received: " - << "type " << event->type << ", " - << "serial " << event->serial << ", " - << "error_code " << static_cast(event->error_code) << ", " - << "request_code " << static_cast(event->request_code) << ", " - << "minor_code " << static_cast(event->minor_code); - return 0; - } +int XErrorHandlerImpl(Display *display, XErrorEvent *event) { + LOG(WARNING) + << "X error received: " + << "type " << event->type << ", " + << "serial " << event->serial << ", " + << "error_code " << static_cast(event->error_code) << ", " + << "request_code " << static_cast(event->request_code) << ", " + << "minor_code " << static_cast(event->minor_code); + return 0; +} - int XIOErrorHandlerImpl(Display *display) { - return 0; - } +int XIOErrorHandlerImpl(Display *display) { + return 0; +} } #endif @@ -92,89 +85,88 @@ int main(int argc, char* argv[]) { // Do all the standard CEF setup stuff. //------------------------------------- - void * sandbox_info = nullptr; + void * sandbox_info = nullptr; #ifdef _WIN32 // Enable High-DPI support on Windows 7 or newer. - CefEnableHighDPISupport(); + CefEnableHighDPISupport(); - // Read command line arguments. - CefMainArgs main_args(hInstance); + // Read command line arguments. + CefMainArgs main_args(hInstance); #else // Read command line arguments. - CefMainArgs main_args(argc, argv); + CefMainArgs main_args(argc, argv); #endif // Create the process reference. - CefRefPtr app(new loot::LootApp); + CefRefPtr app(new loot::LootApp); - // Run the process. - int exit_code = CefExecuteProcess(main_args, app.get(), nullptr); - if (exit_code >= 0) { - // The sub-process has completed so return here. - return exit_code; - } + // Run the process. + int exit_code = CefExecuteProcess(main_args, app.get(), nullptr); + if (exit_code >= 0) { + // The sub-process has completed so return here. + return exit_code; + } #ifdef _WIN32 // Check if LOOT is already running //--------------------------------- - HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, L"LOOT.Shell.Instance"); - if (hMutex != NULL) { - // An instance of LOOT is already running, so focus its window then quit. - HWND hWnd = ::FindWindow(NULL, L"LOOT"); - ::SetForegroundWindow(hWnd); - return 0; - } - else { - //Create the mutex so that future instances will not run. - hMutex = ::CreateMutex(NULL, FALSE, L"LOOT.Shell.Instance"); - } + HANDLE hMutex = ::OpenMutex(MUTEX_ALL_ACCESS, FALSE, L"LOOT.Shell.Instance"); + if (hMutex != NULL) { + // An instance of LOOT is already running, so focus its window then quit. + HWND hWnd = ::FindWindow(NULL, L"LOOT"); + ::SetForegroundWindow(hWnd); + return 0; + } else { + //Create the mutex so that future instances will not run. + hMutex = ::CreateMutex(NULL, FALSE, L"LOOT.Shell.Instance"); + } #endif // Handle command line args (not CEF args) //---------------------------------------- - string gameStr; + std::string gameStr; - // Record command line arguments. - CefRefPtr command_line = CefCommandLine::CreateCommandLine(); + // Record command line arguments. + CefRefPtr command_line = CefCommandLine::CreateCommandLine(); #ifdef _WIN32 - command_line->InitFromString(::GetCommandLineW()); + command_line->InitFromString(::GetCommandLineW()); #endif - if (command_line->HasSwitch("game")) { // Format is: --game= - gameStr = command_line->GetSwitchValue("game"); - } + if (command_line->HasSwitch("game")) { // Format is: --game= + gameStr = command_line->GetSwitchValue("game"); + } - app.get()->Initialise(gameStr); + app.get()->Initialise(gameStr); - // Back to CEF - //------------ + // Back to CEF + //------------ - // Initialise CEF settings. - CefSettings cef_settings = GetCefSettings(); + // Initialise CEF settings. + CefSettings cef_settings = GetCefSettings(); #ifndef _WIN32 // Install xlib error handlers so that the application won't be terminated // on non-fatal errors. - XSetErrorHandler(XErrorHandlerImpl); - XSetIOErrorHandler(XIOErrorHandlerImpl); + XSetErrorHandler(XErrorHandlerImpl); + XSetIOErrorHandler(XIOErrorHandlerImpl); #endif // Initialize CEF. - CefInitialize(main_args, cef_settings, app.get(), sandbox_info); + CefInitialize(main_args, cef_settings, app.get(), sandbox_info); - // Run the CEF message loop. This will block until CefQuitMessageLoop() is called. - CefRunMessageLoop(); + // Run the CEF message loop. This will block until CefQuitMessageLoop() is called. + CefRunMessageLoop(); - // Shut down CEF. - CefShutdown(); + // Shut down CEF. + CefShutdown(); #ifdef _WIN32 // Release the program instance mutex. - if (hMutex != NULL) - ReleaseMutex(hMutex); + if (hMutex != NULL) + ReleaseMutex(hMutex); #endif - return 0; + return 0; } diff --git a/src/gui/query_handler.cpp b/src/gui/query_handler.cpp index 1a1850ac..c7d437ea 100644 --- a/src/gui/query_handler.cpp +++ b/src/gui/query_handler.cpp @@ -23,1068 +23,999 @@ */ #include "query_handler.h" -#include "resource.h" -#include "loot_app.h" -#include "loot_handler.h" - -#include "../backend/error.h" -#include "../backend/app/loot_paths.h" -#include "../backend/app/loot_version.h" -#include "../backend/plugin/plugin_sorter.h" -#include "../backend/helpers/helpers.h" -#include "../backend/helpers/json.h" -#include "../backend/helpers/version.h" - -#include -#include -#include -#include - -#include -#include -#include -#include #include #include #include -using namespace std; +#include +#include +#include +#include +#include +#include +#include +#include +#include "gui/loot_app.h" +#include "gui/loot_handler.h" +#include "gui/resource.h" + +#include "backend/error.h" +#include "backend/app/loot_paths.h" +#include "backend/app/loot_version.h" +#include "backend/plugin/plugin_sorter.h" +#include "backend/helpers/helpers.h" +#include "backend/helpers/json.h" +#include "backend/helpers/version.h" + +using boost::filesystem::exists; using boost::format; - -namespace fs = boost::filesystem; -namespace loc = boost::locale; +using boost::locale::translate; +using std::exception; +using std::list; +using std::set; +using std::string; +using std::vector; namespace loot { - QueryHandler::QueryHandler(LootState& lootState) : _lootState(lootState) {} +QueryHandler::QueryHandler(LootState& lootState) : lootState_(lootState) {} - // Called due to cefQuery execution in binding.html. - bool QueryHandler::OnQuery(CefRefPtr browser, - CefRefPtr frame, - int64 query_id, - const CefString& request, - bool persistent, - CefRefPtr callback) { - if (request == "openReadme") { - try { - OpenReadme(); - callback->Success(""); - } - catch (Error &e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - callback->Failure(e.codeAsUnsignedInt(), e.what()); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - callback->Failure(-1, e.what()); - } - return true; - } - else if (request == "openLogLocation") { - try { - OpenLogLocation(); - callback->Success(""); - } - catch (Error &e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - callback->Failure(e.codeAsUnsignedInt(), e.what()); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - callback->Failure(-1, e.what()); - } - return true; - } - else if (request == "getVersion") { - callback->Success(GetVersion()); - return true; - } - else if (request == "getSettings") { - callback->Success(GetSettings()); - return true; - } - else if (request == "getLanguages") { - callback->Success(GetLanguages()); - return true; - } - else if (request == "getGameTypes") { - callback->Success(GetGameTypes()); - return true; - } - else if (request == "getInstalledGames") { - callback->Success(GetInstalledGames()); - return true; - } - else if (request == "getGameData") { - SendProgressUpdate(frame, loc::translate("Parsing, merging and evaluating metadata...")); - return CefPostTask(TID_FILE, base::Bind(&QueryHandler::GetGameData, base::Unretained(this), frame, callback)); - } - else if (request == "cancelFind") { - browser->GetHost()->StopFinding(true); - callback->Success(""); - return true; - } - else if (request == "clearAllMetadata") { - callback->Success(ClearAllMetadata()); - return true; - } - else if (request == "redatePlugins") { - BOOST_LOG_TRIVIAL(debug) << "Redating plugins."; - try { - _lootState.CurrentGame().RedatePlugins(); - callback->Success(""); - } - catch (Error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to redate plugins. " << e.what(); - callback->Failure(e.codeAsUnsignedInt(), e.what()); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to redate plugins. " << e.what(); - callback->Failure(-1, e.what()); - } - - return true; - } - else if (request == "updateMasterlist") { - return CefPostTask(TID_FILE, base::Bind(&QueryHandler::UpdateMasterlist, base::Unretained(this), callback)); - } - else if (request == "sortPlugins") { - return CefPostTask(TID_FILE, base::Bind(&QueryHandler::SortPlugins, base::Unretained(this), frame, callback)); - } - else if (request == "getInitErrors") { - YAML::Node node(_lootState.InitErrors()); - if (node.size() > 0) - callback->Success(JSON::stringify(node)); - else - callback->Success("null"); - return true; - } - else if (request == "cancelSort") { - _lootState.decrementUnappliedChangeCounter(); - _lootState.CurrentGame().SetLoadOrderSorted(false); - - YAML::Node node(GetGeneralMessages()); - callback->Success(JSON::stringify(node)); - return true; - } - else if (request == "editorOpened") { - _lootState.incrementUnappliedChangeCounter(); - callback->Success(""); - return true; - } - else if (request == "editorClosed") { - // This version of the editorClosed query has no arguments as it is - // sent when editing is cancelled. Just update the unapplied changes - // counter. - _lootState.decrementUnappliedChangeCounter(); - callback->Success(""); - return true; - } - else if (request == "discardUnappliedChanges") { - while (_lootState.hasUnappliedChanges()) - _lootState.decrementUnappliedChangeCounter(); - callback->Success(""); - return true; - } - else { - // May be a request with arguments. - YAML::Node req; - try { - // Can't pass this as a reference directly as GCC - // complains about it. - std::string requestString = request.ToString(); - req = JSON::parse(requestString); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to parse CEF query request \"" << request.ToString() << "\": " << e.what(); - callback->Failure(-1, e.what()); - return true; - } - - return HandleComplexQuery(browser, frame, req, callback); - } - - return false; +// Called due to cefQuery execution in binding.html. +bool QueryHandler::OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) { + if (request == "openReadme") { + try { + OpenReadme(); + callback->Success(""); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + callback->Failure(e.codeAsUnsignedInt(), e.what()); + } catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + callback->Failure(-1, e.what()); + } + return true; + } else if (request == "openLogLocation") { + try { + OpenLogLocation(); + callback->Success(""); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + callback->Failure(e.codeAsUnsignedInt(), e.what()); + } catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + callback->Failure(-1, e.what()); + } + return true; + } else if (request == "getVersion") { + callback->Success(GetVersion()); + return true; + } else if (request == "getSettings") { + callback->Success(GetSettings()); + return true; + } else if (request == "getLanguages") { + callback->Success(GetLanguages()); + return true; + } else if (request == "getGameTypes") { + callback->Success(GetGameTypes()); + return true; + } else if (request == "getInstalledGames") { + callback->Success(GetInstalledGames()); + return true; + } else if (request == "getGameData") { + SendProgressUpdate(frame, translate("Parsing, merging and evaluating metadata...")); + return CefPostTask(TID_FILE, base::Bind(&QueryHandler::GetGameData, base::Unretained(this), frame, callback)); + } else if (request == "cancelFind") { + browser->GetHost()->StopFinding(true); + callback->Success(""); + return true; + } else if (request == "clearAllMetadata") { + callback->Success(ClearAllMetadata()); + return true; + } else if (request == "redatePlugins") { + BOOST_LOG_TRIVIAL(debug) << "Redating plugins."; + try { + lootState_.getCurrentGame().RedatePlugins(); + callback->Success(""); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to redate plugins. " << e.what(); + callback->Failure(e.codeAsUnsignedInt(), e.what()); + } catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to redate plugins. " << e.what(); + callback->Failure(-1, e.what()); } - // Handle queries with input arguments. - bool QueryHandler::HandleComplexQuery(CefRefPtr browser, - CefRefPtr frame, - YAML::Node& request, - CefRefPtr callback) { - const string requestName = request["name"].as(); + return true; + } else if (request == "updateMasterlist") { + return CefPostTask(TID_FILE, base::Bind(&QueryHandler::UpdateMasterlist, base::Unretained(this), callback)); + } else if (request == "sortPlugins") { + return CefPostTask(TID_FILE, base::Bind(&QueryHandler::SortPlugins, base::Unretained(this), frame, callback)); + } else if (request == "getInitErrors") { + YAML::Node node(lootState_.getInitErrors()); + if (node.size() > 0) + callback->Success(JSON::stringify(node)); + else + callback->Success("null"); + return true; + } else if (request == "cancelSort") { + lootState_.decrementUnappliedChangeCounter(); + lootState_.getCurrentGame().SetLoadOrderSorted(false); - if (requestName == "changeGame") { - try { - // Has one arg, which is the folder name of the new game. - _lootState.ChangeGame(request["args"][0].as()); - - CefPostTask(TID_FILE, base::Bind(&QueryHandler::GetGameData, base::Unretained(this), frame, callback)); - } - catch (loot::Error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to change game. Details: " << e.what(); - callback->Failure(e.codeAsUnsignedInt(), (boost::format(loc::translate("Failed to change game. Details: %1%")) % e.what()).str()); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Failed to change game. Details: " << e.what(); - callback->Failure(-1, (boost::format(loc::translate("Failed to change game. Details: %1%")) % e.what()).str()); - } - return true; - } - else if (requestName == "getConflictingPlugins") { - // Has one arg, which is the name of the plugin to get conflicts for. - CefPostTask(TID_FILE, base::Bind(&QueryHandler::GetConflictingPlugins, base::Unretained(this), request["args"][0].as(), callback)); - return true; - } - else if (requestName == "copyMetadata") { - // Has one arg, which is the name of the plugin to copy metadata for. - try { - CopyMetadata(request["args"][0].as()); - callback->Success(""); - } - catch (loot::Error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); - callback->Failure(e.codeAsUnsignedInt(), (boost::format(loc::translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); - callback->Failure(-1, (boost::format(loc::translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); - } - return true; - } - else if (requestName == "clearPluginMetadata") { - // Has one arg, which is the name of the plugin to copy metadata for. - callback->Success(ClearPluginMetadata(request["args"][0].as())); - return true; - } - else if (requestName == "editorClosed") { - BOOST_LOG_TRIVIAL(debug) << "Editor for plugin closed."; - // One argument, which is the plugin metadata that has changed (+ its name). - try { - callback->Success(ApplyUserEdits(request["args"][0])); - _lootState.decrementUnappliedChangeCounter(); - } - catch (loot::Error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to apply plugin metadata. Details: " << e.what(); - callback->Failure(e.codeAsUnsignedInt(), (boost::format(loc::translate("Failed to apply plugin metadata. Details: %1%")) % e.what()).str()); - } - catch (std::exception& e) { - // If this was a YAML conversion error, cut off the line and column numbers, - // since the YAML wasn't written to a file. - string error = e.what(); - size_t pos = string::npos; - if ((pos = error.find("bad conversion")) != string::npos) { - error = error.substr(pos); - } - BOOST_LOG_TRIVIAL(error) << "Failed to apply plugin metadata. Details: " << e.what(); - callback->Failure(-1, (boost::format(loc::translate("Failed to apply plugin metadata. Details: %1%")) % error).str()); - } - return true; - } - else if (requestName == "closeSettings") { - BOOST_LOG_TRIVIAL(trace) << "Settings dialog closed and changes accepted, updating settings object."; - - try { - // Update the settings. - // If the user has deleted a default game, we don't want to restore it now. - // It will be restored when LOOT is next loaded. - YAML::Node settings = request["args"][0]; - _lootState.load(settings); - - // Now send back the new list of installed games to the UI. - BOOST_LOG_TRIVIAL(trace) << "Getting new list of installed games."; - callback->Success(GetInstalledGames()); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - callback->Failure(-1, e.what()); - } - return true; - } - else if (requestName == "applySort") { - _lootState.decrementUnappliedChangeCounter(); - BOOST_LOG_TRIVIAL(trace) << "User has accepted sorted load order, applying it."; - try { - _lootState.CurrentGame().SetLoadOrder(request["args"][0].as>()); - callback->Success(""); - } - catch (Error &e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - callback->Failure(e.codeAsUnsignedInt(), e.what()); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - callback->Failure(-1, e.what()); - } - - return true; - } - else if (requestName == "copyContent") { - // Has one arg, just convert it to a YAML output string. - try { - YAML::Emitter yout; - yout.SetIndent(2); - yout << request["args"][0]; - string text = yout.c_str(); - // Get rid of yaml-cpp weirdness. - boost::replace_all(text, "! ", ""); - text = "[spoiler][code]" + text + "[/code][/spoiler]"; - CopyToClipboard(text); - callback->Success(""); - } - catch (loot::Error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); - callback->Failure(e.codeAsUnsignedInt(), (boost::format(loc::translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); - callback->Failure(-1, (boost::format(loc::translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); - } - return true; - } - else if (requestName == "copyLoadOrder") { - // Has one arg, an array of plugins in load order. Output them with indices in dec and hex. - try { - stringstream ss; - vector plugins = request["args"][0].as>(); - int decLength = 1; - if (plugins.size() > 99) { - decLength = 3; - } - else if (plugins.size() > 9) { - decLength = 2; - } - size_t i = 0; - for (const auto& pluginName : plugins) { - if (_lootState.CurrentGame().IsPluginActive(pluginName)) { - ss << setw(decLength) << i << " " << hex << setw(2) << i << dec << " "; - ++i; - } - else { - ss << setw(decLength + 4) << " "; - } - ss << pluginName << "\r\n"; - } - CopyToClipboard(ss.str()); - callback->Success(""); - } - catch (loot::Error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); - callback->Failure(e.codeAsUnsignedInt(), (boost::format(loc::translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); - callback->Failure(-1, (boost::format(loc::translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); - } - return true; - } - else if (requestName == "saveFilterState") { - // Has two args: the first is the filter ID, the second is the value. - BOOST_LOG_TRIVIAL(trace) << "Saving filter states."; - try { - _lootState.storeFilterState(request["args"][0].as(), request["args"][1].as()); - callback->Success(""); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << e.what(); - callback->Failure(-1, e.what()); - } - return true; - } - return false; + YAML::Node node(GetGeneralMessages()); + callback->Success(JSON::stringify(node)); + return true; + } else if (request == "editorOpened") { + lootState_.incrementUnappliedChangeCounter(); + callback->Success(""); + return true; + } else if (request == "editorClosed") { + // This version of the editorClosed query has no arguments as it is + // sent when editing is cancelled. Just update the unapplied changes + // counter. + lootState_.decrementUnappliedChangeCounter(); + callback->Success(""); + return true; + } else if (request == "discardUnappliedChanges") { + while (lootState_.hasUnappliedChanges()) + lootState_.decrementUnappliedChangeCounter(); + callback->Success(""); + return true; + } else { + // May be a request with arguments. + YAML::Node req; + try { + // Can't pass this as a reference directly as GCC + // complains about it. + std::string requestString = request.ToString(); + req = JSON::parse(requestString); + } catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to parse CEF query request \"" << request.ToString() << "\": " << e.what(); + callback->Failure(-1, e.what()); + return true; } - void QueryHandler::GetConflictingPlugins(const std::string& pluginName, CefRefPtr callback) { - BOOST_LOG_TRIVIAL(debug) << "Searching for plugins that conflict with " << pluginName; + return HandleComplexQuery(browser, frame, req, callback); + } - // Checking for FormID overlap will only work if the plugins have been loaded, so check if - // the plugins have been fully loaded, and if not load all plugins. - if (!_lootState.CurrentGame().ArePluginsFullyLoaded()) - _lootState.CurrentGame().LoadPlugins(false); - - YAML::Node node; - auto plugin = _lootState.CurrentGame().GetPlugin(pluginName); - for (const auto& otherPlugin : _lootState.CurrentGame().GetPlugins()) { - // Plugin loading may have produced an error message, so rederive - // displayed data. - - YAML::Node pluginNode = GenerateDerivedMetadata(otherPlugin.Name()); - - pluginNode["name"] = otherPlugin.Name(); - pluginNode["crc"] = otherPlugin.Crc(); - pluginNode["isEmpty"] = otherPlugin.IsEmpty(); - if (plugin.DoFormIDsOverlap(otherPlugin)) { - BOOST_LOG_TRIVIAL(debug) << "Found conflicting plugin: " << otherPlugin.Name(); - pluginNode["conflicts"] = true; - } - else { - pluginNode["conflicts"] = false; - } - - node.push_back(pluginNode); - } - - if (node.size() > 0) - callback->Success(JSON::stringify(node)); - else - callback->Success("[]"); + return false; +} + +// Handle queries with input arguments. +bool QueryHandler::HandleComplexQuery(CefRefPtr browser, + CefRefPtr frame, + YAML::Node& request, + CefRefPtr callback) { + const string requestName = request["name"].as(); + + if (requestName == "changeGame") { + try { + // Has one arg, which is the folder name of the new game. + lootState_.changeGame(request["args"][0].as()); + + CefPostTask(TID_FILE, base::Bind(&QueryHandler::GetGameData, base::Unretained(this), frame, callback)); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to change game. Details: " << e.what(); + callback->Failure(e.codeAsUnsignedInt(), (boost::format(translate("Failed to change game. Details: %1%")) % e.what()).str()); + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to change game. Details: " << e.what(); + callback->Failure(-1, (boost::format(translate("Failed to change game. Details: %1%")) % e.what()).str()); } - - void QueryHandler::CopyMetadata(const std::string& pluginName) { - BOOST_LOG_TRIVIAL(debug) << "Copying metadata for plugin " << pluginName; - - // Get metadata from masterlist and userlist. - PluginMetadata plugin = _lootState.CurrentGame().GetMasterlist().FindPlugin(pluginName); - plugin.MergeMetadata(_lootState.CurrentGame().GetUserlist().FindPlugin(pluginName)); - - // Generate text representation. - string text; - YAML::Emitter yout; - yout.SetIndent(2); - yout << plugin; - text = yout.c_str(); - // Get rid of yaml-cpp weirdness. - boost::replace_all(text, "! ", ""); - text = "[spoiler][code]" + text + "[/code][/spoiler]"; - - CopyToClipboard(text); - - BOOST_LOG_TRIVIAL(info) << "Exported userlist metadata text for \"" << pluginName << "\": " << text; + return true; + } else if (requestName == "getConflictingPlugins") { + // Has one arg, which is the name of the plugin to get conflicts for. + CefPostTask(TID_FILE, base::Bind(&QueryHandler::GetConflictingPlugins, base::Unretained(this), request["args"][0].as(), callback)); + return true; + } else if (requestName == "copyMetadata") { + // Has one arg, which is the name of the plugin to copy metadata for. + try { + CopyMetadata(request["args"][0].as()); + callback->Success(""); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); + callback->Failure(e.codeAsUnsignedInt(), (boost::format(translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); + callback->Failure(-1, (boost::format(translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); } - - std::string QueryHandler::ClearPluginMetadata(const std::string& pluginName) { - BOOST_LOG_TRIVIAL(debug) << "Clearing user metadata for plugin " << pluginName; - - _lootState.CurrentGame().GetUserlist().ErasePlugin(PluginMetadata(pluginName)); - - // Save userlist edits. - _lootState.CurrentGame().GetUserlist().Save(_lootState.CurrentGame().UserlistPath()); - - // Now rederive the displayed metadata from the masterlist. - YAML::Node derivedMetadata = GenerateDerivedMetadata(pluginName); - if (derivedMetadata.size() > 0) - return JSON::stringify(derivedMetadata); - else - return "null"; + return true; + } else if (requestName == "clearPluginMetadata") { + // Has one arg, which is the name of the plugin to copy metadata for. + callback->Success(ClearPluginMetadata(request["args"][0].as())); + return true; + } else if (requestName == "editorClosed") { + BOOST_LOG_TRIVIAL(debug) << "Editor for plugin closed."; + // One argument, which is the plugin metadata that has changed (+ its name). + try { + callback->Success(ApplyUserEdits(request["args"][0])); + lootState_.decrementUnappliedChangeCounter(); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to apply plugin metadata. Details: " << e.what(); + callback->Failure(e.codeAsUnsignedInt(), (boost::format(translate("Failed to apply plugin metadata. Details: %1%")) % e.what()).str()); + } catch (std::exception& e) { + // If this was a YAML conversion error, cut off the line and column numbers, + // since the YAML wasn't written to a file. + string error = e.what(); + size_t pos = string::npos; + if ((pos = error.find("bad conversion")) != string::npos) { + error = error.substr(pos); + } + BOOST_LOG_TRIVIAL(error) << "Failed to apply plugin metadata. Details: " << e.what(); + callback->Failure(-1, (boost::format(translate("Failed to apply plugin metadata. Details: %1%")) % error).str()); } - - std::string QueryHandler::ApplyUserEdits(const YAML::Node& pluginMetadata) { - BOOST_LOG_TRIVIAL(trace) << "Applying user edits for: " << pluginMetadata["name"].as(); - // Create new object for userlist entry. - PluginMetadata newUserlistEntry(pluginMetadata["name"].as()); - - // Find existing userlist entry. - PluginMetadata ulistPlugin = _lootState.CurrentGame().GetUserlist().FindPlugin(newUserlistEntry); - - // First sort out the priority value. This is only given if it was changed. - BOOST_LOG_TRIVIAL(trace) << "Calculating userlist metadata priority value from Javascript variables."; - if (pluginMetadata["priority"] && pluginMetadata["isPriorityGlobal"]) { - BOOST_LOG_TRIVIAL(trace) << "Priority value was changed, recalculating..."; - // Priority value was changed, so add it to the userlist data. - newUserlistEntry.Priority(pluginMetadata["priority"].as()); - newUserlistEntry.SetPriorityExplicit(true); - newUserlistEntry.SetPriorityGlobal(pluginMetadata["isPriorityGlobal"].as()); - } - else { - // Priority value wasn't changed, use the existing userlist value. - BOOST_LOG_TRIVIAL(trace) << "Priority value is unchanged, using existing userlist value (if it exists)."; - if (!ulistPlugin.HasNameOnly()) { - newUserlistEntry.Priority(ulistPlugin.Priority()); - newUserlistEntry.SetPriorityExplicit(ulistPlugin.IsPriorityExplicit()); - newUserlistEntry.SetPriorityGlobal(ulistPlugin.IsPriorityGlobal()); - } - } - - // Now the enabled flag. - newUserlistEntry.Enabled(pluginMetadata["userlist"]["enabled"].as()); - - // Now metadata lists. These are given in their entirety, so replace anything that - // currently exists. - BOOST_LOG_TRIVIAL(trace) << "Recording metadata lists from Javascript variables."; - if (pluginMetadata["userlist"]["after"]) - newUserlistEntry.LoadAfter(pluginMetadata["userlist"]["after"].as>()); - if (pluginMetadata["userlist"]["req"]) - newUserlistEntry.Reqs(pluginMetadata["userlist"]["req"].as>()); - if (pluginMetadata["userlist"]["inc"]) - newUserlistEntry.Incs(pluginMetadata["userlist"]["inc"].as>()); - - if (pluginMetadata["userlist"]["msg"]) - newUserlistEntry.Messages(pluginMetadata["userlist"]["msg"].as>()); - if (pluginMetadata["userlist"]["tag"]) - newUserlistEntry.Tags(pluginMetadata["userlist"]["tag"].as>()); - if (pluginMetadata["userlist"]["dirty"]) - newUserlistEntry.DirtyInfo(pluginMetadata["userlist"]["dirty"].as>()); - if (pluginMetadata["userlist"]["url"]) - newUserlistEntry.Locations(pluginMetadata["userlist"]["url"].as>()); - - // For cleanliness, only data that does not duplicate masterlist and plugin data should be retained, so diff that. - BOOST_LOG_TRIVIAL(trace) << "Removing any user metadata that duplicates masterlist metadata."; - try { - Plugin tempPlugin(_lootState.CurrentGame().GetPlugin(newUserlistEntry.Name())); - tempPlugin.MergeMetadata(_lootState.CurrentGame().GetMasterlist().FindPlugin(newUserlistEntry)); - newUserlistEntry = newUserlistEntry.NewMetadata(tempPlugin); - } - catch (...) { - newUserlistEntry = newUserlistEntry.NewMetadata(_lootState.CurrentGame().GetMasterlist().FindPlugin(newUserlistEntry)); - } - - // Now erase any existing userlist entry. - if (!ulistPlugin.HasNameOnly()) { - BOOST_LOG_TRIVIAL(trace) << "Erasing the existing userlist entry."; - _lootState.CurrentGame().GetUserlist().ErasePlugin(ulistPlugin); - } - // Add a new userlist entry if necessary. - if (!newUserlistEntry.HasNameOnly()) { - BOOST_LOG_TRIVIAL(trace) << "Adding new metadata to new userlist entry."; - _lootState.CurrentGame().GetUserlist().AddPlugin(newUserlistEntry); - } - - // Save edited userlist. - _lootState.CurrentGame().GetUserlist().Save(_lootState.CurrentGame().UserlistPath()); - - // Now rederive the derived metadata. - BOOST_LOG_TRIVIAL(trace) << "Returning newly derived display metadata."; - YAML::Node derivedMetadata = GenerateDerivedMetadata(newUserlistEntry.Name()); - if (derivedMetadata.size() > 0) - return JSON::stringify(derivedMetadata); - else - return "null"; + return true; + } else if (requestName == "closeSettings") { + BOOST_LOG_TRIVIAL(trace) << "Settings dialog closed and changes accepted, updating settings object."; + + try { + // Update the settings. + // If the user has deleted a default game, we don't want to restore it now. + // It will be restored when LOOT is next loaded. + YAML::Node settings = request["args"][0]; + lootState_.load(settings); + + // Now send back the new list of installed games to the UI. + BOOST_LOG_TRIVIAL(trace) << "Getting new list of installed games."; + callback->Success(GetInstalledGames()); + } catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + callback->Failure(-1, e.what()); } - - void QueryHandler::OpenReadme() { - BOOST_LOG_TRIVIAL(info) << "Opening LOOT readme."; - // Open readme in default application. - OpenInDefaultApplication(LootPaths::getReadmePath()); + return true; + } else if (requestName == "applySort") { + lootState_.decrementUnappliedChangeCounter(); + BOOST_LOG_TRIVIAL(trace) << "User has accepted sorted load order, applying it."; + try { + lootState_.getCurrentGame().SetLoadOrder(request["args"][0].as>()); + callback->Success(""); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + callback->Failure(e.codeAsUnsignedInt(), e.what()); + } catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + callback->Failure(-1, e.what()); } - void QueryHandler::OpenLogLocation() { - BOOST_LOG_TRIVIAL(info) << "Opening LOOT local appdata folder."; - //Open debug log folder. - OpenInDefaultApplication(LootPaths::getLogPath().parent_path()); + return true; + } else if (requestName == "copyContent") { + // Has one arg, just convert it to a YAML output string. + try { + YAML::Emitter yout; + yout.SetIndent(2); + yout << request["args"][0]; + string text = yout.c_str(); + // Get rid of yaml-cpp weirdness. + boost::replace_all(text, "! ", ""); + text = "[spoiler][code]" + text + "[/code][/spoiler]"; + CopyToClipboard(text); + callback->Success(""); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); + callback->Failure(e.codeAsUnsignedInt(), (boost::format(translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); + callback->Failure(-1, (boost::format(translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); } - - std::string QueryHandler::GetVersion() { - BOOST_LOG_TRIVIAL(info) << "Getting LOOT version."; - YAML::Node version(LootVersion::string() + "." + LootVersion::revision); - return JSON::stringify(version); + return true; + } else if (requestName == "copyLoadOrder") { + // Has one arg, an array of plugins in load order. Output them with indices in dec and hex. + try { + std::stringstream ss; + vector plugins = request["args"][0].as>(); + int decLength = 1; + if (plugins.size() > 99) { + decLength = 3; + } else if (plugins.size() > 9) { + decLength = 2; + } + size_t i = 0; + for (const auto& pluginName : plugins) { + if (lootState_.getCurrentGame().IsPluginActive(pluginName)) { + ss << std::setw(decLength) << i << " " << std::hex << std::setw(2) << i << std::dec << " "; + ++i; + } else { + ss << std::setw(decLength + 4) << " "; + } + ss << pluginName << "\r\n"; + } + CopyToClipboard(ss.str()); + callback->Success(""); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); + callback->Failure(e.codeAsUnsignedInt(), (boost::format(translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to copy plugin metadata. Details: " << e.what(); + callback->Failure(-1, (boost::format(translate("Failed to copy plugin metadata. Details: %1%")) % e.what()).str()); } - - std::string QueryHandler::GetSettings() { - BOOST_LOG_TRIVIAL(info) << "Getting LOOT settings."; - return JSON::stringify(_lootState.toYaml()); + return true; + } else if (requestName == "saveFilterState") { + // Has two args: the first is the filter ID, the second is the value. + BOOST_LOG_TRIVIAL(trace) << "Saving filter states."; + try { + lootState_.storeFilterState(request["args"][0].as(), request["args"][1].as()); + callback->Success(""); + } catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << e.what(); + callback->Failure(-1, e.what()); } - - std::string QueryHandler::GetLanguages() { - BOOST_LOG_TRIVIAL(info) << "Getting LOOT's supported languages."; - // Need to get an array of language names and their corresponding codes. - YAML::Node temp; - for (const auto& code : Language::Codes) { - YAML::Node lang; - Language language(code); - lang["name"] = language.GetName(); - lang["locale"] = language.GetLocale(); - temp.push_back(lang); - } - return JSON::stringify(temp); + return true; + } + return false; +} + +void QueryHandler::GetConflictingPlugins(const std::string& pluginName, CefRefPtr callback) { + BOOST_LOG_TRIVIAL(debug) << "Searching for plugins that conflict with " << pluginName; + + // Checking for FormID overlap will only work if the plugins have been loaded, so check if + // the plugins have been fully loaded, and if not load all plugins. + if (!lootState_.getCurrentGame().ArePluginsFullyLoaded()) + lootState_.getCurrentGame().LoadPlugins(false); + + YAML::Node node; + auto plugin = lootState_.getCurrentGame().GetPlugin(pluginName); + for (const auto& otherPlugin : lootState_.getCurrentGame().GetPlugins()) { + // Plugin loading may have produced an error message, so rederive + // displayed data. + + YAML::Node pluginNode = GenerateDerivedMetadata(otherPlugin.Name()); + + pluginNode["name"] = otherPlugin.Name(); + pluginNode["crc"] = otherPlugin.Crc(); + pluginNode["isEmpty"] = otherPlugin.IsEmpty(); + if (plugin.DoFormIDsOverlap(otherPlugin)) { + BOOST_LOG_TRIVIAL(debug) << "Found conflicting plugin: " << otherPlugin.Name(); + pluginNode["conflicts"] = true; + } else { + pluginNode["conflicts"] = false; } - std::string QueryHandler::GetGameTypes() { - BOOST_LOG_TRIVIAL(info) << "Getting LOOT's supported game types."; - YAML::Node temp; - temp.push_back(Game(GameType::tes4).FolderName()); - temp.push_back(Game(GameType::tes5).FolderName()); - temp.push_back(Game(GameType::fo3).FolderName()); - temp.push_back(Game(GameType::fonv).FolderName()); - temp.push_back(Game(GameType::fo4).FolderName()); - return JSON::stringify(temp); + node.push_back(pluginNode); + } + + if (node.size() > 0) + callback->Success(JSON::stringify(node)); + else + callback->Success("[]"); +} + +void QueryHandler::CopyMetadata(const std::string& pluginName) { + BOOST_LOG_TRIVIAL(debug) << "Copying metadata for plugin " << pluginName; + + // Get metadata from masterlist and userlist. + PluginMetadata plugin = lootState_.getCurrentGame().GetMasterlist().FindPlugin(pluginName); + plugin.MergeMetadata(lootState_.getCurrentGame().GetUserlist().FindPlugin(pluginName)); + + // Generate text representation. + string text; + YAML::Emitter yout; + yout.SetIndent(2); + yout << plugin; + text = yout.c_str(); + // Get rid of yaml-cpp weirdness. + boost::replace_all(text, "! ", ""); + text = "[spoiler][code]" + text + "[/code][/spoiler]"; + + CopyToClipboard(text); + + BOOST_LOG_TRIVIAL(info) << "Exported userlist metadata text for \"" << pluginName << "\": " << text; +} + +std::string QueryHandler::ClearPluginMetadata(const std::string& pluginName) { + BOOST_LOG_TRIVIAL(debug) << "Clearing user metadata for plugin " << pluginName; + + lootState_.getCurrentGame().GetUserlist().ErasePlugin(PluginMetadata(pluginName)); + + // Save userlist edits. + lootState_.getCurrentGame().GetUserlist().Save(lootState_.getCurrentGame().UserlistPath()); + + // Now rederive the displayed metadata from the masterlist. + YAML::Node derivedMetadata = GenerateDerivedMetadata(pluginName); + if (derivedMetadata.size() > 0) + return JSON::stringify(derivedMetadata); + else + return "null"; +} + +std::string QueryHandler::ApplyUserEdits(const YAML::Node& pluginMetadata) { + BOOST_LOG_TRIVIAL(trace) << "Applying user edits for: " << pluginMetadata["name"].as(); + // Create new object for userlist entry. + PluginMetadata newUserlistEntry(pluginMetadata["name"].as()); + + // Find existing userlist entry. + PluginMetadata ulistPlugin = lootState_.getCurrentGame().GetUserlist().FindPlugin(newUserlistEntry); + + // First sort out the priority value. This is only given if it was changed. + BOOST_LOG_TRIVIAL(trace) << "Calculating userlist metadata priority value from Javascript variables."; + if (pluginMetadata["priority"] && pluginMetadata["isPriorityGlobal"]) { + BOOST_LOG_TRIVIAL(trace) << "Priority value was changed, recalculating..."; + // Priority value was changed, so add it to the userlist data. + newUserlistEntry.Priority(pluginMetadata["priority"].as()); + newUserlistEntry.SetPriorityExplicit(true); + newUserlistEntry.SetPriorityGlobal(pluginMetadata["isPriorityGlobal"].as()); + } else { + // Priority value wasn't changed, use the existing userlist value. + BOOST_LOG_TRIVIAL(trace) << "Priority value is unchanged, using existing userlist value (if it exists)."; + if (!ulistPlugin.HasNameOnly()) { + newUserlistEntry.Priority(ulistPlugin.Priority()); + newUserlistEntry.SetPriorityExplicit(ulistPlugin.IsPriorityExplicit()); + newUserlistEntry.SetPriorityGlobal(ulistPlugin.IsPriorityGlobal()); } - - std::string QueryHandler::GetInstalledGames() { - BOOST_LOG_TRIVIAL(info) << "Getting LOOT's detected games."; - YAML::Node temp = YAML::Node(_lootState.InstalledGames()); - if (temp.size() > 0) - return JSON::stringify(temp); - else - return "[]"; + } + + // Now the enabled flag. + newUserlistEntry.Enabled(pluginMetadata["userlist"]["enabled"].as()); + + // Now metadata lists. These are given in their entirety, so replace anything that + // currently exists. + BOOST_LOG_TRIVIAL(trace) << "Recording metadata lists from Javascript variables."; + if (pluginMetadata["userlist"]["after"]) + newUserlistEntry.LoadAfter(pluginMetadata["userlist"]["after"].as>()); + if (pluginMetadata["userlist"]["req"]) + newUserlistEntry.Reqs(pluginMetadata["userlist"]["req"].as>()); + if (pluginMetadata["userlist"]["inc"]) + newUserlistEntry.Incs(pluginMetadata["userlist"]["inc"].as>()); + + if (pluginMetadata["userlist"]["msg"]) + newUserlistEntry.Messages(pluginMetadata["userlist"]["msg"].as>()); + if (pluginMetadata["userlist"]["tag"]) + newUserlistEntry.Tags(pluginMetadata["userlist"]["tag"].as>()); + if (pluginMetadata["userlist"]["dirty"]) + newUserlistEntry.DirtyInfo(pluginMetadata["userlist"]["dirty"].as>()); + if (pluginMetadata["userlist"]["url"]) + newUserlistEntry.Locations(pluginMetadata["userlist"]["url"].as>()); + +// For cleanliness, only data that does not duplicate masterlist and plugin data should be retained, so diff that. + BOOST_LOG_TRIVIAL(trace) << "Removing any user metadata that duplicates masterlist metadata."; + try { + Plugin tempPlugin(lootState_.getCurrentGame().GetPlugin(newUserlistEntry.Name())); + tempPlugin.MergeMetadata(lootState_.getCurrentGame().GetMasterlist().FindPlugin(newUserlistEntry)); + newUserlistEntry = newUserlistEntry.NewMetadata(tempPlugin); + } catch (...) { + newUserlistEntry = newUserlistEntry.NewMetadata(lootState_.getCurrentGame().GetMasterlist().FindPlugin(newUserlistEntry)); + } + + // Now erase any existing userlist entry. + if (!ulistPlugin.HasNameOnly()) { + BOOST_LOG_TRIVIAL(trace) << "Erasing the existing userlist entry."; + lootState_.getCurrentGame().GetUserlist().ErasePlugin(ulistPlugin); + } + // Add a new userlist entry if necessary. + if (!newUserlistEntry.HasNameOnly()) { + BOOST_LOG_TRIVIAL(trace) << "Adding new metadata to new userlist entry."; + lootState_.getCurrentGame().GetUserlist().AddPlugin(newUserlistEntry); + } + + // Save edited userlist. + lootState_.getCurrentGame().GetUserlist().Save(lootState_.getCurrentGame().UserlistPath()); + + // Now rederive the derived metadata. + BOOST_LOG_TRIVIAL(trace) << "Returning newly derived display metadata."; + YAML::Node derivedMetadata = GenerateDerivedMetadata(newUserlistEntry.Name()); + if (derivedMetadata.size() > 0) + return JSON::stringify(derivedMetadata); + else + return "null"; +} + +void QueryHandler::OpenReadme() { + BOOST_LOG_TRIVIAL(info) << "Opening LOOT readme."; + // Open readme in default application. + OpenInDefaultApplication(LootPaths::getReadmePath()); +} + +void QueryHandler::OpenLogLocation() { + BOOST_LOG_TRIVIAL(info) << "Opening LOOT local appdata folder."; + //Open debug log folder. + OpenInDefaultApplication(LootPaths::getLogPath().parent_path()); +} + +std::string QueryHandler::GetVersion() { + BOOST_LOG_TRIVIAL(info) << "Getting LOOT version."; + YAML::Node version(LootVersion::string() + "." + LootVersion::revision); + return JSON::stringify(version); +} + +std::string QueryHandler::GetSettings() { + BOOST_LOG_TRIVIAL(info) << "Getting LOOT settings."; + return JSON::stringify(lootState_.toYaml()); +} + +std::string QueryHandler::GetLanguages() { + BOOST_LOG_TRIVIAL(info) << "Getting LOOT's supported languages."; + // Need to get an array of language names and their corresponding codes. + YAML::Node temp; + for (const auto& code : Language::codes) { + YAML::Node lang; + Language language(code); + lang["name"] = language.GetName(); + lang["locale"] = language.GetLocale(); + temp.push_back(lang); + } + return JSON::stringify(temp); +} + +std::string QueryHandler::GetGameTypes() { + BOOST_LOG_TRIVIAL(info) << "Getting LOOT's supported game types."; + YAML::Node temp; + temp.push_back(Game(GameType::tes4).FolderName()); + temp.push_back(Game(GameType::tes5).FolderName()); + temp.push_back(Game(GameType::fo3).FolderName()); + temp.push_back(Game(GameType::fonv).FolderName()); + temp.push_back(Game(GameType::fo4).FolderName()); + return JSON::stringify(temp); +} + +std::string QueryHandler::GetInstalledGames() { + BOOST_LOG_TRIVIAL(info) << "Getting LOOT's detected games."; + YAML::Node temp = YAML::Node(lootState_.getInstalledGames()); + if (temp.size() > 0) + return JSON::stringify(temp); + else + return "[]"; +} + +void QueryHandler::GetGameData(CefRefPtr frame, CefRefPtr callback) { + try { + /* GetGameData() can be called for initialising the UI for a game for the first time + in a session, or it can be called when changing to a game that has previously been + active. In the first case, all data should be loaded, but in the second, only load + order and plugin header info should be re-loaded. + Determine which case it is by checking to see if the game's plugins object is empty. + */ + BOOST_LOG_TRIVIAL(info) << "Getting data specific to LOOT's active game."; + // Get masterlist revision info and parse if it exists. Also get plugin headers info and parse userlist if it exists. + + // First clear CRC and condition caches, otherwise they could lead to incorrect evaluations. + lootState_.getCurrentGame().ClearCachedConditions(); + + bool isFirstLoad = lootState_.getCurrentGame().GetPlugins().empty(); + lootState_.getCurrentGame().LoadPlugins(true); + + //Sort plugins into their load order. + list installed; + list loadOrder = lootState_.getCurrentGame().GetLoadOrder(); + for (const auto &pluginName : loadOrder) { + try { + const auto plugin = lootState_.getCurrentGame().GetPlugin(pluginName); + installed.push_back(plugin); + } catch (...) {} } - void QueryHandler::GetGameData(CefRefPtr frame, CefRefPtr callback) { + if (isFirstLoad) { + //Parse masterlist, don't update it. + if (exists(lootState_.getCurrentGame().MasterlistPath())) { + BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; try { - /* GetGameData() can be called for initialising the UI for a game for the first time - in a session, or it can be called when changing to a game that has previously been - active. In the first case, all data should be loaded, but in the second, only load - order and plugin header info should be re-loaded. - Determine which case it is by checking to see if the game's plugins object is empty. - */ - BOOST_LOG_TRIVIAL(info) << "Getting data specific to LOOT's active game."; - // Get masterlist revision info and parse if it exists. Also get plugin headers info and parse userlist if it exists. - - // First clear CRC and condition caches, otherwise they could lead to incorrect evaluations. - _lootState.CurrentGame().ClearCachedConditions(); - - bool isFirstLoad = _lootState.CurrentGame().GetPlugins().empty(); - _lootState.CurrentGame().LoadPlugins(true); - - //Sort plugins into their load order. - list installed; - list loadOrder = _lootState.CurrentGame().GetLoadOrder(); - for (const auto &pluginName : loadOrder) { - try { - const auto plugin = _lootState.CurrentGame().GetPlugin(pluginName); - installed.push_back(plugin); - } - catch (...) {} - } - - if (isFirstLoad) { - //Parse masterlist, don't update it. - if (fs::exists(_lootState.CurrentGame().MasterlistPath())) { - BOOST_LOG_TRIVIAL(debug) << "Parsing masterlist."; - try { - _lootState.CurrentGame().GetMasterlist().Load(_lootState.CurrentGame().MasterlistPath()); - } - catch (exception &e) { - _lootState.CurrentGame().GetMasterlist().AppendMessage(Message(Message::Type::error, (boost::format(loc::translate( - "An error occurred while parsing the masterlist: %1%. " - "This probably happened because an update to LOOT changed " - "its metadata syntax support. Try updating your masterlist " - "to resolve the error." - )) % e.what()).str())); - } - } - - //Parse userlist. - if (fs::exists(_lootState.CurrentGame().UserlistPath())) { - BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; - try { - _lootState.CurrentGame().GetUserlist().Load(_lootState.CurrentGame().UserlistPath()); - } - catch (exception &e) { - _lootState.CurrentGame().GetUserlist().AppendMessage(Message(Message::Type::error, (boost::format(loc::translate( - "An error occurred while parsing the userlist: %1%. " - "This probably happened because an update to LOOT changed " - "its metadata syntax support. Your user metadata will have " - "to be updated manually.\n\n" - "To do so, use the 'Open Debug Log Location' in LOOT's main " - "menu to open its data folder, then open your 'userlist.yaml' " - "file in the relevant game folder. You can then edit the " - "metadata it contains with reference to the " - "[syntax documentation](http://loot.github.io/docs/%2%.%3%.%4%/LOOT%%20Metadata%%20Syntax.html).\n\n" - "You can also seek support on LOOT's forum thread, which is " - "linked to on [LOOT's website](http://loot.github.io/)." - )) % e.what() % LootVersion::major % LootVersion::minor % LootVersion::patch).str())); - } - } - } - - // Now convert to a single object that can be turned into a JSON string - //--------------------------------------------------------------------- - - // The data structure is to be set as 'loot.game'. - YAML::Node gameNode; - - // ID the game using its folder value. - gameNode["folder"] = _lootState.CurrentGame().FolderName(); - - // Store the masterlist revision and date. - try { - Masterlist::Info info = _lootState.CurrentGame().GetMasterlist().GetInfo(_lootState.CurrentGame().MasterlistPath(), true); - gameNode["masterlist"]["revision"] = info.revision; - gameNode["masterlist"]["date"] = info.date; - } - catch (Error &e) { - gameNode["masterlist"]["revision"] = e.what(); - gameNode["masterlist"]["date"] = e.what(); - } - - // Now store global messages. - gameNode["globalMessages"] = GetGeneralMessages(); - - gameNode["bashTags"] = _lootState.CurrentGame().GetMasterlist().BashTags(); - - // Now store plugin data. - for (const auto& plugin : installed) { - /* Each plugin has members while hold its raw masterlist and userlist data for - the editor, and also processed data for the main display. - */ - YAML::Node pluginNode; - // Find the masterlist metadata for this plugin. Treat Bash Tags from the plugin - // description as part of it. - BOOST_LOG_TRIVIAL(trace) << "Getting masterlist metadata for: " << plugin.Name(); - Plugin mlistPlugin(plugin); - mlistPlugin.MergeMetadata(_lootState.CurrentGame().GetMasterlist().FindPlugin(plugin)); - - // Now do the same again for any userlist data. - BOOST_LOG_TRIVIAL(trace) << "Getting userlist metadata for: " << plugin.Name(); - PluginMetadata ulistPlugin(_lootState.CurrentGame().GetUserlist().FindPlugin(plugin)); - - pluginNode["__type"] = "Plugin"; // For conversion back into a JS typed object. - pluginNode["name"] = plugin.Name(); - pluginNode["isActive"] = plugin.IsActive(); - pluginNode["isEmpty"] = plugin.IsEmpty(); - pluginNode["isMaster"] = plugin.isMasterFile(); - pluginNode["loadsArchive"] = plugin.LoadsArchive(); - pluginNode["crc"] = plugin.Crc(); - pluginNode["version"] = Version(plugin.getDescription()).AsString(); - - if (!mlistPlugin.HasNameOnly()) { - // Now add the masterlist metadata to the pluginNode. - pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); - pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); - pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); - pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); - pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); - pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); - pluginNode["masterlist"]["url"] = mlistPlugin.Locations(); - } - - if (!ulistPlugin.HasNameOnly()) { - // Now add the userlist metadata to the pluginNode. - pluginNode["userlist"]["enabled"] = ulistPlugin.Enabled(); - pluginNode["userlist"]["after"] = ulistPlugin.LoadAfter(); - pluginNode["userlist"]["req"] = ulistPlugin.Reqs(); - pluginNode["userlist"]["inc"] = ulistPlugin.Incs(); - pluginNode["userlist"]["msg"] = ulistPlugin.Messages(); - pluginNode["userlist"]["tag"] = ulistPlugin.Tags(); - pluginNode["userlist"]["dirty"] = ulistPlugin.DirtyInfo(); - pluginNode["userlist"]["url"] = ulistPlugin.Locations(); - // The raw priority data isn't used, but should be set - // that LOOT knows it exists. - if (ulistPlugin.IsPriorityExplicit()) { - pluginNode["userlist"]["hasExplicitPriority"] = true; - } - } - - // Now merge masterlist and userlist metadata and evaluate, - // putting any resulting metadata into the base of the pluginNode. - YAML::Node derivedNode = GenerateDerivedMetadata(plugin, mlistPlugin, ulistPlugin); - - for (auto it = derivedNode.begin(); it != derivedNode.end(); ++it) { - const string key = it->first.as(); - pluginNode[key] = it->second; - } - - gameNode["plugins"].push_back(pluginNode); - } - - callback->Success(JSON::stringify(gameNode)); - } - catch (loot::Error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to get game data. Details: " << e.what(); - callback->Failure(e.codeAsUnsignedInt(), (boost::format(loc::translate("Failed to get game data. Details: %1%")) % e.what()).str()); + lootState_.getCurrentGame().GetMasterlist().Load(lootState_.getCurrentGame().MasterlistPath()); + } catch (exception &e) { + lootState_.getCurrentGame().GetMasterlist().AppendMessage(Message(Message::Type::error, (boost::format(translate( + "An error occurred while parsing the masterlist: %1%. " + "This probably happened because an update to LOOT changed " + "its metadata syntax support. Try updating your masterlist " + "to resolve the error." + )) % e.what()).str())); } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "Failed to get game data. Details: " << e.what(); - callback->Failure(-1, (boost::format(loc::translate("Failed to get game data. Details: %1%")) % e.what()).str()); - } - } + } - void QueryHandler::UpdateMasterlist(CefRefPtr callback) { + //Parse userlist. + if (exists(lootState_.getCurrentGame().UserlistPath())) { + BOOST_LOG_TRIVIAL(debug) << "Parsing userlist."; try { - // Update / parse masterlist. - BOOST_LOG_TRIVIAL(debug) << "Updating and parsing masterlist."; - bool wasChanged = true; - try { - wasChanged = _lootState.CurrentGame().GetMasterlist().Update(_lootState.CurrentGame()); - } - catch (loot::Error &e) { - if (e.code() == loot::Error::Code::ok) { - // There was a parsing error, but roll-back was successful, so the process - - // should still complete. - _lootState.CurrentGame().GetMasterlist().AppendMessage(Message(Message::Type::error, e.what())); - wasChanged = true; - } - else { - // Error wasn't a parsing error. Need to try parsing masterlist if it exists. - try { - _lootState.CurrentGame().GetMasterlist().Load(_lootState.CurrentGame().MasterlistPath()); - } - catch (...) {} - } - throw; - } - - // Now regenerate the JS-side masterlist data if the masterlist was changed. - if (wasChanged) { - // The data structure is to be set as 'loot.game'. - YAML::Node gameNode; - - // Store the masterlist revision and date. - try { - Masterlist::Info info = _lootState.CurrentGame().GetMasterlist().GetInfo(_lootState.CurrentGame().MasterlistPath(), true); - gameNode["masterlist"]["revision"] = info.revision; - gameNode["masterlist"]["date"] = info.date; - } - catch (Error &e) { - gameNode["masterlist"]["revision"] = e.what(); - gameNode["masterlist"]["date"] = e.what(); - } - - // Store bash tags in case they have changed. - gameNode["bashTags"] = _lootState.CurrentGame().GetMasterlist().BashTags(); - - // Store global messages in case they have changed. - gameNode["globalMessages"] = GetGeneralMessages(); - - for (const auto& plugin : _lootState.CurrentGame().GetPlugins()) { - Plugin mlistPlugin(plugin); - mlistPlugin.MergeMetadata(_lootState.CurrentGame().GetMasterlist().FindPlugin(plugin)); - - YAML::Node pluginNode; - if (!mlistPlugin.HasNameOnly()) { - // Now add the masterlist metadata to the pluginNode. - pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); - pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); - pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); - pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); - pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); - pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); - pluginNode["masterlist"]["url"] = mlistPlugin.Locations(); - } - - // Now merge masterlist and userlist metadata and evaluate, - // putting any resulting metadata into the base of the pluginNode. - YAML::Node derivedNode = GenerateDerivedMetadata(plugin.Name()); - - for (const auto &pair : derivedNode) { - const string key = pair.first.as(); - pluginNode[key] = pair.second; - } - - gameNode["plugins"].push_back(pluginNode); - } - - callback->Success(JSON::stringify(gameNode)); - } - else - callback->Success("null"); + lootState_.getCurrentGame().GetUserlist().Load(lootState_.getCurrentGame().UserlistPath()); + } catch (exception &e) { + lootState_.getCurrentGame().GetUserlist().AppendMessage(Message(Message::Type::error, (boost::format(translate( + "An error occurred while parsing the userlist: %1%. " + "This probably happened because an update to LOOT changed " + "its metadata syntax support. Your user metadata will have " + "to be updated manually.\n\n" + "To do so, use the 'Open Debug Log Location' in LOOT's main " + "menu to open its data folder, then open your 'userlist.yaml' " + "file in the relevant game folder. You can then edit the " + "metadata it contains with reference to the " + "[syntax documentation](http://loot.github.io/docs/%2%.%3%.%4%/LOOT%%20Metadata%%20Syntax.html).\n\n" + "You can also seek support on LOOT's forum thread, which is " + "linked to on [LOOT's website](http://loot.github.io/)." + )) % e.what() % LootVersion::major % LootVersion::minor % LootVersion::patch).str())); } - catch (Error &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to update the masterlist. Details: " << e.what(); - callback->Failure(e.codeAsUnsignedInt(), (boost::format(loc::translate("Failed to update the masterlist. Details: %1%")) % e.what()).str()); - } - catch (exception &e) { - BOOST_LOG_TRIVIAL(error) << "Failed to update the masterlist. Details: " << e.what(); - callback->Failure(-1, (boost::format(loc::translate("Failed to update the masterlist. Details: %1%")) % e.what()).str()); - } + } } - std::string QueryHandler::ClearAllMetadata() { - BOOST_LOG_TRIVIAL(debug) << "Clearing all user metadata."; - // Record which plugins have userlist entries. - vector userlistPlugins; - for (const auto &plugin : _lootState.CurrentGame().GetUserlist().Plugins()) { - userlistPlugins.push_back(plugin.Name()); - } - BOOST_LOG_TRIVIAL(trace) << "User metadata exists for " << userlistPlugins.size() << " plugins."; + // Now convert to a single object that can be turned into a JSON string + //--------------------------------------------------------------------- + + // The data structure is to be set as 'loot.game'. + YAML::Node gameNode; - // Clear the user metadata. - _lootState.CurrentGame().GetUserlist().clear(); + // ID the game using its folder value. + gameNode["folder"] = lootState_.getCurrentGame().FolderName(); - // Save userlist edits. - _lootState.CurrentGame().GetUserlist().Save(_lootState.CurrentGame().UserlistPath()); + // Store the masterlist revision and date. + try { + Masterlist::Info info = lootState_.getCurrentGame().GetMasterlist().GetInfo(lootState_.getCurrentGame().MasterlistPath(), true); + gameNode["masterlist"]["revision"] = info.revision; + gameNode["masterlist"]["date"] = info.date; + } catch (Error &e) { + gameNode["masterlist"]["revision"] = e.what(); + gameNode["masterlist"]["date"] = e.what(); + } - // Regenerate the derived metadata (priority, messages, tags and dirty state) - // for any plugins with userlist entries. - YAML::Node pluginsNode; - for (const auto &plugin : userlistPlugins) { - pluginsNode.push_back(GenerateDerivedMetadata(plugin)); + // Now store global messages. + gameNode["globalMessages"] = GetGeneralMessages(); + + gameNode["bashTags"] = lootState_.getCurrentGame().GetMasterlist().BashTags(); + + // Now store plugin data. + for (const auto& plugin : installed) { + /* Each plugin has members while hold its raw masterlist and userlist data for + the editor, and also processed data for the main display. + */ + YAML::Node pluginNode; + // Find the masterlist metadata for this plugin. Treat Bash Tags from the plugin + // description as part of it. + BOOST_LOG_TRIVIAL(trace) << "Getting masterlist metadata for: " << plugin.Name(); + Plugin mlistPlugin(plugin); + mlistPlugin.MergeMetadata(lootState_.getCurrentGame().GetMasterlist().FindPlugin(plugin)); + + // Now do the same again for any userlist data. + BOOST_LOG_TRIVIAL(trace) << "Getting userlist metadata for: " << plugin.Name(); + PluginMetadata ulistPlugin(lootState_.getCurrentGame().GetUserlist().FindPlugin(plugin)); + + pluginNode["__type"] = "Plugin"; // For conversion back into a JS typed object. + pluginNode["name"] = plugin.Name(); + pluginNode["isActive"] = plugin.IsActive(); + pluginNode["isEmpty"] = plugin.IsEmpty(); + pluginNode["isMaster"] = plugin.isMasterFile(); + pluginNode["loadsArchive"] = plugin.LoadsArchive(); + pluginNode["crc"] = plugin.Crc(); + pluginNode["version"] = Version(plugin.getDescription()).AsString(); + + if (!mlistPlugin.HasNameOnly()) { + // Now add the masterlist metadata to the pluginNode. + pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); + pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); + pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); + pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); + pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); + pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); + pluginNode["masterlist"]["url"] = mlistPlugin.Locations(); + } + + if (!ulistPlugin.HasNameOnly()) { + // Now add the userlist metadata to the pluginNode. + pluginNode["userlist"]["enabled"] = ulistPlugin.Enabled(); + pluginNode["userlist"]["after"] = ulistPlugin.LoadAfter(); + pluginNode["userlist"]["req"] = ulistPlugin.Reqs(); + pluginNode["userlist"]["inc"] = ulistPlugin.Incs(); + pluginNode["userlist"]["msg"] = ulistPlugin.Messages(); + pluginNode["userlist"]["tag"] = ulistPlugin.Tags(); + pluginNode["userlist"]["dirty"] = ulistPlugin.DirtyInfo(); + pluginNode["userlist"]["url"] = ulistPlugin.Locations(); + // The raw priority data isn't used, but should be set + // that LOOT knows it exists. + if (ulistPlugin.IsPriorityExplicit()) { + pluginNode["userlist"]["hasExplicitPriority"] = true; } - BOOST_LOG_TRIVIAL(trace) << "Display metadata rederived for " << pluginsNode.size() << " plugins."; + } - if (pluginsNode.size() > 0) - return JSON::stringify(pluginsNode); - else - return "[]"; - } + // Now merge masterlist and userlist metadata and evaluate, + // putting any resulting metadata into the base of the pluginNode. + YAML::Node derivedNode = GenerateDerivedMetadata(plugin, mlistPlugin, ulistPlugin); - void QueryHandler::SortPlugins(CefRefPtr frame, CefRefPtr callback) { - BOOST_LOG_TRIVIAL(info) << "Beginning sorting operation."; - BOOST_LOG_TRIVIAL(info) << "Using message language: " << _lootState.getLanguage().GetName(); + for (auto it = derivedNode.begin(); it != derivedNode.end(); ++it) { + const string key = it->first.as(); + pluginNode[key] = it->second; + } - try { - // Always reload all the plugins. - SendProgressUpdate(frame, loc::translate("Loading plugin contents...")); - _lootState.CurrentGame().LoadPlugins(false); - - //Sort plugins into their load order. - SendProgressUpdate(frame, loc::translate("Sorting load order...")); - PluginSorter sorter; - list plugins = sorter.Sort(_lootState.CurrentGame(), _lootState.getLanguage().GetCode()); - - // If TESV or FO4, check if load order has been changed. - if ((_lootState.CurrentGame().Type() == GameType::tes5 || _lootState.CurrentGame().Type() == GameType::fo4) - && equal(begin(plugins), end(plugins), begin(_lootState.CurrentGame().GetLoadOrder()))) { - // Load order has not been changed, set it without asking for - // user input because there are no changes to accept and some - // plugins' positions may only be inferred and not written to - // loadorder.txt/plugins.txt. - std::list newLoadOrder; - std::transform(begin(plugins), - end(plugins), - back_inserter(newLoadOrder), - [](const Plugin& plugin) { - return plugin.Name(); - }); - _lootState.CurrentGame().SetLoadOrder(newLoadOrder); - } - - YAML::Node node; - - // Store global messages in case they have changed. - node["globalMessages"] = GetGeneralMessages(); - - for (const auto &plugin : plugins) { - YAML::Node pluginNode; - - pluginNode["name"] = plugin.Name(); - pluginNode["crc"] = plugin.Crc(); - pluginNode["isEmpty"] = plugin.IsEmpty(); - - // Sorting may have produced a plugin loading error message, so rederive displayed data. - YAML::Node derivedNode = GenerateDerivedMetadata(plugin.Name()); - for (const auto &pair : derivedNode) { - const string key = pair.first.as(); - pluginNode[key] = pair.second; - } - - node["plugins"].push_back(pluginNode); - } - _lootState.incrementUnappliedChangeCounter(); - - if (node.size() > 0) - callback->Success(JSON::stringify(node)); - else - callback->Success("null"); - } - catch (loot::Error& e) { - BOOST_LOG_TRIVIAL(error) << "Failed to sort plugins. Details: " << e.what(); - if (e.code() == Error::Code::sorting_error) { - _lootState.CurrentGame().AppendMessage(Message(Message::Type::error, e.what())); - - YAML::Node node; - node["globalMessages"] = GetGeneralMessages(); - callback->Success(JSON::stringify(node)); - } - else - callback->Failure(e.codeAsUnsignedInt(), (boost::format(loc::translate("Failed to sort plugins. Details: %1%")) % e.what()).str()); - } + gameNode["plugins"].push_back(pluginNode); } - - std::vector QueryHandler::GetGeneralMessages() const { - vector messages; - auto metadataListMessages = _lootState.CurrentGame().GetMasterlist().Messages(); - messages.insert(end(messages), - begin(metadataListMessages), - end(metadataListMessages)); - metadataListMessages = _lootState.CurrentGame().GetUserlist().Messages(); - messages.insert(end(messages), - begin(metadataListMessages), - end(metadataListMessages)); - auto gameMessages = _lootState.CurrentGame().GetMessages(); - messages.insert(end(messages), - begin(gameMessages), - end(gameMessages)); + callback->Success(JSON::stringify(gameNode)); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to get game data. Details: " << e.what(); + callback->Failure(e.codeAsUnsignedInt(), (boost::format(translate("Failed to get game data. Details: %1%")) % e.what()).str()); + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to get game data. Details: " << e.what(); + callback->Failure(-1, (boost::format(translate("Failed to get game data. Details: %1%")) % e.what()).str()); + } +} + +void QueryHandler::UpdateMasterlist(CefRefPtr callback) { + try { + // Update / parse masterlist. + BOOST_LOG_TRIVIAL(debug) << "Updating and parsing masterlist."; + bool wasChanged = true; + try { + wasChanged = lootState_.getCurrentGame().GetMasterlist().Update(lootState_.getCurrentGame()); + } catch (Error &e) { + if (e.code() == Error::Code::ok) { + // There was a parsing error, but roll-back was successful, so the process + + // should still complete. + lootState_.getCurrentGame().GetMasterlist().AppendMessage(Message(Message::Type::error, e.what())); + wasChanged = true; + } else { + // Error wasn't a parsing error. Need to try parsing masterlist if it exists. try { - BOOST_LOG_TRIVIAL(info) << "Using message language: " << _lootState.getLanguage().GetName(); - auto it = begin(messages); - while (it != end(messages)) { - if (!it->EvalCondition(_lootState.CurrentGame(), _lootState.getLanguage().GetCode())) - it = messages.erase(it); - else - ++it; - } - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "A global message contains a condition that could not be evaluated. Details: " << e.what(); - messages.push_back(Message(Message::Type::error, (format(loc::translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str())); - } - - return messages; + lootState_.getCurrentGame().GetMasterlist().Load(lootState_.getCurrentGame().MasterlistPath()); + } catch (...) {} + } + throw; } - YAML::Node QueryHandler::GenerateDerivedMetadata(const Plugin& file, const PluginMetadata& masterlist, const PluginMetadata& userlist) { - BOOST_LOG_TRIVIAL(info) << "Using message language: " << _lootState.getLanguage().GetName(); + // Now regenerate the JS-side masterlist data if the masterlist was changed. + if (wasChanged) { + // The data structure is to be set as 'loot.game'. + YAML::Node gameNode; - // Now rederive the displayed metadata from the masterlist and userlist. - Plugin tempPlugin(file); + // Store the masterlist revision and date. + try { + Masterlist::Info info = lootState_.getCurrentGame().GetMasterlist().GetInfo(lootState_.getCurrentGame().MasterlistPath(), true); + gameNode["masterlist"]["revision"] = info.revision; + gameNode["masterlist"]["date"] = info.date; + } catch (Error &e) { + gameNode["masterlist"]["revision"] = e.what(); + gameNode["masterlist"]["date"] = e.what(); + } - tempPlugin.MergeMetadata(masterlist); - tempPlugin.MergeMetadata(userlist); + // Store bash tags in case they have changed. + gameNode["bashTags"] = lootState_.getCurrentGame().GetMasterlist().BashTags(); - //Evaluate any conditions - BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; - try { - tempPlugin.EvalAllConditions(_lootState.CurrentGame(), _lootState.getLanguage().GetCode()); - } - catch (std::exception& e) { - BOOST_LOG_TRIVIAL(error) << "\"" << tempPlugin.Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); - list messages(tempPlugin.Messages()); - messages.push_back(Message(Message::Type::error, (format(loc::translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % tempPlugin.Name() % e.what()).str())); - tempPlugin.Messages(messages); - } + // Store global messages in case they have changed. + gameNode["globalMessages"] = GetGeneralMessages(); - //Also check install validity. - bool isDirty = tempPlugin.CheckInstallValidity(_lootState.CurrentGame()); + for (const auto& plugin : lootState_.getCurrentGame().GetPlugins()) { + Plugin mlistPlugin(plugin); + mlistPlugin.MergeMetadata(lootState_.getCurrentGame().GetMasterlist().FindPlugin(plugin)); - // Now add to pluginNode. YAML::Node pluginNode; - pluginNode["name"] = tempPlugin.Name(); - pluginNode["priority"] = tempPlugin.Priority(); - pluginNode["isPriorityGlobal"] = tempPlugin.IsPriorityGlobal(); - pluginNode["messages"] = tempPlugin.Messages(); - pluginNode["tags"] = tempPlugin.Tags(); - pluginNode["isDirty"] = isDirty; - - return pluginNode; - } + if (!mlistPlugin.HasNameOnly()) { + // Now add the masterlist metadata to the pluginNode. + pluginNode["masterlist"]["after"] = mlistPlugin.LoadAfter(); + pluginNode["masterlist"]["req"] = mlistPlugin.Reqs(); + pluginNode["masterlist"]["inc"] = mlistPlugin.Incs(); + pluginNode["masterlist"]["msg"] = mlistPlugin.Messages(); + pluginNode["masterlist"]["tag"] = mlistPlugin.Tags(); + pluginNode["masterlist"]["dirty"] = mlistPlugin.DirtyInfo(); + pluginNode["masterlist"]["url"] = mlistPlugin.Locations(); + } - YAML::Node QueryHandler::GenerateDerivedMetadata(const std::string& pluginName) { - // Now rederive the displayed metadata from the masterlist and userlist. - try { - auto plugin = _lootState.CurrentGame().GetPlugin(pluginName); - PluginMetadata master(_lootState.CurrentGame().GetMasterlist().FindPlugin(plugin)); - PluginMetadata user(_lootState.CurrentGame().GetUserlist().FindPlugin(plugin)); + // Now merge masterlist and userlist metadata and evaluate, + // putting any resulting metadata into the base of the pluginNode. + YAML::Node derivedNode = GenerateDerivedMetadata(plugin.Name()); - return this->GenerateDerivedMetadata(plugin, master, user); + for (const auto &pair : derivedNode) { + const string key = pair.first.as(); + pluginNode[key] = pair.second; } - catch (...) { - return YAML::Node(); - } + + gameNode["plugins"].push_back(pluginNode); + } + + callback->Success(JSON::stringify(gameNode)); + } else + callback->Success("null"); + } catch (Error &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to update the masterlist. Details: " << e.what(); + callback->Failure(e.codeAsUnsignedInt(), (boost::format(translate("Failed to update the masterlist. Details: %1%")) % e.what()).str()); + } catch (exception &e) { + BOOST_LOG_TRIVIAL(error) << "Failed to update the masterlist. Details: " << e.what(); + callback->Failure(-1, (boost::format(translate("Failed to update the masterlist. Details: %1%")) % e.what()).str()); + } +} + +std::string QueryHandler::ClearAllMetadata() { + BOOST_LOG_TRIVIAL(debug) << "Clearing all user metadata."; + // Record which plugins have userlist entries. + vector userlistPlugins; + for (const auto &plugin : lootState_.getCurrentGame().GetUserlist().Plugins()) { + userlistPlugins.push_back(plugin.Name()); + } + BOOST_LOG_TRIVIAL(trace) << "User metadata exists for " << userlistPlugins.size() << " plugins."; + + // Clear the user metadata. + lootState_.getCurrentGame().GetUserlist().Clear(); + + // Save userlist edits. + lootState_.getCurrentGame().GetUserlist().Save(lootState_.getCurrentGame().UserlistPath()); + + // Regenerate the derived metadata (priority, messages, tags and dirty state) + // for any plugins with userlist entries. + YAML::Node pluginsNode; + for (const auto &plugin : userlistPlugins) { + pluginsNode.push_back(GenerateDerivedMetadata(plugin)); + } + BOOST_LOG_TRIVIAL(trace) << "Display metadata rederived for " << pluginsNode.size() << " plugins."; + + if (pluginsNode.size() > 0) + return JSON::stringify(pluginsNode); + else + return "[]"; +} + +void QueryHandler::SortPlugins(CefRefPtr frame, CefRefPtr callback) { + BOOST_LOG_TRIVIAL(info) << "Beginning sorting operation."; + BOOST_LOG_TRIVIAL(info) << "Using message language: " << lootState_.getLanguage().GetName(); + + try { + // Always reload all the plugins. + SendProgressUpdate(frame, translate("Loading plugin contents...")); + lootState_.getCurrentGame().LoadPlugins(false); + + //Sort plugins into their load order. + SendProgressUpdate(frame, translate("Sorting load order...")); + PluginSorter sorter; + list plugins = sorter.Sort(lootState_.getCurrentGame(), lootState_.getLanguage().GetCode()); + + // If TESV or FO4, check if load order has been changed. + if ((lootState_.getCurrentGame().Type() == GameType::tes5 || lootState_.getCurrentGame().Type() == GameType::fo4) + && equal(begin(plugins), end(plugins), begin(lootState_.getCurrentGame().GetLoadOrder()))) { + // Load order has not been changed, set it without asking for + // user input because there are no changes to accept and some + // plugins' positions may only be inferred and not written to + // loadorder.txt/plugins.txt. + std::list newLoadOrder; + std::transform(begin(plugins), + end(plugins), + back_inserter(newLoadOrder), + [](const Plugin& plugin) { + return plugin.Name(); + }); + lootState_.getCurrentGame().SetLoadOrder(newLoadOrder); } + + YAML::Node node; - void QueryHandler::CopyToClipboard(const std::string& text) { -#ifdef _WIN32 - if (!OpenClipboard(NULL)) { - throw loot::Error(loot::Error::Code::windows_error, "Failed to open the Windows clipboard."); - } + // Store global messages in case they have changed. + node["globalMessages"] = GetGeneralMessages(); - if (!EmptyClipboard()) { - throw loot::Error(loot::Error::Code::windows_error, "Failed to empty the Windows clipboard."); - } + for (const auto &plugin : plugins) { + YAML::Node pluginNode; - // The clipboard takes a Unicode (ie. UTF-16) string that it then owns and must not - // be destroyed by LOOT. Convert the string, then copy it into a new block of - // memory for the clipboard. - wstring wtext = ToWinWide(text); - wchar_t * wcstr = new wchar_t[wtext.length() + 1]; - wcscpy(wcstr, wtext.c_str()); + pluginNode["name"] = plugin.Name(); + pluginNode["crc"] = plugin.Crc(); + pluginNode["isEmpty"] = plugin.IsEmpty(); - if (SetClipboardData(CF_UNICODETEXT, wcstr) == NULL) { - throw loot::Error(loot::Error::Code::windows_error, "Failed to copy metadata to the Windows clipboard."); - } + // Sorting may have produced a plugin loading error message, so rederive displayed data. + YAML::Node derivedNode = GenerateDerivedMetadata(plugin.Name()); + for (const auto &pair : derivedNode) { + const string key = pair.first.as(); + pluginNode[key] = pair.second; + } - if (!CloseClipboard()) { - throw loot::Error(loot::Error::Code::windows_error, "Failed to close the Windows clipboard."); - } + node["plugins"].push_back(pluginNode); + } + lootState_.incrementUnappliedChangeCounter(); + + if (node.size() > 0) + callback->Success(JSON::stringify(node)); + else + callback->Success("null"); + } catch (Error& e) { + BOOST_LOG_TRIVIAL(error) << "Failed to sort plugins. Details: " << e.what(); + if (e.code() == Error::Code::sorting_error) { + lootState_.getCurrentGame().AppendMessage(Message(Message::Type::error, e.what())); + + YAML::Node node; + node["globalMessages"] = GetGeneralMessages(); + callback->Success(JSON::stringify(node)); + } else + callback->Failure(e.codeAsUnsignedInt(), (boost::format(translate("Failed to sort plugins. Details: %1%")) % e.what()).str()); + } +} + +std::vector QueryHandler::GetGeneralMessages() const { + vector messages; + auto metadataListMessages = lootState_.getCurrentGame().GetMasterlist().Messages(); + messages.insert(end(messages), + begin(metadataListMessages), + end(metadataListMessages)); + metadataListMessages = lootState_.getCurrentGame().GetUserlist().Messages(); + messages.insert(end(messages), + begin(metadataListMessages), + end(metadataListMessages)); + auto gameMessages = lootState_.getCurrentGame().GetMessages(); + messages.insert(end(messages), + begin(gameMessages), + end(gameMessages)); + + try { + BOOST_LOG_TRIVIAL(info) << "Using message language: " << lootState_.getLanguage().GetName(); + auto it = begin(messages); + while (it != end(messages)) { + if (!it->EvalCondition(lootState_.getCurrentGame(), lootState_.getLanguage().GetCode())) + it = messages.erase(it); + else + ++it; + } + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "A global message contains a condition that could not be evaluated. Details: " << e.what(); + messages.push_back(Message(Message::Type::error, (format(translate("A global message contains a condition that could not be evaluated. Details: %1%")) % e.what()).str())); + } + + return messages; +} + +YAML::Node QueryHandler::GenerateDerivedMetadata(const Plugin& file, const PluginMetadata& masterlist, const PluginMetadata& userlist) { + BOOST_LOG_TRIVIAL(info) << "Using message language: " << lootState_.getLanguage().GetName(); + + // Now rederive the displayed metadata from the masterlist and userlist. + Plugin tempPlugin(file); + + tempPlugin.MergeMetadata(masterlist); + tempPlugin.MergeMetadata(userlist); + + //Evaluate any conditions + BOOST_LOG_TRIVIAL(trace) << "Evaluate conditions for merged plugin data."; + try { + tempPlugin.EvalAllConditions(lootState_.getCurrentGame(), lootState_.getLanguage().GetCode()); + } catch (std::exception& e) { + BOOST_LOG_TRIVIAL(error) << "\"" << tempPlugin.Name() << "\" contains a condition that could not be evaluated. Details: " << e.what(); + list messages(tempPlugin.Messages()); + messages.push_back(Message(Message::Type::error, (format(translate("\"%1%\" contains a condition that could not be evaluated. Details: %2%")) % tempPlugin.Name() % e.what()).str())); + tempPlugin.Messages(messages); + } + + //Also check install validity. + bool isDirty = tempPlugin.CheckInstallValidity(lootState_.getCurrentGame()); + + // Now add to pluginNode. + YAML::Node pluginNode; + pluginNode["name"] = tempPlugin.Name(); + pluginNode["priority"] = tempPlugin.Priority(); + pluginNode["isPriorityGlobal"] = tempPlugin.IsPriorityGlobal(); + pluginNode["messages"] = tempPlugin.Messages(); + pluginNode["tags"] = tempPlugin.Tags(); + pluginNode["isDirty"] = isDirty; + + return pluginNode; +} + +YAML::Node QueryHandler::GenerateDerivedMetadata(const std::string& pluginName) { + // Now rederive the displayed metadata from the masterlist and userlist. + try { + auto plugin = lootState_.getCurrentGame().GetPlugin(pluginName); + PluginMetadata master(lootState_.getCurrentGame().GetMasterlist().FindPlugin(plugin)); + PluginMetadata user(lootState_.getCurrentGame().GetUserlist().FindPlugin(plugin)); + + return this->GenerateDerivedMetadata(plugin, master, user); + } catch (...) { + return YAML::Node(); + } +} + +void QueryHandler::CopyToClipboard(const std::string& text) { +#ifdef _WIN32 + if (!OpenClipboard(NULL)) { + throw Error(Error::Code::windows_error, "Failed to open the Windows clipboard."); + } + + if (!EmptyClipboard()) { + throw Error(Error::Code::windows_error, "Failed to empty the Windows clipboard."); + } + + // The clipboard takes a Unicode (ie. UTF-16) string that it then owns and must not + // be destroyed by LOOT. Convert the string, then copy it into a new block of + // memory for the clipboard. + std::wstring wtext = ToWinWide(text); + wchar_t * wcstr = new wchar_t[wtext.length() + 1]; + wcscpy(wcstr, wtext.c_str()); + + if (SetClipboardData(CF_UNICODETEXT, wcstr) == NULL) { + throw Error(Error::Code::windows_error, "Failed to copy metadata to the Windows clipboard."); + } + + if (!CloseClipboard()) { + throw Error(Error::Code::windows_error, "Failed to close the Windows clipboard."); + } #endif - } +} - void QueryHandler::SendProgressUpdate(CefRefPtr frame, const std::string& message) { - BOOST_LOG_TRIVIAL(trace) << "Sending progress update: " << message; - frame->ExecuteJavaScript("loot.Dialog.showProgress('" + message + "');", frame->GetURL(), 0); - } +void QueryHandler::SendProgressUpdate(CefRefPtr frame, const std::string& message) { + BOOST_LOG_TRIVIAL(trace) << "Sending progress update: " << message; + frame->ExecuteJavaScript("loot.Dialog.showProgress('" + message + "');", frame->GetURL(), 0); +} } diff --git a/src/gui/query_handler.h b/src/gui/query_handler.h index ba56f75c..9f66bf0b 100644 --- a/src/gui/query_handler.h +++ b/src/gui/query_handler.h @@ -25,59 +25,58 @@ #ifndef LOOT_GUI_QUERY_HANDLER #define LOOT_GUI_QUERY_HANDLER +#include +#include + #include "backend/app/loot_state.h" #include "backend/plugin/plugin.h" #include "backend/metadata/plugin_metadata.h" -#include - -#include - namespace loot { - class QueryHandler : public CefMessageRouterBrowserSide::Handler { - public: - QueryHandler(LootState& lootState); +class QueryHandler : public CefMessageRouterBrowserSide::Handler { +public: + QueryHandler(LootState& lootState); - // Called due to cefQuery execution in binding.html. - virtual bool OnQuery(CefRefPtr browser, - CefRefPtr frame, - int64 query_id, - const CefString& request, - bool persistent, - CefRefPtr callback) OVERRIDE; - private: - void OpenReadme(); - void OpenLogLocation(); - std::string GetVersion(); - std::string GetSettings(); - std::string GetLanguages(); - std::string GetGameTypes(); - std::string GetInstalledGames(); - void GetGameData(CefRefPtr frame, CefRefPtr callback); - void UpdateMasterlist(CefRefPtr callback); - std::string ClearAllMetadata(); - void SortPlugins(CefRefPtr frame, CefRefPtr callback); + // Called due to cefQuery execution in binding.html. + virtual bool OnQuery(CefRefPtr browser, + CefRefPtr frame, + int64 query_id, + const CefString& request, + bool persistent, + CefRefPtr callback) OVERRIDE; +private: + void OpenReadme(); + void OpenLogLocation(); + std::string GetVersion(); + std::string GetSettings(); + std::string GetLanguages(); + std::string GetGameTypes(); + std::string GetInstalledGames(); + void GetGameData(CefRefPtr frame, CefRefPtr callback); + void UpdateMasterlist(CefRefPtr callback); + std::string ClearAllMetadata(); + void SortPlugins(CefRefPtr frame, CefRefPtr callback); - // Handle queries with input arguments. - bool HandleComplexQuery(CefRefPtr browser, - CefRefPtr frame, - YAML::Node& request, - CefRefPtr callback); + // Handle queries with input arguments. + bool HandleComplexQuery(CefRefPtr browser, + CefRefPtr frame, + YAML::Node& request, + CefRefPtr callback); - void GetConflictingPlugins(const std::string& pluginName, CefRefPtr callback); - void CopyMetadata(const std::string& pluginName); - std::string ClearPluginMetadata(const std::string& pluginName); - std::string ApplyUserEdits(const YAML::Node& pluginMetadata); + void GetConflictingPlugins(const std::string& pluginName, CefRefPtr callback); + void CopyMetadata(const std::string& pluginName); + std::string ClearPluginMetadata(const std::string& pluginName); + std::string ApplyUserEdits(const YAML::Node& pluginMetadata); - std::vector GetGeneralMessages() const; - YAML::Node GenerateDerivedMetadata(const std::string& pluginName); - YAML::Node GenerateDerivedMetadata(const Plugin& file, const PluginMetadata& masterlist, const PluginMetadata& userlist); + std::vector GetGeneralMessages() const; + YAML::Node GenerateDerivedMetadata(const std::string& pluginName); + YAML::Node GenerateDerivedMetadata(const Plugin& file, const PluginMetadata& masterlist, const PluginMetadata& userlist); - void CopyToClipboard(const std::string& text); - void SendProgressUpdate(CefRefPtr frame, const std::string& message); + void CopyToClipboard(const std::string& text); + void SendProgressUpdate(CefRefPtr frame, const std::string& message); - LootState& _lootState; - }; + LootState& lootState_; +}; } #endif diff --git a/src/tests/api/api_game_operations_test.h b/src/tests/api/api_game_operations_test.h index be8f3158..00871a99 100644 --- a/src/tests/api/api_game_operations_test.h +++ b/src/tests/api/api_game_operations_test.h @@ -22,100 +22,102 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_API_GAME_OPERATIONS_TEST -#define LOOT_TEST_API_GAME_OPERATIONS_TEST +#ifndef LOOT_TESTS_API_API_GAME_OPERATIONS_TEST +#define LOOT_TESTS_API_API_GAME_OPERATIONS_TEST + +#include "loot/api.h" #include "tests/common_game_test_fixture.h" namespace loot { - namespace test { - class ApiGameOperationsTest : - public ::testing::TestWithParam, - public CommonGameTestFixture { - protected: - ApiGameOperationsTest() : - CommonGameTestFixture(GetParam()), - db(nullptr), - masterlistPath(localPath / "masterlist.yaml"), - noteMessage("Do not clean ITM records, they are intentional and required for the mod to function."), - warningMessage("Check you are using v2+. If not, Update. v1 has a severe bug with the Mystic Emporium disappearing."), - errorMessage("Obsolete. Remove this and install Enhanced Weather.") {} +namespace test { +class ApiGameOperationsTest : + public ::testing::TestWithParam, + public CommonGameTestFixture { +protected: + ApiGameOperationsTest() : + CommonGameTestFixture(GetParam()), + db_(nullptr), + masterlistPath(localPath / "masterlist.yaml"), + noteMessage("Do not clean ITM records, they are intentional and required for the mod to function."), + warningMessage("Check you are using v2+. If not, Update. v1 has a severe bug with the Mystic Emporium disappearing."), + errorMessage("Obsolete. Remove this and install Enhanced Weather.") {} - inline virtual void SetUp() { - setUp(); + virtual void SetUp() { + setUp(); - ASSERT_FALSE(boost::filesystem::exists(masterlistPath)); + ASSERT_FALSE(boost::filesystem::exists(masterlistPath)); - ASSERT_EQ(loot_ok, loot_create_db(&db, GetParam(), dataPath.parent_path().string().c_str(), localPath.string().c_str())); - } + ASSERT_EQ(loot_ok, loot_create_db(&db_, GetParam(), dataPath.parent_path().string().c_str(), localPath.string().c_str())); + } - inline virtual void TearDown() { - tearDown(); + virtual void TearDown() { + tearDown(); - ASSERT_NO_THROW(loot_destroy_db(db)); + ASSERT_NO_THROW(loot_destroy_db(db_)); - // The masterlist may have been created during the test, so delete it. - ASSERT_NO_THROW(boost::filesystem::remove(masterlistPath)); - } + // The masterlist may have been created during the test, so delete it. + ASSERT_NO_THROW(boost::filesystem::remove(masterlistPath)); + } - inline void generateMasterlist() { - using std::endl; + void GenerateMasterlist() { + using std::endl; - boost::filesystem::ofstream masterlist(masterlistPath); - masterlist - << "plugins:" << endl - << " - name: " << blankEsm << endl - << " after:" << endl - << " - " << masterFile << endl - << " msg:" << endl - << " - type: say" << endl - << " content: '" << noteMessage << "'" << endl - << " tag:" << endl - << " - Actors.ACBS" << endl - << " - Actors.AIData" << endl - << " - '-C.Water'" << endl - << " - name: " << blankDifferentEsm << endl - << " after:" << endl - << " - " << blankMasterDependentEsm << endl - << " msg:" << endl - << " - type: warn" << endl - << " content: '" << warningMessage << "'" << endl - << " dirty:" << endl - << " - crc: 0x7d22f9df" << endl - << " util: TES4Edit" << endl - << " udr: 4" << endl - << " - name: " << blankDifferentEsp << endl - << " after:" << endl - << " - " << blankPluginDependentEsp << endl - << " msg:" << endl - << " - type: error" << endl - << " content: '" << errorMessage << "'" << endl - << " - name: " << blankEsp << endl - << " after:" << endl - << " - " << blankDifferentMasterDependentEsp << endl - << " - name: " << blankDifferentMasterDependentEsp << endl - << " after:" << endl - << " - " << blankMasterDependentEsp << endl - << " msg:" << endl - << " - type: say" << endl - << " content: '" << noteMessage << "'" << endl - << " - type: warn" << endl - << " content: '" << warningMessage << "'" << endl - << " - type: error" << endl - << " content: '" << errorMessage << "'" << endl; + boost::filesystem::ofstream masterlist(masterlistPath); + masterlist + << "plugins:" << endl + << " - name: " << blankEsm << endl + << " after:" << endl + << " - " << masterFile << endl + << " msg:" << endl + << " - type: say" << endl + << " content: '" << noteMessage << "'" << endl + << " tag:" << endl + << " - Actors.ACBS" << endl + << " - Actors.AIData" << endl + << " - '-C.Water'" << endl + << " - name: " << blankDifferentEsm << endl + << " after:" << endl + << " - " << blankMasterDependentEsm << endl + << " msg:" << endl + << " - type: warn" << endl + << " content: '" << warningMessage << "'" << endl + << " dirty:" << endl + << " - crc: 0x7d22f9df" << endl + << " util: TES4Edit" << endl + << " udr: 4" << endl + << " - name: " << blankDifferentEsp << endl + << " after:" << endl + << " - " << blankPluginDependentEsp << endl + << " msg:" << endl + << " - type: error" << endl + << " content: '" << errorMessage << "'" << endl + << " - name: " << blankEsp << endl + << " after:" << endl + << " - " << blankDifferentMasterDependentEsp << endl + << " - name: " << blankDifferentMasterDependentEsp << endl + << " after:" << endl + << " - " << blankMasterDependentEsp << endl + << " msg:" << endl + << " - type: say" << endl + << " content: '" << noteMessage << "'" << endl + << " - type: warn" << endl + << " content: '" << warningMessage << "'" << endl + << " - type: error" << endl + << " content: '" << errorMessage << "'" << endl; - masterlist.close(); - } + masterlist.close(); + } - loot_db * db; + loot_db * db_; - const boost::filesystem::path masterlistPath; + const boost::filesystem::path masterlistPath; - const std::string noteMessage; - const std::string warningMessage; - const std::string errorMessage; - }; - } + const std::string noteMessage; + const std::string warningMessage; + const std::string errorMessage; +}; +} } #endif diff --git a/src/tests/api/loot_apply_load_order_test.h b/src/tests/api/loot_apply_load_order_test.h index e798890f..94a12441 100644 --- a/src/tests/api/loot_apply_load_order_test.h +++ b/src/tests/api/loot_apply_load_order_test.h @@ -22,56 +22,57 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_APPLY_LOAD_ORDER -#define LOOT_TEST_LOOT_APPLY_LOAD_ORDER +#ifndef LOOT_TESTS_API_LOOT_APPLY_LOAD_ORDER_TEST +#define LOOT_TESTS_API_LOOT_APPLY_LOAD_ORDER_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_apply_load_order_test : public ApiGameOperationsTest {}; +namespace test { +class loot_apply_load_order_test : public ApiGameOperationsTest {}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_apply_load_order_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_apply_load_order_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_apply_load_order_test, shouldReturnAnInvalidArgsIfTheDbOrLoadOrderPointersAreNull) { - const char * loadOrder[1] = { - masterFile.c_str(), - }; - size_t numPlugins = 0; +TEST_P(loot_apply_load_order_test, shouldReturnAnInvalidArgsIfTheDbOrLoadOrderPointersAreNull) { + const char * loadOrder[1] = { + masterFile.c_str(), + }; + size_t numPlugins = 0; - EXPECT_EQ(loot_error_invalid_args, loot_apply_load_order(NULL, loadOrder, numPlugins)); - EXPECT_EQ(loot_error_invalid_args, loot_apply_load_order(db, NULL, numPlugins)); - } + EXPECT_EQ(loot_error_invalid_args, loot_apply_load_order(NULL, loadOrder, numPlugins)); + EXPECT_EQ(loot_error_invalid_args, loot_apply_load_order(db_, NULL, numPlugins)); +} - TEST_P(loot_apply_load_order_test, shouldReturnOkIfLoadOrderGivenIsNotEmpty) { - const char * loadOrder[11] = { - masterFile.c_str(), - blankEsm.c_str(), - blankMasterDependentEsm.c_str(), - blankDifferentEsm.c_str(), - blankDifferentMasterDependentEsm.c_str(), - blankMasterDependentEsp.c_str(), - blankDifferentMasterDependentEsp.c_str(), - blankEsp.c_str(), - blankPluginDependentEsp.c_str(), - blankDifferentEsp.c_str(), - blankDifferentPluginDependentEsp.c_str(), - }; - size_t numPlugins = 11; +TEST_P(loot_apply_load_order_test, shouldReturnOkIfLoadOrderGivenIsNotEmpty) { + const char * loadOrder[11] = { + masterFile.c_str(), + blankEsm.c_str(), + blankMasterDependentEsm.c_str(), + blankDifferentEsm.c_str(), + blankDifferentMasterDependentEsm.c_str(), + blankMasterDependentEsp.c_str(), + blankDifferentMasterDependentEsp.c_str(), + blankEsp.c_str(), + blankPluginDependentEsp.c_str(), + blankDifferentEsp.c_str(), + blankDifferentPluginDependentEsp.c_str(), + }; + size_t numPlugins = 11; - EXPECT_EQ(loot_ok, loot_apply_load_order(db, loadOrder, numPlugins)); - } - } + EXPECT_EQ(loot_ok, loot_apply_load_order(db_, loadOrder, numPlugins)); +} +} } #endif diff --git a/src/tests/api/loot_create_db_test.h b/src/tests/api/loot_create_db_test.h index f8f070aa..d4594b87 100644 --- a/src/tests/api/loot_create_db_test.h +++ b/src/tests/api/loot_create_db_test.h @@ -22,83 +22,84 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_CREATE_DB -#define LOOT_TEST_LOOT_CREATE_DB +#ifndef LOOT_TESTS_API_LOOT_CREATE_DB_TEST +#define LOOT_TESTS_API_LOOT_CREATE_DB_TEST -#include "../include/loot/api.h" -#include "tests/common_game_test_fixture.h" +#include "loot/api.h" #include +#include "tests/common_game_test_fixture.h" + namespace loot { - namespace test { - class loot_create_db_test : - public ::testing::TestWithParam, - public CommonGameTestFixture { - protected: - loot_create_db_test() : - CommonGameTestFixture(GetParam()), - db(nullptr) {} +namespace test { +class loot_create_db_test : + public ::testing::TestWithParam, + public CommonGameTestFixture { +protected: + loot_create_db_test() : + CommonGameTestFixture(GetParam()), + db_(nullptr) {} - void SetUp() { - setUp(); - } + void SetUp() { + setUp(); + } - void TearDown() { - tearDown(); + void TearDown() { + tearDown(); - ASSERT_NO_THROW(loot_destroy_db(db)); - } + ASSERT_NO_THROW(loot_destroy_db(db_)); + } - loot_db * db; - }; + loot_db * db_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_create_db_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_create_db_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_create_db_test, shouldSucceedIfPassedValidParametersWithRelativePaths) { - EXPECT_EQ(loot_ok, loot_create_db(&db, GetParam(), dataPath.parent_path().string().c_str(), localPath.string().c_str())); - EXPECT_NE(nullptr, db); - } +TEST_P(loot_create_db_test, shouldSucceedIfPassedValidParametersWithRelativePaths) { + EXPECT_EQ(loot_ok, loot_create_db(&db_, GetParam(), dataPath.parent_path().string().c_str(), localPath.string().c_str())); + EXPECT_NE(nullptr, db_); +} - TEST_P(loot_create_db_test, shouldSucceedIfPassedValidParametersWithAbsolutePaths) { - boost::filesystem::path game = boost::filesystem::current_path() / dataPath.parent_path(); - boost::filesystem::path local = boost::filesystem::current_path() / localPath; +TEST_P(loot_create_db_test, shouldSucceedIfPassedValidParametersWithAbsolutePaths) { + boost::filesystem::path game = boost::filesystem::current_path() / dataPath.parent_path(); + boost::filesystem::path local = boost::filesystem::current_path() / localPath; - EXPECT_EQ(loot_ok, loot_create_db(&db, GetParam(), game.string().c_str(), local.string().c_str())); - EXPECT_NE(nullptr, db); - } + EXPECT_EQ(loot_ok, loot_create_db(&db_, GetParam(), game.string().c_str(), local.string().c_str())); + EXPECT_NE(nullptr, db_); +} - TEST_P(loot_create_db_test, shouldReturnAnInvalidArgsErrorIfPassedANullPointer) { - EXPECT_EQ(loot_error_invalid_args, loot_create_db(NULL, GetParam(), dataPath.parent_path().string().c_str(), localPath.string().c_str())); - } +TEST_P(loot_create_db_test, shouldReturnAnInvalidArgsErrorIfPassedANullPointer) { + EXPECT_EQ(loot_error_invalid_args, loot_create_db(NULL, GetParam(), dataPath.parent_path().string().c_str(), localPath.string().c_str())); +} - TEST_P(loot_create_db_test, shouldReturnAnInvalidArgsErrorIfPassedAnInvalidGameType) { - EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db, UINT_MAX, dataPath.parent_path().string().c_str(), localPath.string().c_str())); - } +TEST_P(loot_create_db_test, shouldReturnAnInvalidArgsErrorIfPassedAnInvalidGameType) { + EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db_, UINT_MAX, dataPath.parent_path().string().c_str(), localPath.string().c_str())); +} - TEST_P(loot_create_db_test, shouldReturnAnInvalidArgsErrorIfPassedAGamePathThatDoesNotExist) { - EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db, GetParam(), missingPath.string().c_str(), localPath.string().c_str())); - } +TEST_P(loot_create_db_test, shouldReturnAnInvalidArgsErrorIfPassedAGamePathThatDoesNotExist) { + EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db_, GetParam(), missingPath.string().c_str(), localPath.string().c_str())); +} - TEST_P(loot_create_db_test, shouldReturnAnInvalidArgsErrorIfPassedALocalPathThatDoesNotExist) { - EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db, GetParam(), dataPath.parent_path().string().c_str(), missingPath.string().c_str())); - } +TEST_P(loot_create_db_test, shouldReturnAnInvalidArgsErrorIfPassedALocalPathThatDoesNotExist) { + EXPECT_EQ(loot_error_invalid_args, loot_create_db(&db_, GetParam(), dataPath.parent_path().string().c_str(), missingPath.string().c_str())); +} #ifdef _WIN32 - TEST_P(loot_create_db_test, shouldReturnOkIfPassedANullLocalPathPointer) { - EXPECT_EQ(loot_ok, loot_create_db(&db, GetParam(), dataPath.parent_path().string().c_str(), NULL)); - } +TEST_P(loot_create_db_test, shouldReturnOkIfPassedANullLocalPathPointer) { + EXPECT_EQ(loot_ok, loot_create_db(&db_, GetParam(), dataPath.parent_path().string().c_str(), NULL)); +} #endif - } +} } #endif diff --git a/src/tests/api/loot_db_test.h b/src/tests/api/loot_db_test.h index aa63ac92..e14fb805 100644 --- a/src/tests/api/loot_db_test.h +++ b/src/tests/api/loot_db_test.h @@ -22,303 +22,306 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_API_LOOT_DB_INT -#define LOOT_TEST_API_LOOT_DB_INT +#ifndef LOOT_TESTS_API_LOOT_DB_TEST +#define LOOT_TESTS_API_LOOT_DB_TEST #include "api/loot_db.h" -#include "backend/game/game_settings.h" -#include "tests/backend/base_game_test.h" + +#include "tests/common_game_test_fixture.h" namespace loot { - namespace test { - class loot_db_test : public BaseGameTest { - protected: - loot_db_test() : - db(nullptr) {} +namespace test { +class loot_db_test : + public ::testing::TestWithParam, + public CommonGameTestFixture { +protected: + loot_db_test() : + CommonGameTestFixture(GetParam()), + db_(nullptr) {} - virtual void SetUp() { - BaseGameTest::SetUp(); + virtual void SetUp() { + setUp(); - db = new loot_db(static_cast(GetParam()), dataPath.parent_path().string().c_str(), localPath.string().c_str()); - } + db_ = new loot_db(static_cast(GetParam()), dataPath.parent_path().string().c_str(), localPath.string().c_str()); + } - inline virtual void TearDown() { - BaseGameTest::TearDown(); + inline virtual void TearDown() { + tearDown(); - delete db; - } + delete db_; + } - loot_db * db; - }; + loot_db * db_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_db_test, - ::testing::Values( - GameType::tes4, - GameType::tes5, - GameType::fo3, - GameType::fonv, - GameType::fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_db_test, + ::testing::Values( + static_cast(GameType::tes4), + static_cast(GameType::tes5), + static_cast(GameType::fo3), + static_cast(GameType::fonv), + static_cast(GameType::fo4))); - TEST_P(loot_db_test, settingRevisionIdStringShouldCopyIt) { - db->setRevisionIdString("id"); - EXPECT_STREQ("id", db->getRevisionIdString()); - } +TEST_P(loot_db_test, settingRevisionIdStringShouldCopyIt) { + db_->setRevisionIdString("id"); + EXPECT_STREQ("id", db_->getRevisionIdString()); +} - TEST_P(loot_db_test, settingRevisionDateStringShouldCopyIt) { - db->setRevisionDateString("date"); - EXPECT_STREQ("date", db->getRevisionDateString()); - } +TEST_P(loot_db_test, settingRevisionDateStringShouldCopyIt) { + db_->setRevisionDateString("date"); + EXPECT_STREQ("date", db_->getRevisionDateString()); +} - TEST_P(loot_db_test, settingPluginNamesShouldCopyThem) { - db->setPluginNames(std::vector({ - PluginMetadata("Blank.esm"), - PluginMetadata("Blank.esp"), - })); +TEST_P(loot_db_test, settingPluginNamesShouldCopyThem) { + db_->setPluginNames(std::vector({ + PluginMetadata("Blank.esm"), + PluginMetadata("Blank.esp"), + })); - EXPECT_EQ(2, db->getPluginNames().size()); - EXPECT_STREQ("Blank.esm", db->getPluginNames()[0]); - EXPECT_STREQ("Blank.esp", db->getPluginNames()[1]); - } + EXPECT_EQ(2, db_->getPluginNames().size()); + EXPECT_STREQ("Blank.esm", db_->getPluginNames()[0]); + EXPECT_STREQ("Blank.esp", db_->getPluginNames()[1]); +} - TEST_P(loot_db_test, settingPluginNamesTwiceShouldOverwriteTheFirstDataSet) { - db->setPluginNames(std::vector({ - PluginMetadata("Blank.esm"), - PluginMetadata("Blank.esp"), - })); - db->setPluginNames(std::vector({ - PluginMetadata("Blank - Different.esm"), - PluginMetadata("Blank - Different.esp"), - })); +TEST_P(loot_db_test, settingPluginNamesTwiceShouldOverwriteTheFirstDataSet) { + db_->setPluginNames(std::vector({ + PluginMetadata("Blank.esm"), + PluginMetadata("Blank.esp"), + })); + db_->setPluginNames(std::vector({ + PluginMetadata("Blank - Different.esm"), + PluginMetadata("Blank - Different.esp"), + })); - EXPECT_EQ(2, db->getPluginNames().size()); - EXPECT_STREQ("Blank - Different.esm", db->getPluginNames()[0]); - EXPECT_STREQ("Blank - Different.esp", db->getPluginNames()[1]); - } + EXPECT_EQ(2, db_->getPluginNames().size()); + EXPECT_STREQ("Blank - Different.esm", db_->getPluginNames()[0]); + EXPECT_STREQ("Blank - Different.esp", db_->getPluginNames()[1]); +} - TEST_P(loot_db_test, addingNewBashTagsToTheMapShouldAppendThem) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); +TEST_P(loot_db_test, addingNewBashTagsToTheMapShouldAppendThem) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); - EXPECT_EQ(2, db->getBashTagMap().size()); - EXPECT_STREQ("C.Climate", db->getBashTagMap()[0]); - EXPECT_STREQ("Relev", db->getBashTagMap()[1]); - } + EXPECT_EQ(2, db_->getBashTagMap().size()); + EXPECT_STREQ("C.Climate", db_->getBashTagMap()[0]); + EXPECT_STREQ("Relev", db_->getBashTagMap()[1]); +} - TEST_P(loot_db_test, addingAnExistingBashTagToTheMapShouldNotDuplicateIt) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - "C.Climate", - }); +TEST_P(loot_db_test, addingAnExistingBashTagToTheMapShouldNotDuplicateIt) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + "C.Climate", + }); - EXPECT_EQ(2, db->getBashTagMap().size()); - EXPECT_STREQ("C.Climate", db->getBashTagMap()[0]); - EXPECT_STREQ("Relev", db->getBashTagMap()[1]); - } + EXPECT_EQ(2, db_->getBashTagMap().size()); + EXPECT_STREQ("C.Climate", db_->getBashTagMap()[0]); + EXPECT_STREQ("Relev", db_->getBashTagMap()[1]); +} - TEST_P(loot_db_test, gettingABashTagsUidForATagThatIsNotInTheMapShouldThrow) { - EXPECT_ANY_THROW(db->getBashTagUid("Relev")); - } +TEST_P(loot_db_test, gettingABashTagsUidForATagThatIsNotInTheMapShouldThrow) { + EXPECT_ANY_THROW(db_->getBashTagUid("Relev")); +} - TEST_P(loot_db_test, gettingABashTagsUidShouldReturnItsTagMapIndex) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); +TEST_P(loot_db_test, gettingABashTagsUidShouldReturnItsTagMapIndex) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); - EXPECT_EQ(1, db->getBashTagUid("Relev")); - } + EXPECT_EQ(1, db_->getBashTagUid("Relev")); +} - TEST_P(loot_db_test, clearingAnEmptyBashTagMapShouldDoNothing) { - EXPECT_NO_THROW(db->clearBashTagMap()); - } +TEST_P(loot_db_test, clearingAnEmptyBashTagMapShouldDoNothing) { + EXPECT_NO_THROW(db_->clearBashTagMap()); +} - TEST_P(loot_db_test, clearingABashTagMapShouldEmptyIt) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); +TEST_P(loot_db_test, clearingABashTagMapShouldEmptyIt) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); - db->clearBashTagMap(); - EXPECT_TRUE(db->getBashTagMap().empty()); - } + db_->clearBashTagMap(); + EXPECT_TRUE(db_->getBashTagMap().empty()); +} - TEST_P(loot_db_test, clearingABashTagMapShouldAffectExistingReferences) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); +TEST_P(loot_db_test, clearingABashTagMapShouldAffectExistingReferences) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); - auto& bashTagMap = db->getBashTagMap(); - db->clearBashTagMap(); - EXPECT_TRUE(bashTagMap.empty()); - } + auto& bashTagMap = db_->getBashTagMap(); + db_->clearBashTagMap(); + EXPECT_TRUE(bashTagMap.empty()); +} - TEST_P(loot_db_test, settingAddedTagsWithNoTagMapShouldThrow) { - EXPECT_ANY_THROW(db->setAddedTags({ - "Relev", - })); - } +TEST_P(loot_db_test, settingAddedTagsWithNoTagMapShouldThrow) { + EXPECT_ANY_THROW(db_->setAddedTags({ + "Relev", + })); +} - TEST_P(loot_db_test, gettingSetAddedTagsShouldReturnTheirUids) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); +TEST_P(loot_db_test, gettingSetAddedTagsShouldReturnTheirUids) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); - db->setAddedTags({ - "Relev", - }); + db_->setAddedTags({ + "Relev", + }); - EXPECT_EQ(std::vector({ - 1, - }), db->getAddedTagIds()); - } + EXPECT_EQ(std::vector({ + 1, + }), db_->getAddedTagIds()); +} - TEST_P(loot_db_test, settingAddedTagsShouldReplaceExistingTags) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); +TEST_P(loot_db_test, settingAddedTagsShouldReplaceExistingTags) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); - db->setAddedTags({ - "Relev", - }); + db_->setAddedTags({ + "Relev", + }); - db->setAddedTags({ - "C.Climate", - }); + db_->setAddedTags({ + "C.Climate", + }); - EXPECT_EQ(std::vector({ - 0, - }), db->getAddedTagIds()); - } + EXPECT_EQ(std::vector({ + 0, + }), db_->getAddedTagIds()); +} - TEST_P(loot_db_test, settingRemovedTagsWithNoTagMapShouldThrow) { - EXPECT_ANY_THROW(db->setRemovedTags({ - "Relev", - })); - } +TEST_P(loot_db_test, settingRemovedTagsWithNoTagMapShouldThrow) { + EXPECT_ANY_THROW(db_->setRemovedTags({ + "Relev", + })); +} - TEST_P(loot_db_test, gettingSetRemovedTagsShouldReturnTheirUids) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); +TEST_P(loot_db_test, gettingSetRemovedTagsShouldReturnTheirUids) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); - db->setRemovedTags({ - "Relev", - }); + db_->setRemovedTags({ + "Relev", + }); - EXPECT_EQ(std::vector({ - 1, - }), db->getRemovedTagIds()); - } + EXPECT_EQ(std::vector({ + 1, + }), db_->getRemovedTagIds()); +} - TEST_P(loot_db_test, settingRemovedTagsShouldReplaceExistingTags) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); +TEST_P(loot_db_test, settingRemovedTagsShouldReplaceExistingTags) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); - db->setRemovedTags({ - "Relev", - }); + db_->setRemovedTags({ + "Relev", + }); - db->setRemovedTags({ - "C.Climate", - }); + db_->setRemovedTags({ + "C.Climate", + }); - EXPECT_EQ(std::vector({ - 0, - }), db->getRemovedTagIds()); - } + EXPECT_EQ(std::vector({ + 0, + }), db_->getRemovedTagIds()); +} - TEST_P(loot_db_test, settingPluginMessagesShouldCopyThem) { - db->setPluginMessages(std::list({ - Message(Message::Type::warn, "Test 1"), - Message(Message::Type::error, "Test 2"), - })); +TEST_P(loot_db_test, settingPluginMessagesShouldCopyThem) { + db_->setPluginMessages(std::list({ + Message(Message::Type::warn, "Test 1"), + Message(Message::Type::error, "Test 2"), + })); - EXPECT_EQ(2, db->getPluginMessages().size()); - EXPECT_EQ(static_cast(Message::Type::warn), db->getPluginMessages()[0].type); - EXPECT_STREQ("Test 1", db->getPluginMessages()[0].message); - EXPECT_EQ(static_cast(Message::Type::error), db->getPluginMessages()[1].type); - EXPECT_STREQ("Test 2", db->getPluginMessages()[1].message); - } + EXPECT_EQ(2, db_->getPluginMessages().size()); + EXPECT_EQ(static_cast(Message::Type::warn), db_->getPluginMessages()[0].type); + EXPECT_STREQ("Test 1", db_->getPluginMessages()[0].message); + EXPECT_EQ(static_cast(Message::Type::error), db_->getPluginMessages()[1].type); + EXPECT_STREQ("Test 2", db_->getPluginMessages()[1].message); +} - TEST_P(loot_db_test, settingPluginMessagesTwiceShouldOverwriteTheFirstDataSet) { - db->setPluginMessages(std::list({ - Message(Message::Type::warn, "Test 1"), - Message(Message::Type::error, "Test 2"), - })); - db->setPluginMessages(std::list({ - Message(Message::Type::error, "Test 3"), - Message(Message::Type::warn, "Test 4"), - Message(Message::Type::say, "Test 5"), - })); +TEST_P(loot_db_test, settingPluginMessagesTwiceShouldOverwriteTheFirstDataSet) { + db_->setPluginMessages(std::list({ + Message(Message::Type::warn, "Test 1"), + Message(Message::Type::error, "Test 2"), + })); + db_->setPluginMessages(std::list({ + Message(Message::Type::error, "Test 3"), + Message(Message::Type::warn, "Test 4"), + Message(Message::Type::say, "Test 5"), + })); - EXPECT_EQ(3, db->getPluginMessages().size()); - EXPECT_EQ(static_cast(Message::Type::error), db->getPluginMessages()[0].type); - EXPECT_STREQ("Test 3", db->getPluginMessages()[0].message); - EXPECT_EQ(static_cast(Message::Type::warn), db->getPluginMessages()[1].type); - EXPECT_STREQ("Test 4", db->getPluginMessages()[1].message); - EXPECT_EQ(static_cast(Message::Type::say), db->getPluginMessages()[2].type); - EXPECT_STREQ("Test 5", db->getPluginMessages()[2].message); - } + EXPECT_EQ(3, db_->getPluginMessages().size()); + EXPECT_EQ(static_cast(Message::Type::error), db_->getPluginMessages()[0].type); + EXPECT_STREQ("Test 3", db_->getPluginMessages()[0].message); + EXPECT_EQ(static_cast(Message::Type::warn), db_->getPluginMessages()[1].type); + EXPECT_STREQ("Test 4", db_->getPluginMessages()[1].message); + EXPECT_EQ(static_cast(Message::Type::say), db_->getPluginMessages()[2].type); + EXPECT_STREQ("Test 5", db_->getPluginMessages()[2].message); +} - TEST_P(loot_db_test, clearingArraysShouldEmptyPluginNamesTagIdsAndMessages) { - db->setPluginMessages(std::list({ - Message(Message::Type::warn, "Test 1"), - Message(Message::Type::error, "Test 2"), - })); - db->setPluginNames(std::vector({ - PluginMetadata("Blank.esm"), - PluginMetadata("Blank.esp"), - })); +TEST_P(loot_db_test, clearingArraysShouldEmptyPluginNamesTagIdsAndMessages) { + db_->setPluginMessages(std::list({ + Message(Message::Type::warn, "Test 1"), + Message(Message::Type::error, "Test 2"), + })); + db_->setPluginNames(std::vector({ + PluginMetadata("Blank.esm"), + PluginMetadata("Blank.esp"), + })); - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); - db->setAddedTags({ - "Relev", - }); - db->setRemovedTags({ - "Relev", - }); + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); + db_->setAddedTags({ + "Relev", + }); + db_->setRemovedTags({ + "Relev", + }); - ASSERT_FALSE(db->getPluginMessages().empty()); - ASSERT_FALSE(db->getPluginNames().empty()); - ASSERT_FALSE(db->getAddedTagIds().empty()); - ASSERT_FALSE(db->getRemovedTagIds().empty()); - ASSERT_FALSE(db->getBashTagMap().empty()); + ASSERT_FALSE(db_->getPluginMessages().empty()); + ASSERT_FALSE(db_->getPluginNames().empty()); + ASSERT_FALSE(db_->getAddedTagIds().empty()); + ASSERT_FALSE(db_->getRemovedTagIds().empty()); + ASSERT_FALSE(db_->getBashTagMap().empty()); - EXPECT_NO_THROW(db->clearArrays()); + EXPECT_NO_THROW(db_->clearArrays()); - EXPECT_TRUE(db->getPluginMessages().empty()); - EXPECT_TRUE(db->getPluginNames().empty()); - EXPECT_TRUE(db->getAddedTagIds().empty()); - EXPECT_TRUE(db->getRemovedTagIds().empty()); - } + EXPECT_TRUE(db_->getPluginMessages().empty()); + EXPECT_TRUE(db_->getPluginNames().empty()); + EXPECT_TRUE(db_->getAddedTagIds().empty()); + EXPECT_TRUE(db_->getRemovedTagIds().empty()); +} - TEST_P(loot_db_test, clearingArraysShouldNotEmptyBashTagMap) { - db->addBashTagsToMap({ - "C.Climate", - "Relev", - }); - ASSERT_FALSE(db->getBashTagMap().empty()); +TEST_P(loot_db_test, clearingArraysShouldNotEmptyBashTagMap) { + db_->addBashTagsToMap({ + "C.Climate", + "Relev", + }); + ASSERT_FALSE(db_->getBashTagMap().empty()); - EXPECT_NO_THROW(db->clearArrays()); + EXPECT_NO_THROW(db_->clearArrays()); - EXPECT_FALSE(db->getBashTagMap().empty()); - } - } + EXPECT_FALSE(db_->getBashTagMap().empty()); +} +} } #endif diff --git a/src/tests/api/loot_eval_lists_test.h b/src/tests/api/loot_eval_lists_test.h index bca329fc..9b802d4b 100644 --- a/src/tests/api/loot_eval_lists_test.h +++ b/src/tests/api/loot_eval_lists_test.h @@ -22,66 +22,67 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_EVAL_LISTS -#define LOOT_TEST_LOOT_EVAL_LISTS +#ifndef LOOT_TESTS_API_LOOT_EVAL_LISTS_TEST +#define LOOT_TESTS_API_LOOT_EVAL_LISTS_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_eval_lists_test : public ApiGameOperationsTest {}; +namespace test { +class loot_eval_lists_test : public ApiGameOperationsTest {}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_eval_lists_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_eval_lists_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_eval_lists_test, shouldReturnAnInvalidArgsErrorIfPassedANullPointer) { - EXPECT_EQ(loot_error_invalid_args, loot_eval_lists(NULL, loot_lang_english)); - } +TEST_P(loot_eval_lists_test, shouldReturnAnInvalidArgsErrorIfPassedANullPointer) { + EXPECT_EQ(loot_error_invalid_args, loot_eval_lists(NULL, loot_lang_english)); +} - TEST_P(loot_eval_lists_test, shouldReturnAnInvalidArgsErrorIfPassedAnInvalidLanguageCode) { - EXPECT_EQ(loot_error_invalid_args, loot_eval_lists(db, UINT_MAX)); - } +TEST_P(loot_eval_lists_test, shouldReturnAnInvalidArgsErrorIfPassedAnInvalidLanguageCode) { + EXPECT_EQ(loot_error_invalid_args, loot_eval_lists(db_, UINT_MAX)); +} - TEST_P(loot_eval_lists_test, shouldReturnOkForAllLanguagesWithNoListsLoaded) { - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_english)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_english)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_spanish)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_russian)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_french)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_chinese)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_polish)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_brazilian_portuguese)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_finnish)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_german)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_danish)); - } +TEST_P(loot_eval_lists_test, shouldReturnOkForAllLanguagesWithNoListsLoaded) { + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_english)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_english)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_spanish)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_russian)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_french)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_chinese)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_polish)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_brazilian_portuguese)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_finnish)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_german)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_danish)); +} - TEST_P(loot_eval_lists_test, shouldReturnOKForAllLanguagesWithAMasterlistLoaded) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_eval_lists_test, shouldReturnOKForAllLanguagesWithAMasterlistLoaded) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_english)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_english)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_spanish)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_russian)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_french)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_chinese)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_polish)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_brazilian_portuguese)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_finnish)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_german)); - EXPECT_EQ(loot_ok, loot_eval_lists(db, loot_lang_danish)); - } - } + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_english)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_english)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_spanish)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_russian)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_french)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_chinese)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_polish)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_brazilian_portuguese)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_finnish)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_german)); + EXPECT_EQ(loot_ok, loot_eval_lists(db_, loot_lang_danish)); +} +} } #endif diff --git a/src/tests/api/loot_get_dirty_info_test.h b/src/tests/api/loot_get_dirty_info_test.h index 286512b4..82b3364f 100644 --- a/src/tests/api/loot_get_dirty_info_test.h +++ b/src/tests/api/loot_get_dirty_info_test.h @@ -22,60 +22,61 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_GET_DIRTY_INFO -#define LOOT_TEST_LOOT_GET_DIRTY_INFO +#ifndef LOOT_TEST_API_LOOT_GET_DIRTY_INFO_TEST +#define LOOT_TEST_API_LOOT_GET_DIRTY_INFO_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_get_dirty_info_test : public ApiGameOperationsTest { - protected: - loot_get_dirty_info_test() : - needsCleaning(0) {} +namespace test { +class loot_get_dirty_info_test : public ApiGameOperationsTest { +protected: + loot_get_dirty_info_test() : + needsCleaning_(0) {} - unsigned int needsCleaning; - }; + unsigned int needsCleaning_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_get_dirty_info_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_get_dirty_info_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_get_dirty_info_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_get_dirty_info(NULL, blankEsp.c_str(), &needsCleaning)); - EXPECT_EQ(loot_error_invalid_args, loot_get_dirty_info(db, NULL, &needsCleaning)); - EXPECT_EQ(loot_error_invalid_args, loot_get_dirty_info(db, blankEsp.c_str(), NULL)); - } +TEST_P(loot_get_dirty_info_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { + EXPECT_EQ(loot_error_invalid_args, loot_get_dirty_info(NULL, blankEsp.c_str(), &needsCleaning_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_dirty_info(db_, NULL, &needsCleaning_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_dirty_info(db_, blankEsp.c_str(), NULL)); +} - TEST_P(loot_get_dirty_info_test, shouldReturnOkAndOutputUnknownForAPluginWithNoDirtyInfo) { - EXPECT_EQ(loot_ok, loot_get_dirty_info(db, blankEsp.c_str(), &needsCleaning)); - EXPECT_EQ(loot_needs_cleaning_unknown, needsCleaning); - } +TEST_P(loot_get_dirty_info_test, shouldReturnOkAndOutputUnknownForAPluginWithNoDirtyInfo) { + EXPECT_EQ(loot_ok, loot_get_dirty_info(db_, blankEsp.c_str(), &needsCleaning_)); + EXPECT_EQ(loot_needs_cleaning_unknown, needsCleaning_); +} - TEST_P(loot_get_dirty_info_test, shouldReturnOkAndOutputYesForAPluginWithDirtyInfo) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_get_dirty_info_test, shouldReturnOkAndOutputYesForAPluginWithDirtyInfo) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_get_dirty_info(db, blankDifferentEsm.c_str(), &needsCleaning)); - EXPECT_EQ(loot_needs_cleaning_yes, needsCleaning); - } + EXPECT_EQ(loot_ok, loot_get_dirty_info(db_, blankDifferentEsm.c_str(), &needsCleaning_)); + EXPECT_EQ(loot_needs_cleaning_yes, needsCleaning_); +} - TEST_P(loot_get_dirty_info_test, shouldReturnOkAndOutputNoForAPluginWithADoNotCleanMessage) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_get_dirty_info_test, shouldReturnOkAndOutputNoForAPluginWithADoNotCleanMessage) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_get_dirty_info(db, blankEsm.c_str(), &needsCleaning)); - EXPECT_EQ(loot_needs_cleaning_no, needsCleaning); - } - } + EXPECT_EQ(loot_ok, loot_get_dirty_info(db_, blankEsm.c_str(), &needsCleaning_)); + EXPECT_EQ(loot_needs_cleaning_no, needsCleaning_); +} +} } #endif diff --git a/src/tests/api/loot_get_masterlist_revision_test.h b/src/tests/api/loot_get_masterlist_revision_test.h index 5894c540..2119a917 100644 --- a/src/tests/api/loot_get_masterlist_revision_test.h +++ b/src/tests/api/loot_get_masterlist_revision_test.h @@ -22,98 +22,99 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_GET_MASTERLIST_REVISION -#define LOOT_TEST_LOOT_GET_MASTERLIST_REVISION +#ifndef LOOT_TESTS_API_LOOT_GET_MASTERLIST_REVISION_TEST +#define LOOT_TESTS_API_LOOT_GET_MASTERLIST_REVISION_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_get_masterlist_revision_test : public ApiGameOperationsTest { - protected: - loot_get_masterlist_revision_test() : - revisionId("foo"), - revisionDate("bar"), - isModified(true), - updated(false) {} +namespace test { +class loot_get_masterlist_revision_test : public ApiGameOperationsTest { +protected: + loot_get_masterlist_revision_test() : + revisionId_("foo"), + revisionDate_("bar"), + isModified_(true), + updated_(false) {} - const char * revisionId; - const char * revisionDate; - bool isModified; - bool updated; - }; + const char * revisionId_; + const char * revisionDate_; + bool isModified_; + bool updated_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_get_masterlist_revision_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_get_masterlist_revision_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_get_masterlist_revision_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(NULL, masterlistPath.string().c_str(), false, &revisionId, &revisionDate, &isModified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(db, NULL, false, &revisionId, &revisionDate, &isModified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(db, masterlistPath.string().c_str(), false, NULL, &revisionDate, &isModified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(db, masterlistPath.string().c_str(), false, &revisionId, NULL, &isModified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(db, masterlistPath.string().c_str(), false, &revisionId, &revisionDate, NULL)); - } +TEST_P(loot_get_masterlist_revision_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { + EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(NULL, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(db_, NULL, false, &revisionId_, &revisionDate_, &isModified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, NULL, &revisionDate_, &isModified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, NULL, &isModified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, NULL)); +} - TEST_P(loot_get_masterlist_revision_test, shouldSucceedIfNoMasterlistIsPresent) { - EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db, masterlistPath.string().c_str(), false, &revisionId, &revisionDate, &isModified)); - EXPECT_EQ(NULL, revisionId); - EXPECT_EQ(NULL, revisionDate); - EXPECT_FALSE(isModified); - } +TEST_P(loot_get_masterlist_revision_test, shouldSucceedIfNoMasterlistIsPresent) { + EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); + EXPECT_EQ(NULL, revisionId_); + EXPECT_EQ(NULL, revisionDate_); + EXPECT_FALSE(isModified_); +} - TEST_P(loot_get_masterlist_revision_test, shouldSucceedIfANonVersionControlledMasterlistIsPresent) { - ASSERT_NO_THROW(generateMasterlist()); - EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db, masterlistPath.string().c_str(), false, &revisionId, &revisionDate, &isModified)); - EXPECT_EQ(NULL, revisionId); - EXPECT_EQ(NULL, revisionDate); - EXPECT_FALSE(isModified); - } +TEST_P(loot_get_masterlist_revision_test, shouldSucceedIfANonVersionControlledMasterlistIsPresent) { + ASSERT_NO_THROW(GenerateMasterlist()); + EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); + EXPECT_EQ(NULL, revisionId_); + EXPECT_EQ(NULL, revisionDate_); + EXPECT_FALSE(isModified_); +} - TEST_P(loot_get_masterlist_revision_test, shouldOutputLongStringsAndBooleanFalseIfAVersionControlledMasterlistIsPresentAndGetShortIdParameterIsFalse) { - ASSERT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated)); +TEST_P(loot_get_masterlist_revision_test, shouldOutputLongStringsAndBooleanFalseIfAVersionControlledMasterlistIsPresentAndGetShortIdParameterIsFalse) { + ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); - EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db, masterlistPath.string().c_str(), false, &revisionId, &revisionDate, &isModified)); - EXPECT_STRNE(NULL, revisionId); - EXPECT_EQ(40, strlen(revisionId)); - EXPECT_STRNE(NULL, revisionDate); - EXPECT_EQ(10, strlen(revisionDate)); - EXPECT_FALSE(isModified); - } + EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); + EXPECT_STRNE(NULL, revisionId_); + EXPECT_EQ(40, strlen(revisionId_)); + EXPECT_STRNE(NULL, revisionDate_); + EXPECT_EQ(10, strlen(revisionDate_)); + EXPECT_FALSE(isModified_); +} - TEST_P(loot_get_masterlist_revision_test, shouldOutputShortStringsAndBooleanFalseIfAVersionControlledMasterlistIsPresentAndGetShortIdParameterIsTrue) { - ASSERT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated)); +TEST_P(loot_get_masterlist_revision_test, shouldOutputShortStringsAndBooleanFalseIfAVersionControlledMasterlistIsPresentAndGetShortIdParameterIsTrue) { + ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); - EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db, masterlistPath.string().c_str(), false, &revisionId, &revisionDate, &isModified)); - EXPECT_STRNE(NULL, revisionId); - EXPECT_GE(size_t(40), strlen(revisionId)); - EXPECT_LE(size_t(7), strlen(revisionId)); - EXPECT_STRNE(NULL, revisionDate); - EXPECT_EQ(10, strlen(revisionDate)); - EXPECT_FALSE(isModified); - } + EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); + EXPECT_STRNE(NULL, revisionId_); + EXPECT_GE(size_t(40), strlen(revisionId_)); + EXPECT_LE(size_t(7), strlen(revisionId_)); + EXPECT_STRNE(NULL, revisionDate_); + EXPECT_EQ(10, strlen(revisionDate_)); + EXPECT_FALSE(isModified_); +} - TEST_P(loot_get_masterlist_revision_test, shouldSucceedIfAnEditedVersionControlledMasterlistIsPresent) { - ASSERT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated)); +TEST_P(loot_get_masterlist_revision_test, shouldSucceedIfAnEditedVersionControlledMasterlistIsPresent) { + ASSERT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); - ASSERT_NO_THROW(generateMasterlist()); - EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db, masterlistPath.string().c_str(), false, &revisionId, &revisionDate, &isModified)); + ASSERT_NO_THROW(GenerateMasterlist()); + EXPECT_EQ(loot_ok, loot_get_masterlist_revision(db_, masterlistPath.string().c_str(), false, &revisionId_, &revisionDate_, &isModified_)); - EXPECT_STRNE(NULL, revisionId); - EXPECT_EQ(40, strlen(revisionId)); - EXPECT_STRNE(NULL, revisionDate); - EXPECT_EQ(10, strlen(revisionDate)); - EXPECT_TRUE(isModified); - } - } + EXPECT_STRNE(NULL, revisionId_); + EXPECT_EQ(40, strlen(revisionId_)); + EXPECT_STRNE(NULL, revisionDate_); + EXPECT_EQ(10, strlen(revisionDate_)); + EXPECT_TRUE(isModified_); +} +} } #endif diff --git a/src/tests/api/loot_get_plugin_messages_test.h b/src/tests/api/loot_get_plugin_messages_test.h index 9ddd0283..8bc8351e 100644 --- a/src/tests/api/loot_get_plugin_messages_test.h +++ b/src/tests/api/loot_get_plugin_messages_test.h @@ -22,92 +22,93 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_GET_PLUGIN_MESSAGES -#define LOOT_TEST_LOOT_GET_PLUGIN_MESSAGES +#ifndef LOOT_TESTS_API_LOOT_GET_PLUGIN_MESSAGES_TEST +#define LOOT_TESTS_API_LOOT_GET_PLUGIN_MESSAGES_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_get_plugin_messages_test : public ApiGameOperationsTest { - protected: - loot_get_plugin_messages_test() : - messages(nullptr), - numMessages(0) {} +namespace test { +class loot_get_plugin_messages_test : public ApiGameOperationsTest { +protected: + loot_get_plugin_messages_test() : + messages_(nullptr), + numMessages_(0) {} - const loot_message * messages; - size_t numMessages; - }; + const loot_message * messages_; + size_t numMessages_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_get_plugin_messages_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_get_plugin_messages_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_get_plugin_messages_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_messages(NULL, blankEsp.c_str(), &messages, &numMessages)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_messages(db, NULL, &messages, &numMessages)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_messages(db, blankEsp.c_str(), NULL, &numMessages)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_messages(db, blankEsp.c_str(), &messages, NULL)); - } +TEST_P(loot_get_plugin_messages_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_messages(NULL, blankEsp.c_str(), &messages_, &numMessages_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_messages(db_, NULL, &messages_, &numMessages_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_messages(db_, blankEsp.c_str(), NULL, &numMessages_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_messages(db_, blankEsp.c_str(), &messages_, NULL)); +} - TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputANullArrayIfAPluginWithNoMessagesIsQueried) { - EXPECT_EQ(loot_ok, loot_get_plugin_messages(db, blankEsp.c_str(), &messages, &numMessages)); - EXPECT_EQ(0, numMessages); - EXPECT_EQ(NULL, messages); - } +TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputANullArrayIfAPluginWithNoMessagesIsQueried) { + EXPECT_EQ(loot_ok, loot_get_plugin_messages(db_, blankEsp.c_str(), &messages_, &numMessages_)); + EXPECT_EQ(0, numMessages_); + EXPECT_EQ(NULL, messages_); +} - TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputANoteIfAPluginWithANoteMessageIsQueried) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputANoteIfAPluginWithANoteMessageIsQueried) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_get_plugin_messages(db, blankEsm.c_str(), &messages, &numMessages)); - ASSERT_EQ(1, numMessages); - EXPECT_EQ(loot_message_say, messages[0].type); - EXPECT_STREQ(noteMessage.c_str(), messages[0].message); - } + EXPECT_EQ(loot_ok, loot_get_plugin_messages(db_, blankEsm.c_str(), &messages_, &numMessages_)); + ASSERT_EQ(1, numMessages_); + EXPECT_EQ(loot_message_say, messages_[0].type); + EXPECT_STREQ(noteMessage.c_str(), messages_[0].message); +} - TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputAWarningIfAPluginWithAWarningMessageIsQueried) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputAWarningIfAPluginWithAWarningMessageIsQueried) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_get_plugin_messages(db, blankDifferentEsm.c_str(), &messages, &numMessages)); - ASSERT_EQ(1, numMessages); - EXPECT_EQ(loot_message_warn, messages[0].type); - EXPECT_STREQ(warningMessage.c_str(), messages[0].message); - } + EXPECT_EQ(loot_ok, loot_get_plugin_messages(db_, blankDifferentEsm.c_str(), &messages_, &numMessages_)); + ASSERT_EQ(1, numMessages_); + EXPECT_EQ(loot_message_warn, messages_[0].type); + EXPECT_STREQ(warningMessage.c_str(), messages_[0].message); +} - TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputAnErrorIfAPluginWithAnErrorMessageIsQueried) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputAnErrorIfAPluginWithAnErrorMessageIsQueried) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_get_plugin_messages(db, blankDifferentEsp.c_str(), &messages, &numMessages)); - ASSERT_EQ(1, numMessages); - EXPECT_EQ(loot_message_error, messages[0].type); - EXPECT_STREQ(errorMessage.c_str(), messages[0].message); - } + EXPECT_EQ(loot_ok, loot_get_plugin_messages(db_, blankDifferentEsp.c_str(), &messages_, &numMessages_)); + ASSERT_EQ(1, numMessages_); + EXPECT_EQ(loot_message_error, messages_[0].type); + EXPECT_STREQ(errorMessage.c_str(), messages_[0].message); +} - TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputMultipleMessagesIfAPluginWithMultipleMessagesIsQueried) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_get_plugin_messages_test, shouldReturnOkAndOutputMultipleMessagesIfAPluginWithMultipleMessagesIsQueried) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_get_plugin_messages(db, blankDifferentMasterDependentEsp.c_str(), &messages, &numMessages)); - ASSERT_EQ(3, numMessages); - EXPECT_EQ(loot_message_say, messages[0].type); - EXPECT_STREQ(noteMessage.c_str(), messages[0].message); - EXPECT_EQ(loot_message_warn, messages[1].type); - EXPECT_STREQ(warningMessage.c_str(), messages[1].message); - EXPECT_EQ(loot_message_error, messages[2].type); - EXPECT_STREQ(errorMessage.c_str(), messages[2].message); - } - } + EXPECT_EQ(loot_ok, loot_get_plugin_messages(db_, blankDifferentMasterDependentEsp.c_str(), &messages_, &numMessages_)); + ASSERT_EQ(3, numMessages_); + EXPECT_EQ(loot_message_say, messages_[0].type); + EXPECT_STREQ(noteMessage.c_str(), messages_[0].message); + EXPECT_EQ(loot_message_warn, messages_[1].type); + EXPECT_STREQ(warningMessage.c_str(), messages_[1].message); + EXPECT_EQ(loot_message_error, messages_[2].type); + EXPECT_STREQ(errorMessage.c_str(), messages_[2].message); +} +} } #endif diff --git a/src/tests/api/loot_get_plugin_tags_test.h b/src/tests/api/loot_get_plugin_tags_test.h index 706ace70..b655507a 100644 --- a/src/tests/api/loot_get_plugin_tags_test.h +++ b/src/tests/api/loot_get_plugin_tags_test.h @@ -22,143 +22,144 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_GET_PLUGIN_TAGS -#define LOOT_TEST_LOOT_GET_PLUGIN_TAGS +#ifndef LOOT_TESTS_API_LOOT_GET_PLUGIN_TAGS_TEST +#define LOOT_TESTS_API_LOOT_GET_PLUGIN_TAGS_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_get_plugin_tags_test : public ApiGameOperationsTest { - protected: - loot_get_plugin_tags_test() : - tagMap(nullptr), - numTags(0), - added(nullptr), - removed(nullptr), - numAdded(0), - numRemoved(0), - modified(false) {} +namespace test { +class loot_get_plugin_tags_test : public ApiGameOperationsTest { +protected: + loot_get_plugin_tags_test() : + tagMap_(nullptr), + numTags_(0), + added_(nullptr), + removed_(nullptr), + numAdded_(0), + numRemoved_(0), + modified_(false) {} - const char * const * tagMap; - size_t numTags; + const char * const * tagMap_; + size_t numTags_; - const unsigned int * added; - const unsigned int * removed; - size_t numAdded; - size_t numRemoved; - bool modified; + const unsigned int * added_; + const unsigned int * removed_; + size_t numAdded_; + size_t numRemoved_; + bool modified_; - void getTagMap() { - ASSERT_EQ(loot_ok, loot_get_tag_map(db, &tagMap, &numTags)); + void getTagMap() { + ASSERT_EQ(loot_ok, loot_get_tag_map(db_, &tagMap_, &numTags_)); - ASSERT_EQ(3, numTags); - ASSERT_STREQ("Actors.ACBS", tagMap[0]); - ASSERT_STREQ("Actors.AIData", tagMap[1]); - ASSERT_STREQ("C.Water", tagMap[2]); - } - }; + ASSERT_EQ(3, numTags_); + ASSERT_STREQ("Actors.ACBS", tagMap_[0]); + ASSERT_STREQ("Actors.AIData", tagMap_[1]); + ASSERT_STREQ("C.Water", tagMap_[2]); + } +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_get_plugin_tags_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_get_plugin_tags_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_get_plugin_tags_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(NULL, blankEsm.c_str(), &added, &numAdded, &removed, &numRemoved, &modified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db, NULL, &added, &numAdded, &removed, &numRemoved, &modified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db, blankEsm.c_str(), NULL, &numAdded, &removed, &numRemoved, &modified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db, blankEsm.c_str(), &added, NULL, &removed, &numRemoved, &modified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db, blankEsm.c_str(), &added, &numAdded, NULL, &numRemoved, &modified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db, blankEsm.c_str(), &added, &numAdded, &removed, NULL, &modified)); - EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db, blankEsm.c_str(), &added, &numAdded, &removed, &numRemoved, NULL)); - } +TEST_P(loot_get_plugin_tags_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(NULL, blankEsm.c_str(), &added_, &numAdded_, &removed_, &numRemoved_, &modified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db_, NULL, &added_, &numAdded_, &removed_, &numRemoved_, &modified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db_, blankEsm.c_str(), NULL, &numAdded_, &removed_, &numRemoved_, &modified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db_, blankEsm.c_str(), &added_, NULL, &removed_, &numRemoved_, &modified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db_, blankEsm.c_str(), &added_, &numAdded_, NULL, &numRemoved_, &modified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db_, blankEsm.c_str(), &added_, &numAdded_, &removed_, NULL, &modified_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_plugin_tags(db_, blankEsm.c_str(), &added_, &numAdded_, &removed_, &numRemoved_, NULL)); +} - TEST_P(loot_get_plugin_tags_test, shouldReturnANoTagMapIfCalledBeforeATagMapHasBeenGotten) { - EXPECT_EQ(loot_error_no_tag_map, loot_get_plugin_tags(db, blankEsm.c_str(), &added, &numAdded, &removed, &numRemoved, &modified)); - } +TEST_P(loot_get_plugin_tags_test, shouldReturnANoTagMapIfCalledBeforeATagMapHasBeenGotten) { + EXPECT_EQ(loot_error_no_tag_map, loot_get_plugin_tags(db_, blankEsm.c_str(), &added_, &numAdded_, &removed_, &numRemoved_, &modified_)); +} - TEST_P(loot_get_plugin_tags_test, shouldReturnOkAndOutputEmptyNonModifiedArraysIfAPluginWithoutTagsIsQueried) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); - getTagMap(); +TEST_P(loot_get_plugin_tags_test, shouldReturnOkAndOutputEmptyNonModifiedArraysIfAPluginWithoutTagsIsQueried) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); + getTagMap(); - EXPECT_EQ(loot_ok, loot_get_plugin_tags(db, blankEsp.c_str(), &added, &numAdded, &removed, &numRemoved, &modified)); + EXPECT_EQ(loot_ok, loot_get_plugin_tags(db_, blankEsp.c_str(), &added_, &numAdded_, &removed_, &numRemoved_, &modified_)); - EXPECT_EQ(0, numAdded); - EXPECT_EQ(NULL, added); - EXPECT_EQ(0, numRemoved); - EXPECT_EQ(NULL, removed); - EXPECT_FALSE(modified); - } + EXPECT_EQ(0, numAdded_); + EXPECT_EQ(NULL, added_); + EXPECT_EQ(0, numRemoved_); + EXPECT_EQ(NULL, removed_); + EXPECT_FALSE(modified_); +} - TEST_P(loot_get_plugin_tags_test, shouldReturnOkAndNonEmptyNonModifiedArraysIfAPluginWithTagsIsQueried) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); - getTagMap(); +TEST_P(loot_get_plugin_tags_test, shouldReturnOkAndNonEmptyNonModifiedArraysIfAPluginWithTagsIsQueried) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); + getTagMap(); - EXPECT_EQ(loot_ok, loot_get_plugin_tags(db, blankEsm.c_str(), &added, &numAdded, &removed, &numRemoved, &modified)); + EXPECT_EQ(loot_ok, loot_get_plugin_tags(db_, blankEsm.c_str(), &added_, &numAdded_, &removed_, &numRemoved_, &modified_)); - // The values are tag map indices, check they match up as expected. - ASSERT_EQ(2, numAdded); - EXPECT_EQ(0, added[0]); - EXPECT_EQ(1, added[1]); + // The values are tag map indices, check they match up as expected. + ASSERT_EQ(2, numAdded_); + EXPECT_EQ(0, added_[0]); + EXPECT_EQ(1, added_[1]); - ASSERT_EQ(1, numRemoved); - EXPECT_EQ(2, removed[0]); + ASSERT_EQ(1, numRemoved_); + EXPECT_EQ(2, removed_[0]); - EXPECT_FALSE(modified); - } + EXPECT_FALSE(modified_); +} - TEST_P(loot_get_plugin_tags_test, shouldReturnOkAndNonEmptyModifiedArraysIfAPluginWithTagsIsQueriedAndMetadataWasAlsoLoadedFromAUserlist) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), masterlistPath.string().c_str())); - getTagMap(); +TEST_P(loot_get_plugin_tags_test, shouldReturnOkAndNonEmptyModifiedArraysIfAPluginWithTagsIsQueriedAndMetadataWasAlsoLoadedFromAUserlist) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), masterlistPath.string().c_str())); + getTagMap(); - EXPECT_EQ(loot_ok, loot_get_plugin_tags(db, blankEsm.c_str(), &added, &numAdded, &removed, &numRemoved, &modified)); + EXPECT_EQ(loot_ok, loot_get_plugin_tags(db_, blankEsm.c_str(), &added_, &numAdded_, &removed_, &numRemoved_, &modified_)); - // The values are tag map indices, check they match up as expected. - ASSERT_EQ(2, numAdded); - EXPECT_EQ(0, added[0]); - EXPECT_EQ(1, added[1]); + // The values are tag map indices, check they match up as expected. + ASSERT_EQ(2, numAdded_); + EXPECT_EQ(0, added_[0]); + EXPECT_EQ(1, added_[1]); - ASSERT_EQ(1, numRemoved); - EXPECT_EQ(2, removed[0]); + ASSERT_EQ(1, numRemoved_); + EXPECT_EQ(2, removed_[0]); - EXPECT_TRUE(modified); - } + EXPECT_TRUE(modified_); +} - TEST_P(loot_get_plugin_tags_test, shouldOutputTheCorrectBashTagsForPluginsWhenMakingConsecutiveCalls) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); - getTagMap(); +TEST_P(loot_get_plugin_tags_test, shouldOutputTheCorrectBashTagsForPluginsWhenMakingConsecutiveCalls) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); + getTagMap(); - EXPECT_EQ(loot_ok, loot_get_plugin_tags(db, blankEsm.c_str(), &added, &numAdded, &removed, &numRemoved, &modified)); + EXPECT_EQ(loot_ok, loot_get_plugin_tags(db_, blankEsm.c_str(), &added_, &numAdded_, &removed_, &numRemoved_, &modified_)); - ASSERT_EQ(2, numAdded); - EXPECT_EQ(0, added[0]); - EXPECT_EQ(1, added[1]); + ASSERT_EQ(2, numAdded_); + EXPECT_EQ(0, added_[0]); + EXPECT_EQ(1, added_[1]); - ASSERT_EQ(1, numRemoved); - EXPECT_EQ(2, removed[0]); - EXPECT_FALSE(modified); + ASSERT_EQ(1, numRemoved_); + EXPECT_EQ(2, removed_[0]); + EXPECT_FALSE(modified_); - EXPECT_EQ(loot_ok, loot_get_plugin_tags(db, blankEsp.c_str(), &added, &numAdded, &removed, &numRemoved, &modified)); + EXPECT_EQ(loot_ok, loot_get_plugin_tags(db_, blankEsp.c_str(), &added_, &numAdded_, &removed_, &numRemoved_, &modified_)); - EXPECT_EQ(0, numAdded); - EXPECT_EQ(NULL, added); - EXPECT_EQ(0, numRemoved); - EXPECT_EQ(NULL, removed); - EXPECT_FALSE(modified); - } - } + EXPECT_EQ(0, numAdded_); + EXPECT_EQ(NULL, added_); + EXPECT_EQ(0, numRemoved_); + EXPECT_EQ(NULL, removed_); + EXPECT_FALSE(modified_); +} +} } #endif diff --git a/src/tests/api/loot_get_tag_map_test.h b/src/tests/api/loot_get_tag_map_test.h index 3bf76771..59e4e1a3 100644 --- a/src/tests/api/loot_get_tag_map_test.h +++ b/src/tests/api/loot_get_tag_map_test.h @@ -22,59 +22,60 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_GET_TAG_MAP -#define LOOT_TEST_LOOT_GET_TAG_MAP +#ifndef LOOT_TESTS_API_LOOT_GET_TAG_MAP_TEST +#define LOOT_TESTS_API_LOOT_GET_TAG_MAP_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_get_tag_map_test : public ApiGameOperationsTest { - protected: - loot_get_tag_map_test() : - tagMap(nullptr), - numTags(0) {} +namespace test { +class loot_get_tag_map_test : public ApiGameOperationsTest { +protected: + loot_get_tag_map_test() : + tagMap_(nullptr), + numTags_(0) {} - const char * const * tagMap; - size_t numTags; - }; + const char * const * tagMap_; + size_t numTags_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_get_tag_map_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_get_tag_map_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_get_tag_map_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_get_tag_map(NULL, &tagMap, &numTags)); - EXPECT_EQ(loot_error_invalid_args, loot_get_tag_map(db, NULL, &numTags)); - EXPECT_EQ(loot_error_invalid_args, loot_get_tag_map(db, &tagMap, NULL)); - } +TEST_P(loot_get_tag_map_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { + EXPECT_EQ(loot_error_invalid_args, loot_get_tag_map(NULL, &tagMap_, &numTags_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_tag_map(db_, NULL, &numTags_)); + EXPECT_EQ(loot_error_invalid_args, loot_get_tag_map(db_, &tagMap_, NULL)); +} - TEST_P(loot_get_tag_map_test, shouldReturnOkAndOutputAnEmptyTagMapIfNoMetadataHasBeenLoaded) { - EXPECT_EQ(loot_ok, loot_get_tag_map(db, &tagMap, &numTags)); - EXPECT_EQ(0, numTags); - EXPECT_EQ(NULL, tagMap); - } +TEST_P(loot_get_tag_map_test, shouldReturnOkAndOutputAnEmptyTagMapIfNoMetadataHasBeenLoaded) { + EXPECT_EQ(loot_ok, loot_get_tag_map(db_, &tagMap_, &numTags_)); + EXPECT_EQ(0, numTags_); + EXPECT_EQ(NULL, tagMap_); +} - TEST_P(loot_get_tag_map_test, shouldReturnOKAndOutputANonEmptyTagMapIfMetadataHasBeenLoaded) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_get_tag_map_test, shouldReturnOKAndOutputANonEmptyTagMapIfMetadataHasBeenLoaded) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_get_tag_map(db, &tagMap, &numTags)); + EXPECT_EQ(loot_ok, loot_get_tag_map(db_, &tagMap_, &numTags_)); - ASSERT_EQ(3, numTags); - EXPECT_STREQ("Actors.ACBS", tagMap[0]); - EXPECT_STREQ("Actors.AIData", tagMap[1]); - EXPECT_STREQ("C.Water", tagMap[2]); - } - } + ASSERT_EQ(3, numTags_); + EXPECT_STREQ("Actors.ACBS", tagMap_[0]); + EXPECT_STREQ("Actors.AIData", tagMap_[1]); + EXPECT_STREQ("C.Water", tagMap_[2]); +} +} } #endif diff --git a/src/tests/api/loot_load_lists_test.h b/src/tests/api/loot_load_lists_test.h index 6c29bced..ca7395b0 100644 --- a/src/tests/api/loot_load_lists_test.h +++ b/src/tests/api/loot_load_lists_test.h @@ -22,61 +22,62 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_LOAD_LISTS -#define LOOT_TEST_LOOT_LOAD_LISTS +#ifndef LOOT_TESTS_API_LOOT_LOAD_LISTS_TEST +#define LOOT_TESTS_API_LOOT_LOAD_LISTS_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_load_lists_test : public ApiGameOperationsTest { - protected: - loot_load_lists_test() : - userlistPath(localPath / "userlist.yaml") {} +namespace test { +class loot_load_lists_test : public ApiGameOperationsTest { +protected: + loot_load_lists_test() : + userlistPath(localPath / "userlist.yaml") {} - inline virtual void TearDown() { - ApiGameOperationsTest::TearDown(); + inline virtual void TearDown() { + ApiGameOperationsTest::TearDown(); - // The userlist may have been created during the test, so delete it. - ASSERT_NO_THROW(boost::filesystem::remove(userlistPath)); - } + // The userlist may have been created during the test, so delete it. + ASSERT_NO_THROW(boost::filesystem::remove(userlistPath)); + } - boost::filesystem::path userlistPath; - }; + const boost::filesystem::path userlistPath; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_load_lists_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_load_lists_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_load_lists_test, shouldReturnAnInvalidArgsErrorIfTheDbOrMasterlistPathArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_load_lists(NULL, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_error_invalid_args, loot_load_lists(db, NULL, NULL)); - } +TEST_P(loot_load_lists_test, shouldReturnAnInvalidArgsErrorIfTheDbOrMasterlistPathArgumentsAreNull) { + EXPECT_EQ(loot_error_invalid_args, loot_load_lists(NULL, masterlistPath.string().c_str(), NULL)); + EXPECT_EQ(loot_error_invalid_args, loot_load_lists(db_, NULL, NULL)); +} - TEST_P(loot_load_lists_test, shouldReturnAPathNotFoundErrorIfNoMasterlistIsPresent) { - EXPECT_EQ(loot_error_path_not_found, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); - } +TEST_P(loot_load_lists_test, shouldReturnAPathNotFoundErrorIfNoMasterlistIsPresent) { + EXPECT_EQ(loot_error_path_not_found, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); +} - TEST_P(loot_load_lists_test, shouldReturnAPathNotFoundErrorIfAMasterlistIsPresentButAUserlistDoesNotExistAtTheGivenPath) { - ASSERT_NO_THROW(generateMasterlist()); - EXPECT_EQ(loot_error_path_not_found, loot_load_lists(db, masterlistPath.string().c_str(), userlistPath.string().c_str())); - } +TEST_P(loot_load_lists_test, shouldReturnAPathNotFoundErrorIfAMasterlistIsPresentButAUserlistDoesNotExistAtTheGivenPath) { + ASSERT_NO_THROW(GenerateMasterlist()); + EXPECT_EQ(loot_error_path_not_found, loot_load_lists(db_, masterlistPath.string().c_str(), userlistPath.string().c_str())); +} - TEST_P(loot_load_lists_test, shouldReturnOkIfTheMasterlistAndUserlistAreBothPresent) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_NO_THROW(boost::filesystem::copy(masterlistPath, userlistPath)); +TEST_P(loot_load_lists_test, shouldReturnOkIfTheMasterlistAndUserlistAreBothPresent) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_NO_THROW(boost::filesystem::copy(masterlistPath, userlistPath)); - EXPECT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), userlistPath.string().c_str())); - } - } + EXPECT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), userlistPath.string().c_str())); +} +} } #endif diff --git a/src/tests/api/loot_sort_plugins_test.h b/src/tests/api/loot_sort_plugins_test.h index 0cb9a831..9e4b9961 100644 --- a/src/tests/api/loot_sort_plugins_test.h +++ b/src/tests/api/loot_sort_plugins_test.h @@ -22,70 +22,71 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_SORT_PLUGINS -#define LOOT_TEST_LOOT_SORT_PLUGINS +#ifndef LOOT_TESTS_API_LOOT_SORT_PLUGINS_TEST +#define LOOT_TESTS_API_LOOT_SORT_PLUGINS_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_sort_plugins_test : public ApiGameOperationsTest { - protected: - loot_sort_plugins_test() : - sortedPlugins(nullptr), - numPlugins(0) {} +namespace test { +class loot_sort_plugins_test : public ApiGameOperationsTest { +protected: + loot_sort_plugins_test() : + sortedPlugins_(nullptr), + numPlugins_(0) {} - const char * const * sortedPlugins; - size_t numPlugins; - }; + const char * const * sortedPlugins_; + size_t numPlugins_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_sort_plugins_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_sort_plugins_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_sort_plugins_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(NULL, &sortedPlugins, &numPlugins)); - EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(db, NULL, &numPlugins)); - EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(db, &sortedPlugins, NULL)); - } +TEST_P(loot_sort_plugins_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { + EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(NULL, &sortedPlugins_, &numPlugins_)); + EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(db_, NULL, &numPlugins_)); + EXPECT_EQ(loot_error_invalid_args, loot_sort_plugins(db_, &sortedPlugins_, NULL)); +} - TEST_P(loot_sort_plugins_test, shouldSucceedIfPassedValidArguments) { - std::list expectedOrder = { - masterFile, - blankEsm, - blankMasterDependentEsm, - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankEsp, - blankPluginDependentEsp, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - }; +TEST_P(loot_sort_plugins_test, shouldSucceedIfPassedValidArguments) { + std::list expectedOrder = { + masterFile, + blankEsm, + blankMasterDependentEsm, + blankDifferentEsm, + blankDifferentMasterDependentEsm, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankEsp, + blankPluginDependentEsp, + blankDifferentEsp, + blankDifferentPluginDependentEsp, + }; - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_sort_plugins(db, &sortedPlugins, &numPlugins)); + EXPECT_EQ(loot_ok, loot_sort_plugins(db_, &sortedPlugins_, &numPlugins_)); - ASSERT_EQ(expectedOrder.size(), numPlugins); + ASSERT_EQ(expectedOrder.size(), numPlugins_); - size_t i = 0; - for (const auto& plugin : expectedOrder) { - EXPECT_EQ(plugin, sortedPlugins[i]); - ++i; - } - } - } + size_t i = 0; + for (const auto& plugin : expectedOrder) { + EXPECT_EQ(plugin, sortedPlugins_[i]); + ++i; + } +} +} } #endif diff --git a/src/tests/api/loot_update_masterlist_test.h b/src/tests/api/loot_update_masterlist_test.h index dc69f43e..29583d97 100644 --- a/src/tests/api/loot_update_masterlist_test.h +++ b/src/tests/api/loot_update_masterlist_test.h @@ -22,87 +22,88 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_UPDATE_MASTERLIST -#define LOOT_TEST_LOOT_UPDATE_MASTERLIST +#ifndef LOOT_TESTS_API_LOOT_UPDATE_MASTERLIST_TEST +#define LOOT_TESTS_API_LOOT_UPDATE_MASTERLIST_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_update_masterlist_test : public ApiGameOperationsTest { - protected: - loot_update_masterlist_test() : - updated(false) {} +namespace test { +class loot_update_masterlist_test : public ApiGameOperationsTest { +protected: + loot_update_masterlist_test() : + updated_(false) {} - inline virtual void TearDown() { - ApiGameOperationsTest::TearDown(); + inline void TearDown() { + ApiGameOperationsTest::TearDown(); - // Also remove the ".git" folder if it has been created. - ASSERT_NO_THROW(boost::filesystem::remove_all(masterlistPath.parent_path() / ".git")); - } + // Also remove the ".git" folder if it has been created. + ASSERT_NO_THROW(boost::filesystem::remove_all(masterlistPath.parent_path() / ".git")); + } - bool updated; - }; + bool updated_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_update_masterlist_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_update_masterlist_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(NULL, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated)); - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, NULL, "https://github.com/loot/testing-metadata.git", "master", &updated)); - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, masterlistPath.string().c_str(), NULL, "master", &updated)); - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", NULL, &updated)); - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", NULL)); - } +TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfAnyOfTheArgumentsAreNull) { + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(NULL, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, NULL, "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), NULL, "master", &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", NULL, &updated_)); + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", NULL)); +} - TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheMasterlistPathGivenIsInvalid) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, ";//\?", "https://github.com/loot/testing-metadata.git", "master", &updated)); - } +TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheMasterlistPathGivenIsInvalid) { + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, ";//\?", "https://github.com/loot/testing-metadata.git", "master", &updated_)); +} - TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheMasterlistPathGivenIsEmpty) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, "", "https://github.com/loot/testing-metadata.git", "master", &updated)); - } +TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheMasterlistPathGivenIsEmpty) { + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, "", "https://github.com/loot/testing-metadata.git", "master", &updated_)); +} - TEST_P(loot_update_masterlist_test, shouldReturnAGitErrorIfTheRepositoryUrlGivenCannotBeFound) { - EXPECT_EQ(loot_error_git_error, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/oblivion-does-not-exist.git", "master", &updated)); - } +TEST_P(loot_update_masterlist_test, shouldReturnAGitErrorIfTheRepositoryUrlGivenCannotBeFound) { + EXPECT_EQ(loot_error_git_error, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/oblivion-does-not-exist.git", "master", &updated_)); +} - TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheRepositoryUrlGivenIsEmpty) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, masterlistPath.string().c_str(), "", "master", &updated)); - } +TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheRepositoryUrlGivenIsEmpty) { + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "", "master", &updated_)); +} - TEST_P(loot_update_masterlist_test, shouldReturnAGitErrorIfTheRepositoryBranchGivenCannotBeFound) { - EXPECT_EQ(loot_error_git_error, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "missing-branch", &updated)); - } +TEST_P(loot_update_masterlist_test, shouldReturnAGitErrorIfTheRepositoryBranchGivenCannotBeFound) { + EXPECT_EQ(loot_error_git_error, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "missing-branch", &updated_)); +} - TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheRepositoryBranchGivenIsEmpty) { - EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "", &updated)); - } +TEST_P(loot_update_masterlist_test, shouldReturnAnInvalidArgsErrorIfTheRepositoryBranchGivenIsEmpty) { + EXPECT_EQ(loot_error_invalid_args, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "", &updated_)); +} - TEST_P(loot_update_masterlist_test, shouldSucceedIfPassedValidParametersAndOutputTrueIfTheMasterlistWasUpdated) { - EXPECT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated)); - EXPECT_TRUE(updated); - EXPECT_TRUE(boost::filesystem::exists(masterlistPath)); - } +TEST_P(loot_update_masterlist_test, shouldSucceedIfPassedValidParametersAndOutputTrueIfTheMasterlistWasUpdated) { + EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_TRUE(updated_); + EXPECT_TRUE(boost::filesystem::exists(masterlistPath)); +} - TEST_P(loot_update_masterlist_test, shouldSucceedIfCalledRepeatedlyButOnlyOutputTrueForTheFirstCall) { - EXPECT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated)); - EXPECT_TRUE(updated); +TEST_P(loot_update_masterlist_test, shouldSucceedIfCalledRepeatedlyButOnlyOutputTrueForTheFirstCall) { + EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_TRUE(updated_); - EXPECT_EQ(loot_ok, loot_update_masterlist(db, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated)); - EXPECT_FALSE(updated); - EXPECT_TRUE(boost::filesystem::exists(masterlistPath)); - } - } + EXPECT_EQ(loot_ok, loot_update_masterlist(db_, masterlistPath.string().c_str(), "https://github.com/loot/testing-metadata.git", "master", &updated_)); + EXPECT_FALSE(updated_); + EXPECT_TRUE(boost::filesystem::exists(masterlistPath)); +} +} } #endif diff --git a/src/tests/api/loot_write_minimal_list_test.h b/src/tests/api/loot_write_minimal_list_test.h index 9c7061d3..5d2ac055 100644 --- a/src/tests/api/loot_write_minimal_list_test.h +++ b/src/tests/api/loot_write_minimal_list_test.h @@ -22,111 +22,112 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_LOOT_WRITE_MINIMAL_LIST -#define LOOT_TEST_LOOT_WRITE_MINIMAL_LIST +#ifndef LOOT_TESTS_API_LOOT_WRITE_MINIMAL_LIST_TEST +#define LOOT_TESTS_API_LOOT_WRITE_MINIMAL_LIST_TEST -#include "../include/loot/api.h" -#include "api_game_operations_test.h" +#include "loot/api.h" + +#include "tests/api/api_game_operations_test.h" namespace loot { - namespace test { - class loot_write_minimal_list_test : public ApiGameOperationsTest { - protected: - loot_write_minimal_list_test() : - outputPath(localPath / "minimal.yml") {} +namespace test { +class loot_write_minimal_list_test : public ApiGameOperationsTest { +protected: + loot_write_minimal_list_test() : + outputPath(localPath / "minimal.yml") {} - void SetUp() { - ApiGameOperationsTest::SetUp(); + void SetUp() { + ApiGameOperationsTest::SetUp(); - ASSERT_FALSE(boost::filesystem::exists(outputPath)); - } + ASSERT_FALSE(boost::filesystem::exists(outputPath)); + } - void TearDown() { - ApiGameOperationsTest::TearDown(); + void TearDown() { + ApiGameOperationsTest::TearDown(); - ASSERT_NO_THROW(boost::filesystem::remove(outputPath)); - } + ASSERT_NO_THROW(boost::filesystem::remove(outputPath)); + } - std::string getExpectedContent() const { - using std::endl; + std::string GetExpectedContent() const { + using std::endl; - std::stringstream expectedContent; - expectedContent - << "plugins:" << endl - << " - name: '" << blankEsm << "'" << endl - << " tag:" << endl - << " - Actors.ACBS" << endl - << " - Actors.AIData" << endl - << " - -C.Water" << endl - << " - name: '" << blankDifferentEsm << "'" << endl - << " dirty:" << endl - << " - crc: 0x7d22f9df" << endl - << " util: 'TES4Edit'" << endl - << " udr: 4"; + std::stringstream expectedContent; + expectedContent + << "plugins:" << endl + << " - name: '" << blankEsm << "'" << endl + << " tag:" << endl + << " - Actors.ACBS" << endl + << " - Actors.AIData" << endl + << " - -C.Water" << endl + << " - name: '" << blankDifferentEsm << "'" << endl + << " dirty:" << endl + << " - crc: 0x7d22f9df" << endl + << " util: 'TES4Edit'" << endl + << " udr: 4"; - return expectedContent.str(); - } + return expectedContent.str(); + } - const boost::filesystem::path outputPath; - }; + const boost::filesystem::path outputPath; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - loot_write_minimal_list_test, - ::testing::Values( - loot_game_tes4, - loot_game_tes5, - loot_game_fo3, - loot_game_fonv, - loot_game_fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + loot_write_minimal_list_test, + ::testing::Values( + loot_game_tes4, + loot_game_tes5, + loot_game_fo3, + loot_game_fonv, + loot_game_fo4)); - TEST_P(loot_write_minimal_list_test, shouldReturnAnInvalidArgsErrorIfAPointerArgumentIsNull) { - EXPECT_EQ(loot_error_invalid_args, loot_write_minimal_list(NULL, outputPath.string().c_str(), false)); - EXPECT_EQ(loot_error_invalid_args, loot_write_minimal_list(db, NULL, false)); - } +TEST_P(loot_write_minimal_list_test, shouldReturnAnInvalidArgsErrorIfAPointerArgumentIsNull) { + EXPECT_EQ(loot_error_invalid_args, loot_write_minimal_list(NULL, outputPath.string().c_str(), false)); + EXPECT_EQ(loot_error_invalid_args, loot_write_minimal_list(db_, NULL, false)); +} - TEST_P(loot_write_minimal_list_test, shouldReturnAFileWriteErrorIfThePathGivenIsInvalid) { - EXPECT_EQ(loot_error_file_write_fail, loot_write_minimal_list(db, "/:?*", false)); - } +TEST_P(loot_write_minimal_list_test, shouldReturnAFileWriteErrorIfThePathGivenIsInvalid) { + EXPECT_EQ(loot_error_file_write_fail, loot_write_minimal_list(db_, "/:?*", false)); +} - TEST_P(loot_write_minimal_list_test, shouldReturnOkAndWriteToFileIfArgumentsGivenAreValid) { - EXPECT_EQ(loot_ok, loot_write_minimal_list(db, outputPath.string().c_str(), false)); - EXPECT_TRUE(boost::filesystem::exists(outputPath)); - } +TEST_P(loot_write_minimal_list_test, shouldReturnOkAndWriteToFileIfArgumentsGivenAreValid) { + EXPECT_EQ(loot_ok, loot_write_minimal_list(db_, outputPath.string().c_str(), false)); + EXPECT_TRUE(boost::filesystem::exists(outputPath)); +} - TEST_P(loot_write_minimal_list_test, shouldReturnAFileWriteErrorIfTheFileAlreadyExistsAndTheOverwriteArgumentIsFalse) { - ASSERT_EQ(loot_ok, loot_write_minimal_list(db, outputPath.string().c_str(), false)); - ASSERT_TRUE(boost::filesystem::exists(outputPath)); +TEST_P(loot_write_minimal_list_test, shouldReturnAFileWriteErrorIfTheFileAlreadyExistsAndTheOverwriteArgumentIsFalse) { + ASSERT_EQ(loot_ok, loot_write_minimal_list(db_, outputPath.string().c_str(), false)); + ASSERT_TRUE(boost::filesystem::exists(outputPath)); - EXPECT_EQ(loot_error_file_write_fail, loot_write_minimal_list(db, outputPath.string().c_str(), false)); - } + EXPECT_EQ(loot_error_file_write_fail, loot_write_minimal_list(db_, outputPath.string().c_str(), false)); +} - TEST_P(loot_write_minimal_list_test, shouldReturnOkAndWriteToFileIfTheArgumentsAreValidAndTheOverwriteArgumentIsTrue) { - EXPECT_EQ(loot_ok, loot_write_minimal_list(db, outputPath.string().c_str(), true)); - EXPECT_TRUE(boost::filesystem::exists(outputPath)); - } +TEST_P(loot_write_minimal_list_test, shouldReturnOkAndWriteToFileIfTheArgumentsAreValidAndTheOverwriteArgumentIsTrue) { + EXPECT_EQ(loot_ok, loot_write_minimal_list(db_, outputPath.string().c_str(), true)); + EXPECT_TRUE(boost::filesystem::exists(outputPath)); +} - TEST_P(loot_write_minimal_list_test, shouldReturnOkIfTheFileAlreadyExistsAndTheOverwriteArgumentIsTrue) { - ASSERT_EQ(loot_ok, loot_write_minimal_list(db, outputPath.string().c_str(), false)); - ASSERT_TRUE(boost::filesystem::exists(outputPath)); +TEST_P(loot_write_minimal_list_test, shouldReturnOkIfTheFileAlreadyExistsAndTheOverwriteArgumentIsTrue) { + ASSERT_EQ(loot_ok, loot_write_minimal_list(db_, outputPath.string().c_str(), false)); + ASSERT_TRUE(boost::filesystem::exists(outputPath)); - EXPECT_EQ(loot_ok, loot_write_minimal_list(db, outputPath.string().c_str(), true)); - } + EXPECT_EQ(loot_ok, loot_write_minimal_list(db_, outputPath.string().c_str(), true)); +} - TEST_P(loot_write_minimal_list_test, shouldWriteOnlyBashTagsAndDirtyInfo) { - ASSERT_NO_THROW(generateMasterlist()); - ASSERT_EQ(loot_ok, loot_load_lists(db, masterlistPath.string().c_str(), NULL)); +TEST_P(loot_write_minimal_list_test, shouldWriteOnlyBashTagsAndDirtyInfo) { + ASSERT_NO_THROW(GenerateMasterlist()); + ASSERT_EQ(loot_ok, loot_load_lists(db_, masterlistPath.string().c_str(), NULL)); - EXPECT_EQ(loot_ok, loot_write_minimal_list(db, outputPath.string().c_str(), true)); + EXPECT_EQ(loot_ok, loot_write_minimal_list(db_, outputPath.string().c_str(), true)); - boost::filesystem::ifstream in(outputPath); - std::stringstream content; - content << in.rdbuf(); + boost::filesystem::ifstream in(outputPath); + std::stringstream content; + content << in.rdbuf(); - EXPECT_EQ(getExpectedContent(), content.str()); - } - } + EXPECT_EQ(GetExpectedContent(), content.str()); +} +} } #endif diff --git a/src/tests/api/main.cpp b/src/tests/api/main.cpp index b6580a0c..d1aa7f5b 100644 --- a/src/tests/api/main.cpp +++ b/src/tests/api/main.cpp @@ -43,12 +43,12 @@ int main(int argc, char **argv) { //Set the locale to get encoding conversions working correctly. - std::locale::global(boost::locale::generator().generate("")); - boost::filesystem::path::imbue(std::locale()); + std::locale::global(boost::locale::generator().generate("")); + boost::filesystem::path::imbue(std::locale()); - //Disable logging or else stdout will get overrun. - boost::log::core::get()->set_logging_enabled(false); + //Disable logging or else stdout will get overrun. + boost::log::core::get()->set_logging_enabled(false); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/tests/api/test_api.h b/src/tests/api/test_api.h index dc61a5bb..d6c8e3bb 100644 --- a/src/tests/api/test_api.h +++ b/src/tests/api/test_api.h @@ -22,78 +22,78 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_API -#define LOOT_TEST_API +#ifndef LOOT_TESTS_API_TEST_API +#define LOOT_TESTS_API_TEST_API -#include "../include/loot/api.h" +#include "loot/api.h" #include namespace loot { - namespace test { - TEST(loot_get_version, shouldReturnAnInvalidArgsErrorIfPassedNullPointers) { - unsigned int vMajor, vMinor, vPatch; - EXPECT_EQ(loot_error_invalid_args, loot_get_version(&vMajor, NULL, NULL)); - EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, &vMinor, NULL)); - EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, NULL, &vPatch)); - EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, NULL, NULL)); - } +namespace test { +TEST(loot_get_version, shouldReturnAnInvalidArgsErrorIfPassedNullPointers) { + unsigned int vMajor, vMinor, vPatch; + EXPECT_EQ(loot_error_invalid_args, loot_get_version(&vMajor, NULL, NULL)); + EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, &vMinor, NULL)); + EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, NULL, &vPatch)); + EXPECT_EQ(loot_error_invalid_args, loot_get_version(NULL, NULL, NULL)); +} - TEST(loot_get_version, shouldReturnOkIfPassedNonNullPointers) { - unsigned int vMajor, vMinor, vPatch; - EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch)); - } +TEST(loot_get_version, shouldReturnOkIfPassedNonNullPointers) { + unsigned int vMajor, vMinor, vPatch; + EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch)); +} - TEST(loot_get_build_id, shouldReturnAnInvalidArgsErrorIfPassedANullPointer) { - EXPECT_EQ(loot_error_invalid_args, loot_get_build_id(NULL)); - } +TEST(loot_get_build_id, shouldReturnAnInvalidArgsErrorIfPassedANullPointer) { + EXPECT_EQ(loot_error_invalid_args, loot_get_build_id(NULL)); +} - TEST(loot_get_build_id, shouldReturnOkAndOutputANonNullNonPlaceholderRevisionString) { - const char * revision; - EXPECT_EQ(loot_ok, loot_get_build_id(&revision)); - EXPECT_STRNE(NULL, revision); - EXPECT_STRNE("@GIT_COMMIT_STRING@", revision); // The CMake placeholder. - } +TEST(loot_get_build_id, shouldReturnOkAndOutputANonNullNonPlaceholderRevisionString) { + const char * revision; + EXPECT_EQ(loot_ok, loot_get_build_id(&revision)); + EXPECT_STRNE(NULL, revision); + EXPECT_STRNE("@GIT_COMMIT_STRING@", revision); // The CMake placeholder. +} - TEST(loot_is_compatible, shouldReturnTrueWithEqualMajorAndMinorVersionsAndUnequalPatchVersion) { - unsigned int vMajor, vMinor, vPatch; - EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch)); +TEST(loot_is_compatible, shouldReturnTrueWithEqualMajorAndMinorVersionsAndUnequalPatchVersion) { + unsigned int vMajor, vMinor, vPatch; + EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch)); - EXPECT_TRUE(loot_is_compatible(vMajor, vMinor, vPatch + 1)); - } + EXPECT_TRUE(loot_is_compatible(vMajor, vMinor, vPatch + 1)); +} - TEST(loot_is_compatible, shouldReturnFalseWithEqualMajorVersionAndUnequalMinorAndPatchVersions) { - unsigned int vMajor, vMinor, vPatch; - EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch)); +TEST(loot_is_compatible, shouldReturnFalseWithEqualMajorVersionAndUnequalMinorAndPatchVersions) { + unsigned int vMajor, vMinor, vPatch; + EXPECT_EQ(loot_ok, loot_get_version(&vMajor, &vMinor, &vPatch)); - EXPECT_FALSE(loot_is_compatible(vMajor, vMinor + 1, vPatch + 1)); - } + EXPECT_FALSE(loot_is_compatible(vMajor, vMinor + 1, vPatch + 1)); +} - TEST(loot_get_error_message, shouldReturnAnInvalidArgsErrorIfPassedANullPointer) { - EXPECT_EQ(loot_error_invalid_args, loot_get_error_message(NULL)); +TEST(loot_get_error_message, shouldReturnAnInvalidArgsErrorIfPassedANullPointer) { + EXPECT_EQ(loot_error_invalid_args, loot_get_error_message(NULL)); - const char * error; - EXPECT_EQ(loot_ok, loot_get_error_message(&error)); - ASSERT_STREQ("Null message pointer passed.", error); - } + const char * error; + EXPECT_EQ(loot_ok, loot_get_error_message(&error)); + ASSERT_STREQ("Null message pointer passed.", error); +} - TEST(loot_get_error_message, shouldReturnOkIfPassedANonNullPointer) { - const char * error; - EXPECT_EQ(loot_ok, loot_get_error_message(&error)); - } +TEST(loot_get_error_message, shouldReturnOkIfPassedANonNullPointer) { + const char * error; + EXPECT_EQ(loot_ok, loot_get_error_message(&error)); +} - TEST(loot_get_error_message, shouldOutputAnErrorMessageDetailingTheLastErrorIfAnErrorHasOccurred) { - EXPECT_EQ(loot_error_invalid_args, loot_get_error_message(NULL)); +TEST(loot_get_error_message, shouldOutputAnErrorMessageDetailingTheLastErrorIfAnErrorHasOccurred) { + EXPECT_EQ(loot_error_invalid_args, loot_get_error_message(NULL)); - const char * error; - EXPECT_EQ(loot_ok, loot_get_error_message(&error)); - ASSERT_STREQ("Null message pointer passed.", error); - } + const char * error; + EXPECT_EQ(loot_ok, loot_get_error_message(&error)); + ASSERT_STREQ("Null message pointer passed.", error); +} - TEST(loot_destroy_db, shouldNotThrowIfPassedANullPointer) { - ASSERT_NO_THROW(loot_destroy_db(NULL)); - } - } +TEST(loot_destroy_db, shouldNotThrowIfPassedANullPointer) { + ASSERT_NO_THROW(loot_destroy_db(NULL)); +} +} } #endif diff --git a/src/tests/backend/app/loot_paths_test.h b/src/tests/backend/app/loot_paths_test.h index 4892e727..a1b86806 100644 --- a/src/tests/backend/app/loot_paths_test.h +++ b/src/tests/backend/app/loot_paths_test.h @@ -22,61 +22,61 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_LOOT_PATHS -#define LOOT_TEST_BACKEND_LOOT_PATHS +#ifndef LOOT_TESTS_BACKEND_LOOT_PATHS_TEST +#define LOOT_TESTS_BACKEND_LOOT_PATHS_TEST #include "backend/app/loot_paths.h" #include namespace loot { - namespace test { - TEST(LootPaths, getReadmePathShouldUseLootAppPath) { - LootPaths::initialise(); +namespace test { +TEST(LootPaths, getReadmePathShouldUseLootAppPath) { + LootPaths::initialise(); - EXPECT_EQ(boost::filesystem::current_path() / "docs" / "LOOT Readme.html", LootPaths::getReadmePath()); - } + EXPECT_EQ(boost::filesystem::current_path() / "docs" / "LOOT Readme.html", LootPaths::getReadmePath()); +} - TEST(LootPaths, getUIIndexPathShouldUseLootAppPath) { - LootPaths::initialise(); +TEST(LootPaths, getUIIndexPathShouldUseLootAppPath) { + LootPaths::initialise(); - EXPECT_EQ(boost::filesystem::current_path() / "resources" / "ui" / "index.html", LootPaths::getUIIndexPath()); - } + EXPECT_EQ(boost::filesystem::current_path() / "resources" / "ui" / "index.html", LootPaths::getUIIndexPath()); +} - TEST(LootPaths, getL10nPathShouldUseLootAppPath) { - LootPaths::initialise(); +TEST(LootPaths, getL10nPathShouldUseLootAppPath) { + LootPaths::initialise(); - EXPECT_EQ(boost::filesystem::current_path() / "resources" / "l10n", LootPaths::getL10nPath()); - } + EXPECT_EQ(boost::filesystem::current_path() / "resources" / "l10n", LootPaths::getL10nPath()); +} - TEST(LootPaths, getSettingsPathShouldUseLootDataPath) { - LootPaths::initialise(); +TEST(LootPaths, getSettingsPathShouldUseLootDataPath) { + LootPaths::initialise(); - EXPECT_EQ(LootPaths::getLootDataPath() / "settings.yaml", LootPaths::getSettingsPath()); - } + EXPECT_EQ(LootPaths::getLootDataPath() / "settings.yaml", LootPaths::getSettingsPath()); +} - TEST(LootPaths, getLogPathShouldUseLootDataPath) { - LootPaths::initialise(); +TEST(LootPaths, getLogPathShouldUseLootDataPath) { + LootPaths::initialise(); - EXPECT_EQ(LootPaths::getLootDataPath() / "LOOTDebugLog.txt", LootPaths::getLogPath()); - } + EXPECT_EQ(LootPaths::getLootDataPath() / "LOOTDebugLog.txt", LootPaths::getLogPath()); +} - TEST(LootPaths, initialiseShouldSetTheAppPathToTheCurrentPath) { - LootPaths::initialise(); +TEST(LootPaths, initialiseShouldSetTheAppPathToTheCurrentPath) { + LootPaths::initialise(); - EXPECT_EQ(boost::filesystem::current_path(), LootPaths::getReadmePath().parent_path().parent_path()); - } + EXPECT_EQ(boost::filesystem::current_path(), LootPaths::getReadmePath().parent_path().parent_path()); +} - TEST(LootPaths, initialiseShouldSetTheDataPathToTheLocalAppDataPathSlashLoot) { - LootPaths::initialise(); +TEST(LootPaths, initialiseShouldSetTheDataPathToTheLocalAppDataPathSlashLoot) { + LootPaths::initialise(); - // Can't actually know what the path should be, but we can check - // its properties. - EXPECT_EQ("LOOT", LootPaths::getLootDataPath().filename()); - EXPECT_FALSE(LootPaths::getLootDataPath().parent_path().empty()); - EXPECT_TRUE(boost::filesystem::exists(LootPaths::getLootDataPath().parent_path())); - } - } + // Can't actually know what the path should be, but we can check + // its properties. + EXPECT_EQ("LOOT", LootPaths::getLootDataPath().filename()); + EXPECT_FALSE(LootPaths::getLootDataPath().parent_path().empty()); + EXPECT_TRUE(boost::filesystem::exists(LootPaths::getLootDataPath().parent_path())); +} +} } #endif diff --git a/src/tests/backend/app/loot_settings_test.h b/src/tests/backend/app/loot_settings_test.h index 0484d91b..7050531e 100644 --- a/src/tests/backend/app/loot_settings_test.h +++ b/src/tests/backend/app/loot_settings_test.h @@ -22,458 +22,459 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_GUI_LOOT_SETTINGS -#define LOOT_TEST_GUI_LOOT_SETTINGS +#ifndef LOOT_TESTS_BACKEND_LOOT_SETTINGS_TEST +#define LOOT_TESTS_BACKEND_LOOT_SETTINGS_TEST #include "backend/app/loot_settings.h" -#include "backend/app/loot_version.h" #include +#include "backend/app/loot_version.h" + namespace loot { - namespace test { - class LootSettingsTest : public ::testing::Test { - protected: - LootSettingsTest() : settingsFile("./settings.yaml") {} - - ~LootSettingsTest() { - boost::filesystem::remove(settingsFile); - } - - boost::filesystem::path settingsFile; - LootSettings settings; - }; - - TEST_F(LootSettingsTest, defaultConstructorShouldSetDefaultValues) { - const std::string currentVersion = LootVersion::string(); - const std::vector expectedGameSettings({ - GameSettings(GameType::tes4), - GameSettings(GameType::tes5), - GameSettings(GameType::fo3), - GameSettings(GameType::fonv), - GameSettings(GameType::fo4), - GameSettings(GameType::tes4, "Nehrim") - .SetName("Nehrim - At Fate's Edge") - .SetMaster("Nehrim.esm") - .SetRegistryKey("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Nehrim - At Fate's Edge_is1\\InstallLocation"), - }); - - EXPECT_FALSE(settings.isDebugLoggingEnabled()); - EXPECT_EQ("auto", settings.getGame()); - EXPECT_EQ("en", settings.getLanguage().GetLocale()); - EXPECT_EQ("auto", settings.getLastGame()); - EXPECT_FALSE(settings.isWindowPositionStored()); - - const YAML::Node node = settings.toYaml(); - EXPECT_TRUE(node["updateMasterlist"].as()); - EXPECT_TRUE(node["lastVersion"].as().empty()); - EXPECT_FALSE(node["filters"]); - - // GameSettings equality only checks name and folder, so check - // other settings individually. - const std::vector actualGameSettings = settings.getGameSettings(); - EXPECT_EQ(expectedGameSettings, actualGameSettings); - - EXPECT_EQ(expectedGameSettings[0].Type(), actualGameSettings[0].Type()); - EXPECT_EQ(expectedGameSettings[0].Master(), actualGameSettings[0].Master()); - EXPECT_EQ(expectedGameSettings[0].RegistryKey(), actualGameSettings[0].RegistryKey()); - EXPECT_EQ(expectedGameSettings[0].RepoURL(), actualGameSettings[0].RepoURL()); - EXPECT_EQ(expectedGameSettings[0].RepoBranch(), actualGameSettings[0].RepoBranch()); - - EXPECT_EQ(expectedGameSettings[1].Type(), actualGameSettings[1].Type()); - EXPECT_EQ(expectedGameSettings[1].Master(), actualGameSettings[1].Master()); - EXPECT_EQ(expectedGameSettings[1].RegistryKey(), actualGameSettings[1].RegistryKey()); - EXPECT_EQ(expectedGameSettings[1].RepoURL(), actualGameSettings[1].RepoURL()); - EXPECT_EQ(expectedGameSettings[1].RepoBranch(), actualGameSettings[1].RepoBranch()); - - EXPECT_EQ(expectedGameSettings[2].Type(), actualGameSettings[2].Type()); - EXPECT_EQ(expectedGameSettings[2].Master(), actualGameSettings[2].Master()); - EXPECT_EQ(expectedGameSettings[2].RegistryKey(), actualGameSettings[2].RegistryKey()); - EXPECT_EQ(expectedGameSettings[2].RepoURL(), actualGameSettings[2].RepoURL()); - EXPECT_EQ(expectedGameSettings[2].RepoBranch(), actualGameSettings[2].RepoBranch()); - - EXPECT_EQ(expectedGameSettings[3].Type(), actualGameSettings[3].Type()); - EXPECT_EQ(expectedGameSettings[3].Master(), actualGameSettings[3].Master()); - EXPECT_EQ(expectedGameSettings[3].RegistryKey(), actualGameSettings[3].RegistryKey()); - EXPECT_EQ(expectedGameSettings[3].RepoURL(), actualGameSettings[3].RepoURL()); - EXPECT_EQ(expectedGameSettings[3].RepoBranch(), actualGameSettings[3].RepoBranch()); - - EXPECT_EQ(expectedGameSettings[4].Type(), actualGameSettings[4].Type()); - EXPECT_EQ(expectedGameSettings[4].Master(), actualGameSettings[4].Master()); - EXPECT_EQ(expectedGameSettings[4].RegistryKey(), actualGameSettings[4].RegistryKey()); - EXPECT_EQ(expectedGameSettings[4].RepoURL(), actualGameSettings[4].RepoURL()); - EXPECT_EQ(expectedGameSettings[4].RepoBranch(), actualGameSettings[4].RepoBranch()); - - EXPECT_EQ(expectedGameSettings[5].Type(), actualGameSettings[5].Type()); - EXPECT_EQ(expectedGameSettings[5].Master(), actualGameSettings[5].Master()); - EXPECT_EQ(expectedGameSettings[5].RegistryKey(), actualGameSettings[5].RegistryKey()); - EXPECT_EQ(expectedGameSettings[5].RepoURL(), actualGameSettings[5].RepoURL()); - EXPECT_EQ(expectedGameSettings[5].RepoBranch(), actualGameSettings[5].RepoBranch()); - } - - TEST_F(LootSettingsTest, loadingFromFileShouldLoadContentAsYaml) { - boost::filesystem::ofstream out(settingsFile); - out << "enableDebugLogging: true" << std::endl; - out.close(); - - settings.load(settingsFile); - - EXPECT_TRUE(settings.isDebugLoggingEnabled()); - } - - TEST_F(LootSettingsTest, loadingFromYamlShouldStoreLoadedValues) { - const bool enableDebugLogging = true; - const bool updateMasterlist = true; - const std::string game = "Oblivion"; - const std::string language = "fr"; - const std::string lastGame = "Skyrim"; - const std::string lastVersion = "0.7.1"; - const std::map window({ - {"top", 1}, - {"bottom", 2}, - {"left", 3}, - {"right", 4}, - }); - const std::vector games({ - GameSettings(GameType::tes4).SetName("Game Name"), - }); - const std::map filters({ - {"hideBashTags", false}, - {"hideCRCs", true}, - }); - - YAML::Node inputYaml; - inputYaml["enableDebugLogging"] = enableDebugLogging; - inputYaml["updateMasterlist"] = updateMasterlist; - inputYaml["game"] = game; - inputYaml["language"] = language; - inputYaml["lastGame"] = lastGame; - inputYaml["lastVersion"] = lastVersion; - inputYaml["window"] = window; - inputYaml["games"] = games; - inputYaml["filters"] = filters; - - settings.load(inputYaml); - - EXPECT_EQ(enableDebugLogging, settings.isDebugLoggingEnabled()); - EXPECT_EQ(game, settings.getGame()); - EXPECT_EQ(language, settings.getLanguage().GetLocale()); - EXPECT_EQ(lastGame, settings.getLastGame()); - - EXPECT_EQ(1, settings.getWindowPosition().top); - EXPECT_EQ(2, settings.getWindowPosition().bottom); - EXPECT_EQ(3, settings.getWindowPosition().left); - EXPECT_EQ(4, settings.getWindowPosition().right); - - const YAML::Node outputYaml = settings.toYaml(); - EXPECT_EQ(updateMasterlist, outputYaml["updateMasterlist"].as()); - EXPECT_EQ(lastVersion, outputYaml["lastVersion"].as()); - - for (const auto& filter : filters) { - EXPECT_EQ(filter.second, outputYaml["filters"][filter.first].as()); - } - - EXPECT_EQ(games[0].Name(), settings.getGameSettings()[0].Name()); - } - - TEST_F(LootSettingsTest, loadingFromEmptyYamlShouldNotThrow) { - YAML::Node yaml; - EXPECT_NO_THROW(settings.load(yaml)); - } - - TEST_F(LootSettingsTest, loadingFromYamlShouldUpgradeFromVersion0Point6Format) { - const unsigned int DebugVerbosity = 3; - const bool UpdateMasterlist = true; - const std::string Game = "Oblivion"; - const std::string Language = "fr"; - const std::string LastGame = "Skyrim"; - const std::vector Games({ - GameSettings(GameType::tes4).SetName("Game Name"), - }); - - YAML::Node inputYaml; - inputYaml["Debug Verbosity"] = DebugVerbosity; - inputYaml["Update Masterlist"] = UpdateMasterlist; - inputYaml["Game"] = Game; - inputYaml["Language"] = Language; - inputYaml["Last Game"] = LastGame; - - inputYaml["Games"] = Games; - inputYaml["Games"][0]["url"] = inputYaml["Games"][0]["repo"]; - inputYaml["Games"][0].remove("repo"); - inputYaml["Games"][0].remove("branch"); - - settings.load(inputYaml); - - const YAML::Node outputYaml = settings.toYaml(); - EXPECT_TRUE(settings.isDebugLoggingEnabled()); - EXPECT_EQ(UpdateMasterlist, outputYaml["updateMasterlist"].as()); - EXPECT_EQ(Game, settings.getGame()); - EXPECT_EQ(Language, settings.getLanguage().GetLocale()); - EXPECT_EQ(LastGame, settings.getLastGame()); - - EXPECT_EQ(Games[0].Name(), settings.getGameSettings()[0].Name()); - EXPECT_EQ(Games[0].RepoURL(), settings.getGameSettings()[0].RepoURL()); - EXPECT_EQ(Games[0].RepoBranch(), settings.getGameSettings()[0].RepoBranch()); - } - - TEST_F(LootSettingsTest, loadingFromYamlShouldNotUpgradeVersion0Point6SettingsIfEquivalentsAlreadyExist) { - const unsigned int DebugVerbosity = 3; - const bool enableDebugLogging = false; - const bool UpdateMasterlist = true; - const bool updateMasterlist = false; - const std::string Game = "Oblivion"; - const std::string game = "auto"; - const std::string Language = "fr"; - const std::string language = "en"; - const std::string LastGame = "Skyrim"; - const std::string lastGame = "auto"; - const std::vector Games({ - GameSettings(GameType::tes4).SetName("Old Game Name"), - }); - const std::vector games({ - GameSettings(GameType::fo3).SetName("Game Name"), - }); - - YAML::Node inputYaml; - inputYaml["Debug Verbosity"] = DebugVerbosity; - inputYaml["enableDebugLogging"] = enableDebugLogging; - inputYaml["Update Masterlist"] = UpdateMasterlist; - inputYaml["updateMasterlist"] = updateMasterlist; - inputYaml["Game"] = Game; - inputYaml["game"] = game; - inputYaml["Language"] = Language; - inputYaml["language"] = language; - inputYaml["Last Game"] = LastGame; - inputYaml["lastGame"] = lastGame; - - inputYaml["Games"] = Games; - inputYaml["Games"][0]["url"] = inputYaml["Games"][0]["repo"]; - inputYaml["Games"][0].remove("repo"); - inputYaml["Games"][0].remove("branch"); - inputYaml["games"] = games; - - settings.load(inputYaml); - - const YAML::Node outputYaml = settings.toYaml(); - - EXPECT_EQ(enableDebugLogging, settings.isDebugLoggingEnabled()); - EXPECT_EQ(updateMasterlist, outputYaml["updateMasterlist"].as()); - EXPECT_EQ(game, settings.getGame()); - EXPECT_EQ(language, settings.getLanguage().GetLocale()); - EXPECT_EQ(lastGame, settings.getLastGame()); - - EXPECT_EQ(games[0].Name(), settings.getGameSettings()[0].Name()); - } - - TEST_F(LootSettingsTest, loadingFromYamlShouldUpgradeOldDefaultGameRepositoryBranches) { - const std::vector games({GameSettings(GameType::tes4)}); - - YAML::Node inputYaml; - inputYaml["games"] = games; - inputYaml["games"][0]["branch"] = "v0.7"; - - settings.load(inputYaml); - - EXPECT_EQ(games[0].RepoBranch(), settings.getGameSettings()[0].RepoBranch()); - } - - TEST_F(LootSettingsTest, loadingFromYamlShouldNotUpgradeNonDefaultGameRepositoryBranches) { - const std::vector games({GameSettings(GameType::tes4)}); - - YAML::Node inputYaml; - inputYaml["games"] = games; - inputYaml["games"][0]["branch"] = "foo"; - - settings.load(inputYaml); - - EXPECT_EQ("foo", settings.getGameSettings()[0].RepoBranch()); - } - - TEST_F(LootSettingsTest, loadingFromYamlShouldAddMissingBaseGames) { - const std::vector games({GameSettings(GameType::tes4)}); - YAML::Node inputYaml; - inputYaml["games"] = games; - - settings.load(inputYaml); - - const std::vector expectedGameSettings({ - GameSettings(GameType::tes4), - GameSettings(GameType::tes5), - GameSettings(GameType::fo3), - GameSettings(GameType::fonv), - GameSettings(GameType::fo4), - }); - EXPECT_EQ(expectedGameSettings, settings.getGameSettings()); - } - - TEST_F(LootSettingsTest, loadingFromYamlShouldSkipUnrecognisedGames) { - YAML::Node inputYaml; - inputYaml["games"][0] = GameSettings(GameType::tes4); - inputYaml["games"][0]["type"] = "Foobar"; - inputYaml["games"][0]["name"] = "Foobar"; - inputYaml["games"][1] = GameSettings(GameType::tes5).SetName("Game Name"); - - settings.load(inputYaml); - - EXPECT_EQ("Game Name", settings.getGameSettings()[0].Name()); - } - - TEST_F(LootSettingsTest, loadingFromYamlShouldRemoveTheContentFilterSetting) { - YAML::Node inputYaml; - inputYaml["filters"]["contentFilter"] = "foo"; - - settings.load(inputYaml); - } - - TEST_F(LootSettingsTest, saveShouldWriteSettingsAsYamlToPassedFile) { - settings.storeLastGame("Skyrim"); - settings.save(settingsFile); - - settings.storeLastGame("auto"); - settings.load(settingsFile); - - EXPECT_EQ("Skyrim", settings.getLastGame()); - } - - TEST_F(LootSettingsTest, getLanguageShouldReturnTheCurrentValue) { - YAML::Node inputYaml; - inputYaml["language"] = "fr"; - - settings.load(inputYaml); - - EXPECT_EQ("fr", settings.getLanguage().GetLocale()); - } - - TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnFalseIfAllPositionValuesAreZero) { - LootSettings::WindowPosition position; - settings.storeWindowPosition(position); - - EXPECT_FALSE(settings.isWindowPositionStored()); - } - - TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnTrueIfTopPositionValueIsNonZero) { - LootSettings::WindowPosition position; - position.top = 1; - settings.storeWindowPosition(position); - - EXPECT_TRUE(settings.isWindowPositionStored()); - } - - TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnTrueIfBottomPositionValueIsNonZero) { - LootSettings::WindowPosition position; - position.bottom = 1; - settings.storeWindowPosition(position); - - EXPECT_TRUE(settings.isWindowPositionStored()); - } - - TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnTrueIfLeftPositionValueIsNonZero) { - LootSettings::WindowPosition position; - position.left = 1; - settings.storeWindowPosition(position); - - EXPECT_TRUE(settings.isWindowPositionStored()); - } - - TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnTrueIfRightPositionValueIsNonZero) { - LootSettings::WindowPosition position; - position.right = 1; - settings.storeWindowPosition(position); - - EXPECT_TRUE(settings.isWindowPositionStored()); - } - - TEST_F(LootSettingsTest, storeGameSettingsShouldReplaceExistingGameSettings) { - const std::vector gameSettings({GameSettings(GameType::tes5)}); - settings.storeGameSettings(gameSettings); - - EXPECT_EQ(gameSettings, settings.getGameSettings()); - } - - TEST_F(LootSettingsTest, storeLastGameShouldReplaceExistingValue) { - settings.storeLastGame("Fallout3"); - - EXPECT_EQ("Fallout3", settings.getLastGame()); - } - - TEST_F(LootSettingsTest, storeWindowPositionShouldReplaceExistingValue) { - LootSettings::WindowPosition expectedPosition; - expectedPosition.top = 1; - settings.storeWindowPosition(expectedPosition); - - LootSettings::WindowPosition actualPosition = settings.getWindowPosition(); - EXPECT_EQ(expectedPosition.top, actualPosition.top); - EXPECT_EQ(expectedPosition.bottom, actualPosition.bottom); - EXPECT_EQ(expectedPosition.left, actualPosition.left); - EXPECT_EQ(expectedPosition.right, actualPosition.right); - } - - TEST_F(LootSettingsTest, updateLastVersionShouldSetValueToCurrentLootVersion) { - const std::string currentVersion = LootVersion::string(); - YAML::Node inputYaml; - inputYaml["lastVersion"] = "v0.7.1"; - - settings.load(inputYaml); - settings.updateLastVersion(); - - EXPECT_EQ(currentVersion, settings.getLastVersion()); - } - - TEST_F(LootSettingsTest, toYamlShouldOutputStoredSettings) { - const bool enableDebugLogging = true; - const bool updateMasterlist = true; - const std::string game = "Oblivion"; - const std::string language = "fr"; - const std::string lastGame = "Skyrim"; - const std::string lastVersion = "0.7.1"; - const std::map window({ - {"top", 1}, - {"bottom", 2}, - {"left", 3}, - {"right", 4}, - }); - const std::vector games({ - GameSettings(GameType::tes4).SetName("Game Name"), - }); - const std::map filters({ - {"hideBashTags", false}, - {"hideCRCs", true}, - }); - - YAML::Node inputYaml; - inputYaml["enableDebugLogging"] = enableDebugLogging; - inputYaml["updateMasterlist"] = updateMasterlist; - inputYaml["game"] = game; - inputYaml["language"] = language; - inputYaml["lastGame"] = lastGame; - inputYaml["lastVersion"] = lastVersion; - inputYaml["window"] = window; - inputYaml["games"] = games; - inputYaml["filters"] = filters; - - settings.load(inputYaml); - - const YAML::Node outputYaml = settings.toYaml(); - - EXPECT_EQ(enableDebugLogging, outputYaml["enableDebugLogging"].as()); - EXPECT_EQ(updateMasterlist, outputYaml["updateMasterlist"].as()); - EXPECT_EQ(game, outputYaml["game"].as()); - EXPECT_EQ(language, outputYaml["language"].as()); - EXPECT_EQ(lastGame, outputYaml["lastGame"].as()); - EXPECT_EQ(lastVersion, outputYaml["lastVersion"].as()); - - for (const auto& position : window) { - EXPECT_EQ(position.second, outputYaml["window"][position.first].as()); - } - - for (const auto& filter : filters) { - EXPECT_EQ(filter.second, outputYaml["filters"][filter.first].as()); - } - - EXPECT_EQ(games[0].Name(), outputYaml["games"][0]["name"].as()); - } - } +namespace test { +class LootSettingsTest : public ::testing::Test { +protected: + LootSettingsTest() : settingsFile_("./settings_.yaml") {} + + ~LootSettingsTest() { + boost::filesystem::remove(settingsFile_); + } + + boost::filesystem::path settingsFile_; + LootSettings settings_; +}; + +TEST_F(LootSettingsTest, defaultConstructorShouldSetDefaultValues) { + const std::string currentVersion = LootVersion::string(); + const std::vector expectedGameSettings({ + GameSettings(GameType::tes4), + GameSettings(GameType::tes5), + GameSettings(GameType::fo3), + GameSettings(GameType::fonv), + GameSettings(GameType::fo4), + GameSettings(GameType::tes4, "Nehrim") + .SetName("Nehrim - At Fate's Edge") + .SetMaster("Nehrim.esm") + .SetRegistryKey("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Nehrim - At Fate's Edge_is1\\InstallLocation"), + }); + + EXPECT_FALSE(settings_.isDebugLoggingEnabled()); + EXPECT_EQ("auto", settings_.getGame()); + EXPECT_EQ("en", settings_.getLanguage().GetLocale()); + EXPECT_EQ("auto", settings_.getLastGame()); + EXPECT_FALSE(settings_.isWindowPositionStored()); + + const YAML::Node node = settings_.toYaml(); + EXPECT_TRUE(node["updateMasterlist"].as()); + EXPECT_TRUE(node["lastVersion"].as().empty()); + EXPECT_FALSE(node["filters"]); + + // GameSettings equality only checks name and folder, so check + // other settings_ individually. + const std::vector actualGameSettings = settings_.getGameSettings(); + EXPECT_EQ(expectedGameSettings, actualGameSettings); + + EXPECT_EQ(expectedGameSettings[0].Type(), actualGameSettings[0].Type()); + EXPECT_EQ(expectedGameSettings[0].Master(), actualGameSettings[0].Master()); + EXPECT_EQ(expectedGameSettings[0].RegistryKey(), actualGameSettings[0].RegistryKey()); + EXPECT_EQ(expectedGameSettings[0].RepoURL(), actualGameSettings[0].RepoURL()); + EXPECT_EQ(expectedGameSettings[0].RepoBranch(), actualGameSettings[0].RepoBranch()); + + EXPECT_EQ(expectedGameSettings[1].Type(), actualGameSettings[1].Type()); + EXPECT_EQ(expectedGameSettings[1].Master(), actualGameSettings[1].Master()); + EXPECT_EQ(expectedGameSettings[1].RegistryKey(), actualGameSettings[1].RegistryKey()); + EXPECT_EQ(expectedGameSettings[1].RepoURL(), actualGameSettings[1].RepoURL()); + EXPECT_EQ(expectedGameSettings[1].RepoBranch(), actualGameSettings[1].RepoBranch()); + + EXPECT_EQ(expectedGameSettings[2].Type(), actualGameSettings[2].Type()); + EXPECT_EQ(expectedGameSettings[2].Master(), actualGameSettings[2].Master()); + EXPECT_EQ(expectedGameSettings[2].RegistryKey(), actualGameSettings[2].RegistryKey()); + EXPECT_EQ(expectedGameSettings[2].RepoURL(), actualGameSettings[2].RepoURL()); + EXPECT_EQ(expectedGameSettings[2].RepoBranch(), actualGameSettings[2].RepoBranch()); + + EXPECT_EQ(expectedGameSettings[3].Type(), actualGameSettings[3].Type()); + EXPECT_EQ(expectedGameSettings[3].Master(), actualGameSettings[3].Master()); + EXPECT_EQ(expectedGameSettings[3].RegistryKey(), actualGameSettings[3].RegistryKey()); + EXPECT_EQ(expectedGameSettings[3].RepoURL(), actualGameSettings[3].RepoURL()); + EXPECT_EQ(expectedGameSettings[3].RepoBranch(), actualGameSettings[3].RepoBranch()); + + EXPECT_EQ(expectedGameSettings[4].Type(), actualGameSettings[4].Type()); + EXPECT_EQ(expectedGameSettings[4].Master(), actualGameSettings[4].Master()); + EXPECT_EQ(expectedGameSettings[4].RegistryKey(), actualGameSettings[4].RegistryKey()); + EXPECT_EQ(expectedGameSettings[4].RepoURL(), actualGameSettings[4].RepoURL()); + EXPECT_EQ(expectedGameSettings[4].RepoBranch(), actualGameSettings[4].RepoBranch()); + + EXPECT_EQ(expectedGameSettings[5].Type(), actualGameSettings[5].Type()); + EXPECT_EQ(expectedGameSettings[5].Master(), actualGameSettings[5].Master()); + EXPECT_EQ(expectedGameSettings[5].RegistryKey(), actualGameSettings[5].RegistryKey()); + EXPECT_EQ(expectedGameSettings[5].RepoURL(), actualGameSettings[5].RepoURL()); + EXPECT_EQ(expectedGameSettings[5].RepoBranch(), actualGameSettings[5].RepoBranch()); +} + +TEST_F(LootSettingsTest, loadingFromFileShouldLoadContentAsYaml) { + boost::filesystem::ofstream out(settingsFile_); + out << "enableDebugLogging: true" << std::endl; + out.close(); + + settings_.load(settingsFile_); + + EXPECT_TRUE(settings_.isDebugLoggingEnabled()); +} + +TEST_F(LootSettingsTest, loadingFromYamlShouldStoreLoadedValues) { + const bool enableDebugLogging = true; + const bool updateMasterlist = true; + const std::string game = "Oblivion"; + const std::string language = "fr"; + const std::string lastGame = "Skyrim"; + const std::string lastVersion = "0.7.1"; + const std::map window({ + {"top", 1}, + {"bottom", 2}, + {"left", 3}, + {"right", 4}, + }); + const std::vector games({ + GameSettings(GameType::tes4).SetName("Game Name"), + }); + const std::map filters({ + {"hideBashTags", false}, + {"hideCRCs", true}, + }); + + YAML::Node inputYaml; + inputYaml["enableDebugLogging"] = enableDebugLogging; + inputYaml["updateMasterlist"] = updateMasterlist; + inputYaml["game"] = game; + inputYaml["language"] = language; + inputYaml["lastGame"] = lastGame; + inputYaml["lastVersion"] = lastVersion; + inputYaml["window"] = window; + inputYaml["games"] = games; + inputYaml["filters"] = filters; + + settings_.load(inputYaml); + + EXPECT_EQ(enableDebugLogging, settings_.isDebugLoggingEnabled()); + EXPECT_EQ(game, settings_.getGame()); + EXPECT_EQ(language, settings_.getLanguage().GetLocale()); + EXPECT_EQ(lastGame, settings_.getLastGame()); + + EXPECT_EQ(1, settings_.getWindowPosition().top); + EXPECT_EQ(2, settings_.getWindowPosition().bottom); + EXPECT_EQ(3, settings_.getWindowPosition().left); + EXPECT_EQ(4, settings_.getWindowPosition().right); + + const YAML::Node outputYaml = settings_.toYaml(); + EXPECT_EQ(updateMasterlist, outputYaml["updateMasterlist"].as()); + EXPECT_EQ(lastVersion, outputYaml["lastVersion"].as()); + + for (const auto& filter : filters) { + EXPECT_EQ(filter.second, outputYaml["filters"][filter.first].as()); + } + + EXPECT_EQ(games[0].Name(), settings_.getGameSettings()[0].Name()); +} + +TEST_F(LootSettingsTest, loadingFromEmptyYamlShouldNotThrow) { + YAML::Node yaml; + EXPECT_NO_THROW(settings_.load(yaml)); +} + +TEST_F(LootSettingsTest, loadingFromYamlShouldUpgradeFromVersion0Point6Format) { + const unsigned int DebugVerbosity = 3; + const bool UpdateMasterlist = true; + const std::string Game = "Oblivion"; + const std::string Language = "fr"; + const std::string LastGame = "Skyrim"; + const std::vector Games({ + GameSettings(GameType::tes4).SetName("Game Name"), + }); + + YAML::Node inputYaml; + inputYaml["Debug Verbosity"] = DebugVerbosity; + inputYaml["Update Masterlist"] = UpdateMasterlist; + inputYaml["Game"] = Game; + inputYaml["Language"] = Language; + inputYaml["Last Game"] = LastGame; + + inputYaml["Games"] = Games; + inputYaml["Games"][0]["url"] = inputYaml["Games"][0]["repo"]; + inputYaml["Games"][0].remove("repo"); + inputYaml["Games"][0].remove("branch"); + + settings_.load(inputYaml); + + const YAML::Node outputYaml = settings_.toYaml(); + EXPECT_TRUE(settings_.isDebugLoggingEnabled()); + EXPECT_EQ(UpdateMasterlist, outputYaml["updateMasterlist"].as()); + EXPECT_EQ(Game, settings_.getGame()); + EXPECT_EQ(Language, settings_.getLanguage().GetLocale()); + EXPECT_EQ(LastGame, settings_.getLastGame()); + + EXPECT_EQ(Games[0].Name(), settings_.getGameSettings()[0].Name()); + EXPECT_EQ(Games[0].RepoURL(), settings_.getGameSettings()[0].RepoURL()); + EXPECT_EQ(Games[0].RepoBranch(), settings_.getGameSettings()[0].RepoBranch()); +} + +TEST_F(LootSettingsTest, loadingFromYamlShouldNotUpgradeVersion0Point6SettingsIfEquivalentsAlreadyExist) { + const unsigned int DebugVerbosity = 3; + const bool enableDebugLogging = false; + const bool UpdateMasterlist = true; + const bool updateMasterlist = false; + const std::string Game = "Oblivion"; + const std::string game = "auto"; + const std::string Language = "fr"; + const std::string language = "en"; + const std::string LastGame = "Skyrim"; + const std::string lastGame = "auto"; + const std::vector Games({ + GameSettings(GameType::tes4).SetName("Old Game Name"), + }); + const std::vector games({ + GameSettings(GameType::fo3).SetName("Game Name"), + }); + + YAML::Node inputYaml; + inputYaml["Debug Verbosity"] = DebugVerbosity; + inputYaml["enableDebugLogging"] = enableDebugLogging; + inputYaml["Update Masterlist"] = UpdateMasterlist; + inputYaml["updateMasterlist"] = updateMasterlist; + inputYaml["Game"] = Game; + inputYaml["game"] = game; + inputYaml["Language"] = Language; + inputYaml["language"] = language; + inputYaml["Last Game"] = LastGame; + inputYaml["lastGame"] = lastGame; + + inputYaml["Games"] = Games; + inputYaml["Games"][0]["url"] = inputYaml["Games"][0]["repo"]; + inputYaml["Games"][0].remove("repo"); + inputYaml["Games"][0].remove("branch"); + inputYaml["games"] = games; + + settings_.load(inputYaml); + + const YAML::Node outputYaml = settings_.toYaml(); + + EXPECT_EQ(enableDebugLogging, settings_.isDebugLoggingEnabled()); + EXPECT_EQ(updateMasterlist, outputYaml["updateMasterlist"].as()); + EXPECT_EQ(game, settings_.getGame()); + EXPECT_EQ(language, settings_.getLanguage().GetLocale()); + EXPECT_EQ(lastGame, settings_.getLastGame()); + + EXPECT_EQ(games[0].Name(), settings_.getGameSettings()[0].Name()); +} + +TEST_F(LootSettingsTest, loadingFromYamlShouldUpgradeOldDefaultGameRepositoryBranches) { + const std::vector games({GameSettings(GameType::tes4)}); + + YAML::Node inputYaml; + inputYaml["games"] = games; + inputYaml["games"][0]["branch"] = "v0.7"; + + settings_.load(inputYaml); + + EXPECT_EQ(games[0].RepoBranch(), settings_.getGameSettings()[0].RepoBranch()); +} + +TEST_F(LootSettingsTest, loadingFromYamlShouldNotUpgradeNonDefaultGameRepositoryBranches) { + const std::vector games({GameSettings(GameType::tes4)}); + + YAML::Node inputYaml; + inputYaml["games"] = games; + inputYaml["games"][0]["branch"] = "foo"; + + settings_.load(inputYaml); + + EXPECT_EQ("foo", settings_.getGameSettings()[0].RepoBranch()); +} + +TEST_F(LootSettingsTest, loadingFromYamlShouldAddMissingBaseGames) { + const std::vector games({GameSettings(GameType::tes4)}); + YAML::Node inputYaml; + inputYaml["games"] = games; + + settings_.load(inputYaml); + + const std::vector expectedGameSettings({ + GameSettings(GameType::tes4), + GameSettings(GameType::tes5), + GameSettings(GameType::fo3), + GameSettings(GameType::fonv), + GameSettings(GameType::fo4), + }); + EXPECT_EQ(expectedGameSettings, settings_.getGameSettings()); +} + +TEST_F(LootSettingsTest, loadingFromYamlShouldSkipUnrecognisedGames) { + YAML::Node inputYaml; + inputYaml["games"][0] = GameSettings(GameType::tes4); + inputYaml["games"][0]["type"] = "Foobar"; + inputYaml["games"][0]["name"] = "Foobar"; + inputYaml["games"][1] = GameSettings(GameType::tes5).SetName("Game Name"); + + settings_.load(inputYaml); + + EXPECT_EQ("Game Name", settings_.getGameSettings()[0].Name()); +} + +TEST_F(LootSettingsTest, loadingFromYamlShouldRemoveTheContentFilterSetting) { + YAML::Node inputYaml; + inputYaml["filters"]["contentFilter"] = "foo"; + + settings_.load(inputYaml); +} + +TEST_F(LootSettingsTest, saveShouldWriteSettingsAsYamlToPassedFile) { + settings_.storeLastGame("Skyrim"); + settings_.save(settingsFile_); + + settings_.storeLastGame("auto"); + settings_.load(settingsFile_); + + EXPECT_EQ("Skyrim", settings_.getLastGame()); +} + +TEST_F(LootSettingsTest, getLanguageShouldReturnTheCurrentValue) { + YAML::Node inputYaml; + inputYaml["language"] = "fr"; + + settings_.load(inputYaml); + + EXPECT_EQ("fr", settings_.getLanguage().GetLocale()); +} + +TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnFalseIfAllPositionValuesAreZero) { + LootSettings::WindowPosition position; + settings_.storeWindowPosition(position); + + EXPECT_FALSE(settings_.isWindowPositionStored()); +} + +TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnTrueIfTopPositionValueIsNonZero) { + LootSettings::WindowPosition position; + position.top = 1; + settings_.storeWindowPosition(position); + + EXPECT_TRUE(settings_.isWindowPositionStored()); +} + +TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnTrueIfBottomPositionValueIsNonZero) { + LootSettings::WindowPosition position; + position.bottom = 1; + settings_.storeWindowPosition(position); + + EXPECT_TRUE(settings_.isWindowPositionStored()); +} + +TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnTrueIfLeftPositionValueIsNonZero) { + LootSettings::WindowPosition position; + position.left = 1; + settings_.storeWindowPosition(position); + + EXPECT_TRUE(settings_.isWindowPositionStored()); +} + +TEST_F(LootSettingsTest, isWindowPositionStoredShouldReturnTrueIfRightPositionValueIsNonZero) { + LootSettings::WindowPosition position; + position.right = 1; + settings_.storeWindowPosition(position); + + EXPECT_TRUE(settings_.isWindowPositionStored()); +} + +TEST_F(LootSettingsTest, storeGameSettingsShouldReplaceExistingGameSettings) { + const std::vector gameSettings({GameSettings(GameType::tes5)}); + settings_.storeGameSettings(gameSettings); + + EXPECT_EQ(gameSettings, settings_.getGameSettings()); +} + +TEST_F(LootSettingsTest, storeLastGameShouldReplaceExistingValue) { + settings_.storeLastGame("Fallout3"); + + EXPECT_EQ("Fallout3", settings_.getLastGame()); +} + +TEST_F(LootSettingsTest, storeWindowPositionShouldReplaceExistingValue) { + LootSettings::WindowPosition expectedPosition; + expectedPosition.top = 1; + settings_.storeWindowPosition(expectedPosition); + + LootSettings::WindowPosition actualPosition = settings_.getWindowPosition(); + EXPECT_EQ(expectedPosition.top, actualPosition.top); + EXPECT_EQ(expectedPosition.bottom, actualPosition.bottom); + EXPECT_EQ(expectedPosition.left, actualPosition.left); + EXPECT_EQ(expectedPosition.right, actualPosition.right); +} + +TEST_F(LootSettingsTest, updateLastVersionShouldSetValueToCurrentLootVersion) { + const std::string currentVersion = LootVersion::string(); + YAML::Node inputYaml; + inputYaml["lastVersion"] = "v0.7.1"; + + settings_.load(inputYaml); + settings_.updateLastVersion(); + + EXPECT_EQ(currentVersion, settings_.getLastVersion()); +} + +TEST_F(LootSettingsTest, toYamlShouldOutputStoredSettings) { + const bool enableDebugLogging = true; + const bool updateMasterlist = true; + const std::string game = "Oblivion"; + const std::string language = "fr"; + const std::string lastGame = "Skyrim"; + const std::string lastVersion = "0.7.1"; + const std::map window({ + {"top", 1}, + {"bottom", 2}, + {"left", 3}, + {"right", 4}, + }); + const std::vector games({ + GameSettings(GameType::tes4).SetName("Game Name"), + }); + const std::map filters({ + {"hideBashTags", false}, + {"hideCRCs", true}, + }); + + YAML::Node inputYaml; + inputYaml["enableDebugLogging"] = enableDebugLogging; + inputYaml["updateMasterlist"] = updateMasterlist; + inputYaml["game"] = game; + inputYaml["language"] = language; + inputYaml["lastGame"] = lastGame; + inputYaml["lastVersion"] = lastVersion; + inputYaml["window"] = window; + inputYaml["games"] = games; + inputYaml["filters"] = filters; + + settings_.load(inputYaml); + + const YAML::Node outputYaml = settings_.toYaml(); + + EXPECT_EQ(enableDebugLogging, outputYaml["enableDebugLogging"].as()); + EXPECT_EQ(updateMasterlist, outputYaml["updateMasterlist"].as()); + EXPECT_EQ(game, outputYaml["game"].as()); + EXPECT_EQ(language, outputYaml["language"].as()); + EXPECT_EQ(lastGame, outputYaml["lastGame"].as()); + EXPECT_EQ(lastVersion, outputYaml["lastVersion"].as()); + + for (const auto& position : window) { + EXPECT_EQ(position.second, outputYaml["window"][position.first].as()); + } + + for (const auto& filter : filters) { + EXPECT_EQ(filter.second, outputYaml["filters"][filter.first].as()); + } + + EXPECT_EQ(games[0].Name(), outputYaml["games"][0]["name"].as()); +} +} } #endif diff --git a/src/tests/backend/app/loot_state_test.h b/src/tests/backend/app/loot_state_test.h index b780a81a..b7bae028 100644 --- a/src/tests/backend/app/loot_state_test.h +++ b/src/tests/backend/app/loot_state_test.h @@ -22,56 +22,56 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_GUI_LOOT_STATE -#define LOOT_TEST_GUI_LOOT_STATE +#ifndef LOOT_TESTS_BACKEND_LOOT_STATE_TEST +#define LOOT_TESTS_BACKEND_LOOT_STATE_TEST #include "backend/app/loot_state.h" #include namespace loot { - namespace test { - class LootStateTest : public ::testing::Test { - protected: - LootState lootState; - }; +namespace test { +class LootStateTest : public ::testing::Test { +protected: + LootState lootState_; +}; - TEST_F(LootStateTest, hasUnappliedChangesShouldBeFalseByDefault) { - EXPECT_FALSE(lootState.hasUnappliedChanges()); - } +TEST_F(LootStateTest, hasUnappliedChangesShouldBeFalseByDefault) { + EXPECT_FALSE(lootState_.hasUnappliedChanges()); +} - TEST_F(LootStateTest, shouldNotHaveUnappliedChangesIfCounterIsDeccremented) { - lootState.decrementUnappliedChangeCounter(); - EXPECT_FALSE(lootState.hasUnappliedChanges()); - } +TEST_F(LootStateTest, shouldNotHaveUnappliedChangesIfCounterIsDeccremented) { + lootState_.decrementUnappliedChangeCounter(); + EXPECT_FALSE(lootState_.hasUnappliedChanges()); +} - TEST_F(LootStateTest, shouldHaveUnappliedChangesIfCounterIsIncremented) { - lootState.incrementUnappliedChangeCounter(); - EXPECT_TRUE(lootState.hasUnappliedChanges()); - } +TEST_F(LootStateTest, shouldHaveUnappliedChangesIfCounterIsIncremented) { + lootState_.incrementUnappliedChangeCounter(); + EXPECT_TRUE(lootState_.hasUnappliedChanges()); +} - TEST_F(LootStateTest, incrementingTheChangeCounterMoreThanItIsDecrementedShouldLeaveUnappliedChanges) { - lootState.incrementUnappliedChangeCounter(); - lootState.incrementUnappliedChangeCounter(); - lootState.decrementUnappliedChangeCounter(); - EXPECT_TRUE(lootState.hasUnappliedChanges()); - } +TEST_F(LootStateTest, incrementingTheChangeCounterMoreThanItIsDecrementedShouldLeaveUnappliedChanges) { + lootState_.incrementUnappliedChangeCounter(); + lootState_.incrementUnappliedChangeCounter(); + lootState_.decrementUnappliedChangeCounter(); + EXPECT_TRUE(lootState_.hasUnappliedChanges()); +} - TEST_F(LootStateTest, incrementingTheChangeCounterLessThanItIsDecrementedShouldLeaveNoUnappliedChanges) { - lootState.incrementUnappliedChangeCounter(); - lootState.decrementUnappliedChangeCounter(); - lootState.decrementUnappliedChangeCounter(); - EXPECT_FALSE(lootState.hasUnappliedChanges()); - } +TEST_F(LootStateTest, incrementingTheChangeCounterLessThanItIsDecrementedShouldLeaveNoUnappliedChanges) { + lootState_.incrementUnappliedChangeCounter(); + lootState_.decrementUnappliedChangeCounter(); + lootState_.decrementUnappliedChangeCounter(); + EXPECT_FALSE(lootState_.hasUnappliedChanges()); +} - TEST_F(LootStateTest, incrementingTheChangeCounterThenDecrementingItEquallyShouldLeaveNoUnappliedChanges) { - lootState.incrementUnappliedChangeCounter(); - lootState.incrementUnappliedChangeCounter(); - lootState.decrementUnappliedChangeCounter(); - lootState.decrementUnappliedChangeCounter(); - EXPECT_FALSE(lootState.hasUnappliedChanges()); - } - } +TEST_F(LootStateTest, incrementingTheChangeCounterThenDecrementingItEquallyShouldLeaveNoUnappliedChanges) { + lootState_.incrementUnappliedChangeCounter(); + lootState_.incrementUnappliedChangeCounter(); + lootState_.decrementUnappliedChangeCounter(); + lootState_.decrementUnappliedChangeCounter(); + EXPECT_FALSE(lootState_.hasUnappliedChanges()); +} +} } #endif diff --git a/src/tests/backend/base_game_test.h b/src/tests/backend/base_game_test.h index a2ad839c..6c011ec0 100644 --- a/src/tests/backend/base_game_test.h +++ b/src/tests/backend/base_game_test.h @@ -22,37 +22,30 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BASE_GAME_TEST -#define LOOT_TEST_BASE_GAME_TEST - -#include -#include -#include -#include - -#include -#include +#ifndef LOOT_TESTS_BACKEND_BASE_GAME_TEST +#define LOOT_TESTS_BACKEND_BASE_GAME_TEST +#include "backend/game/game_type.h" #include "tests/common_game_test_fixture.h" namespace loot { - namespace test { - class BaseGameTest : - public ::testing::TestWithParam, - public CommonGameTestFixture { - protected: - BaseGameTest() : - CommonGameTestFixture(static_cast(GetParam())) {} +namespace test { +class BaseGameTest : + public ::testing::TestWithParam, + public CommonGameTestFixture { +protected: + BaseGameTest() : + CommonGameTestFixture(static_cast(GetParam())) {} - inline virtual void SetUp() { - setUp(); - } + inline virtual void SetUp() { + setUp(); + } - inline virtual void TearDown() { - tearDown(); - } - }; - } + inline virtual void TearDown() { + tearDown(); + } +}; +} } #endif diff --git a/src/tests/backend/game/game_cache_test.h b/src/tests/backend/game/game_cache_test.h index fee9a18a..1dbddec3 100644 --- a/src/tests/backend/game/game_cache_test.h +++ b/src/tests/backend/game/game_cache_test.h @@ -22,205 +22,206 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_GAME_CACHE -#define LOOT_TEST_BACKEND_GAME_CACHE +#ifndef LOOT_TESTS_BACKEND_GAME_GAME_CACHE_TEST +#define LOOT_TESTS_BACKEND_GAME_GAME_CACHE_TEST #include "backend/game/game_cache.h" +#include "backend/game/game.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class GameCacheTest : public BaseGameTest { - protected: - GameCacheTest() : - condition("Condition"), - conditionLowercase("condition") {} +namespace test { +class GameCacheTest : public BaseGameTest { +protected: + GameCacheTest() : + condition("Condition"), + conditionLowercase("condition") {} - void initialiseGame() { - game = Game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - } + void initialiseGame() { + game_ = Game(GetParam()); + game_.SetGamePath(dataPath.parent_path()); + } - Game game; - GameCache cache; + Game game_; + GameCache cache_; - const std::string condition; - const std::string conditionLowercase; - }; + const std::string condition; + const std::string conditionLowercase; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - // Just test with one game because if it works for one it will work for them - // all. - INSTANTIATE_TEST_CASE_P(, - GameCacheTest, - ::testing::Values( - GameType::tes5)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +// Just test with one game_ because if it works for one it will work for them +// all. +INSTANTIATE_TEST_CASE_P(, + GameCacheTest, + ::testing::Values( + GameType::tes5)); - TEST_P(GameCacheTest, copyConstructorShouldCopyCachedData) { - initialiseGame(); +TEST_P(GameCacheTest, copyConstructorShouldCopyCachedData) { + initialiseGame(); - cache.CacheCondition(condition, true); - cache.AddPlugin(Plugin(game, blankEsm, true)); - Message expectedMessage(Message::Type::say, "1"); - cache.AppendMessage(expectedMessage); - cache.SetLoadOrderSorted(true); + cache_.CacheCondition(condition, true); + cache_.AddPlugin(Plugin(game_, blankEsm, true)); + Message expectedMessage(Message::Type::say, "1"); + cache_.AppendMessage(expectedMessage); + cache_.SetLoadOrderSorted(true); - GameCache otherCache(cache); - EXPECT_EQ(std::make_pair(true, true), otherCache.GetCachedCondition(conditionLowercase)); - EXPECT_EQ(blankEsm, otherCache.GetPlugin(blankEsm).Name()); - ASSERT_EQ(1, otherCache.GetMessages().size()); - EXPECT_EQ(expectedMessage, otherCache.GetMessages()[0]); - } + GameCache otherCache(cache_); + EXPECT_EQ(std::make_pair(true, true), otherCache.GetCachedCondition(conditionLowercase)); + EXPECT_EQ(blankEsm, otherCache.GetPlugin(blankEsm).Name()); + ASSERT_EQ(1, otherCache.GetMessages().size()); + EXPECT_EQ(expectedMessage, otherCache.GetMessages()[0]); +} - TEST_P(GameCacheTest, assignmentOperatorShouldCopyCachedData) { - initialiseGame(); +TEST_P(GameCacheTest, assignmentOperatorShouldCopyCachedData) { + initialiseGame(); - cache.CacheCondition(condition, true); - cache.AddPlugin(Plugin(game, blankEsm, true)); - Message expectedMessage(Message::Type::say, "1"); - cache.AppendMessage(expectedMessage); - cache.SetLoadOrderSorted(true); + cache_.CacheCondition(condition, true); + cache_.AddPlugin(Plugin(game_, blankEsm, true)); + Message expectedMessage(Message::Type::say, "1"); + cache_.AppendMessage(expectedMessage); + cache_.SetLoadOrderSorted(true); - GameCache otherCache = cache; - EXPECT_EQ(std::make_pair(true, true), otherCache.GetCachedCondition(conditionLowercase)); - EXPECT_EQ(blankEsm, otherCache.GetPlugin(blankEsm).Name()); - ASSERT_EQ(1, otherCache.GetMessages().size()); - EXPECT_EQ(expectedMessage, otherCache.GetMessages()[0]); - } + GameCache otherCache = cache_; + EXPECT_EQ(std::make_pair(true, true), otherCache.GetCachedCondition(conditionLowercase)); + EXPECT_EQ(blankEsm, otherCache.GetPlugin(blankEsm).Name()); + ASSERT_EQ(1, otherCache.GetMessages().size()); + EXPECT_EQ(expectedMessage, otherCache.GetMessages()[0]); +} - TEST_P(GameCacheTest, gettingATrueConditionShouldReturnATrueTruePair) { - EXPECT_NO_THROW(cache.CacheCondition(condition, true)); +TEST_P(GameCacheTest, gettingATrueConditionShouldReturnATrueTruePair) { + EXPECT_NO_THROW(cache_.CacheCondition(condition, true)); - EXPECT_EQ(std::make_pair(true, true), cache.GetCachedCondition(conditionLowercase)); - } + EXPECT_EQ(std::make_pair(true, true), cache_.GetCachedCondition(conditionLowercase)); +} - TEST_P(GameCacheTest, gettingAFalseConditionShouldReturnAFalseTruePair) { - EXPECT_NO_THROW(cache.CacheCondition(condition, false)); +TEST_P(GameCacheTest, gettingAFalseConditionShouldReturnAFalseTruePair) { + EXPECT_NO_THROW(cache_.CacheCondition(condition, false)); - EXPECT_EQ(std::make_pair(false, true), cache.GetCachedCondition(conditionLowercase)); - } + EXPECT_EQ(std::make_pair(false, true), cache_.GetCachedCondition(conditionLowercase)); +} - TEST_P(GameCacheTest, gettingANonCachedConditionShouldReturnAFalseFalsePair) { - EXPECT_EQ(std::make_pair(false, false), cache.GetCachedCondition(condition)); - } +TEST_P(GameCacheTest, gettingANonCachedConditionShouldReturnAFalseFalsePair) { + EXPECT_EQ(std::make_pair(false, false), cache_.GetCachedCondition(condition)); +} - TEST_P(GameCacheTest, addingAPluginThatDoesNotExistShouldSucceed) { - initialiseGame(); +TEST_P(GameCacheTest, addingAPluginThatDoesNotExistShouldSucceed) { + initialiseGame(); - cache.AddPlugin(Plugin(game, blankEsm, true)); - EXPECT_EQ(blankEsm, cache.GetPlugin(blankEsm).Name()); - } + cache_.AddPlugin(Plugin(game_, blankEsm, true)); + EXPECT_EQ(blankEsm, cache_.GetPlugin(blankEsm).Name()); +} - TEST_P(GameCacheTest, addingAPluginThatIsAlreadyCachedShouldOverwriteExistingEntry) { - initialiseGame(); +TEST_P(GameCacheTest, addingAPluginThatIsAlreadyCachedShouldOverwriteExistingEntry) { + initialiseGame(); - cache.AddPlugin(Plugin(game, blankEsm, true)); - EXPECT_EQ(0, cache.GetPlugin(blankEsm).Crc()); + cache_.AddPlugin(Plugin(game_, blankEsm, true)); + EXPECT_EQ(0, cache_.GetPlugin(blankEsm).Crc()); - cache.AddPlugin(Plugin(game, blankEsm, false)); - EXPECT_EQ(blankEsmCrc, cache.GetPlugin(blankEsm).Crc()); - } + cache_.AddPlugin(Plugin(game_, blankEsm, false)); + EXPECT_EQ(blankEsmCrc, cache_.GetPlugin(blankEsm).Crc()); +} - TEST_P(GameCacheTest, gettingAPluginThatIsNotCachedShouldThrow) { - EXPECT_ANY_THROW(cache.GetPlugin(blankEsm)); - } +TEST_P(GameCacheTest, gettingAPluginThatIsNotCachedShouldThrow) { + EXPECT_ANY_THROW(cache_.GetPlugin(blankEsm)); +} - TEST_P(GameCacheTest, gettingAPluginShouldBeCaseInsensitive) { - initialiseGame(); +TEST_P(GameCacheTest, gettingAPluginShouldBeCaseInsensitive) { + initialiseGame(); - cache.AddPlugin(Plugin(game, blankEsm, true)); - EXPECT_EQ(blankEsm, cache.GetPlugin(blankEsm).Name()); - } + cache_.AddPlugin(Plugin(game_, blankEsm, true)); + EXPECT_EQ(blankEsm, cache_.GetPlugin(blankEsm).Name()); +} - TEST_P(GameCacheTest, gettingPluginsShouldReturnAnEmptySetIfNoPluginsHaveBeenCached) { - EXPECT_TRUE(cache.GetPlugins().empty()); - } +TEST_P(GameCacheTest, gettingPluginsShouldReturnAnEmptySetIfNoPluginsHaveBeenCached) { + EXPECT_TRUE(cache_.GetPlugins().empty()); +} - TEST_P(GameCacheTest, gettingPluginsShouldReturnASetOfCachedPluginsIfPluginsHaveBeenCached) { - initialiseGame(); +TEST_P(GameCacheTest, gettingPluginsShouldReturnASetOfCachedPluginsIfPluginsHaveBeenCached) { + initialiseGame(); - cache.AddPlugin(Plugin(game, blankEsm, true)); - cache.AddPlugin(Plugin(game, blankMasterDependentEsm, true)); + cache_.AddPlugin(Plugin(game_, blankEsm, true)); + cache_.AddPlugin(Plugin(game_, blankMasterDependentEsm, true)); - EXPECT_EQ(std::set({ - Plugin(game, blankEsm, true), - Plugin(game, blankMasterDependentEsm, true), - }), cache.GetPlugins()); - } + EXPECT_EQ(std::set({ + Plugin(game_, blankEsm, true), + Plugin(game_, blankMasterDependentEsm, true), + }), cache_.GetPlugins()); +} - TEST_P(GameCacheTest, clearingCachedConditionsShouldNotThrowIfNoConditionsAreCached) { - EXPECT_NO_THROW(cache.ClearCachedConditions()); - } +TEST_P(GameCacheTest, clearingCachedConditionsShouldNotThrowIfNoConditionsAreCached) { + EXPECT_NO_THROW(cache_.ClearCachedConditions()); +} - TEST_P(GameCacheTest, clearingCachedConditionsShouldClearAnyCachedConditions) { - EXPECT_NO_THROW(cache.CacheCondition(condition, true)); +TEST_P(GameCacheTest, clearingCachedConditionsShouldClearAnyCachedConditions) { + EXPECT_NO_THROW(cache_.CacheCondition(condition, true)); - EXPECT_NO_THROW(cache.ClearCachedConditions()); + EXPECT_NO_THROW(cache_.ClearCachedConditions()); - EXPECT_EQ(std::make_pair(false, false), cache.GetCachedCondition(conditionLowercase)); - } + EXPECT_EQ(std::make_pair(false, false), cache_.GetCachedCondition(conditionLowercase)); +} - TEST_P(GameCacheTest, clearingCachedPluginsShouldNotThrowIfNoPluginsAreCached) { - EXPECT_NO_THROW(cache.ClearCachedPlugins()); - } +TEST_P(GameCacheTest, clearingCachedPluginsShouldNotThrowIfNoPluginsAreCached) { + EXPECT_NO_THROW(cache_.ClearCachedPlugins()); +} - TEST_P(GameCacheTest, clearingCachedPluginsShouldClearAnyCachedPlugins) { - initialiseGame(); +TEST_P(GameCacheTest, clearingCachedPluginsShouldClearAnyCachedPlugins) { + initialiseGame(); - cache.AddPlugin(Plugin(game, blankEsm, true)); - cache.ClearCachedPlugins(); + cache_.AddPlugin(Plugin(game_, blankEsm, true)); + cache_.ClearCachedPlugins(); - EXPECT_TRUE(cache.GetPlugins().empty()); - } + EXPECT_TRUE(cache_.GetPlugins().empty()); +} - TEST_P(GameCacheTest, aMessageShouldBeCachedByDefault) { - ASSERT_EQ(1, cache.GetMessages().size()); - } +TEST_P(GameCacheTest, aMessageShouldBeCachedByDefault) { + ASSERT_EQ(1, cache_.GetMessages().size()); +} - TEST_P(GameCacheTest, settingLoadOrderSortedToTrueShouldSupressDefaultCachedMessage) { - cache.SetLoadOrderSorted(true); +TEST_P(GameCacheTest, settingLoadOrderSortedToTrueShouldSupressDefaultCachedMessage) { + cache_.SetLoadOrderSorted(true); - ASSERT_TRUE(cache.GetMessages().empty()); - } + ASSERT_TRUE(cache_.GetMessages().empty()); +} - TEST_P(GameCacheTest, settingLoadOrderSortedToFalseShouldReverseTheDefaultCachedMessageSuppression) { - auto expectedMessages = cache.GetMessages(); - cache.SetLoadOrderSorted(true); - cache.SetLoadOrderSorted(false); +TEST_P(GameCacheTest, settingLoadOrderSortedToFalseShouldReverseTheDefaultCachedMessageSuppression) { + auto expectedMessages = cache_.GetMessages(); + cache_.SetLoadOrderSorted(true); + cache_.SetLoadOrderSorted(false); - ASSERT_EQ(expectedMessages, cache.GetMessages()); - } + ASSERT_EQ(expectedMessages, cache_.GetMessages()); +} - TEST_P(GameCacheTest, appendingMessagesShouldStoreThemInTheGivenOrder) { - std::vector messages({ - Message(Message::Type::say, "1"), - Message(Message::Type::error, "2"), - }); - for (const auto& message : messages) - cache.AppendMessage(message); +TEST_P(GameCacheTest, appendingMessagesShouldStoreThemInTheGivenOrder) { + std::vector messages({ + Message(Message::Type::say, "1"), + Message(Message::Type::error, "2"), + }); + for (const auto& message : messages) + cache_.AppendMessage(message); - ASSERT_EQ(3, cache.GetMessages().size()); - EXPECT_EQ(messages[0], cache.GetMessages()[0]); - EXPECT_EQ(messages[1], cache.GetMessages()[1]); - } + ASSERT_EQ(3, cache_.GetMessages().size()); + EXPECT_EQ(messages[0], cache_.GetMessages()[0]); + EXPECT_EQ(messages[1], cache_.GetMessages()[1]); +} - TEST_P(GameCacheTest, clearingMessagesShouldRemoveAllAppendedMessages) { - std::vector messages({ - Message(Message::Type::say, "1"), - Message(Message::Type::error, "2"), - }); - for (const auto& message : messages) - cache.AppendMessage(message); +TEST_P(GameCacheTest, clearingMessagesShouldRemoveAllAppendedMessages) { + std::vector messages({ + Message(Message::Type::say, "1"), + Message(Message::Type::error, "2"), + }); + for (const auto& message : messages) + cache_.AppendMessage(message); - auto previousSize = cache.GetMessages().size(); + auto previousSize = cache_.GetMessages().size(); - cache.ClearMessages(); + cache_.ClearMessages(); - EXPECT_EQ(previousSize - messages.size(), cache.GetMessages().size()); - } - } + EXPECT_EQ(previousSize - messages.size(), cache_.GetMessages().size()); +} +} } #endif diff --git a/src/tests/backend/game/game_settings_test.h b/src/tests/backend/game/game_settings_test.h index 944d529c..a5ad59e1 100644 --- a/src/tests/backend/game/game_settings_test.h +++ b/src/tests/backend/game/game_settings_test.h @@ -22,256 +22,256 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_GAME_SETTINGS -#define LOOT_TEST_BACKEND_GAME_SETTINGS +#ifndef LOOT_TESTS_BACKEND_GAME_GAME_SETTINGS_TEST +#define LOOT_TESTS_BACKEND_GAME_GAME_SETTINGS_TEST -#include "backend/app/loot_paths.h" #include "backend/game/game_settings.h" +#include "backend/app/loot_paths.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class GameSettingsTest : public BaseGameTest { - protected: - GameSettings game; - }; +namespace test { +class GameSettingsTest : public BaseGameTest { +protected: + GameSettings settings_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - // Just test with one game because if it works for one it will work for them - // all. - INSTANTIATE_TEST_CASE_P(, - GameSettingsTest, - ::testing::Values( - GameType::tes5)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +// Just test with one game because if it works for one it will work for them +// all. +INSTANTIATE_TEST_CASE_P(, + GameSettingsTest, + ::testing::Values( + GameType::tes5)); - TEST_P(GameSettingsTest, defaultConstructorShouldInitialiseIdToAutodetectAndAllOtherSettingsToEmptyStrings) { - EXPECT_EQ(GameType::autodetect, game.Type()); - EXPECT_EQ("", game.Name()); - EXPECT_EQ("", game.FolderName()); - EXPECT_EQ("", game.Master()); - EXPECT_EQ("", game.RegistryKey()); - EXPECT_EQ("", game.RepoURL()); - EXPECT_EQ("", game.RepoBranch()); +TEST_P(GameSettingsTest, defaultConstructorShouldInitialiseIdToAutodetectAndAllOtherSettingsToEmptyStrings) { + EXPECT_EQ(GameType::autodetect, settings_.Type()); + EXPECT_EQ("", settings_.Name()); + EXPECT_EQ("", settings_.FolderName()); + EXPECT_EQ("", settings_.Master()); + EXPECT_EQ("", settings_.RegistryKey()); + EXPECT_EQ("", settings_.RepoURL()); + EXPECT_EQ("", settings_.RepoBranch()); - EXPECT_EQ("", game.GamePath()); - EXPECT_EQ("", game.DataPath()); - EXPECT_EQ("", game.MasterlistPath()); - EXPECT_EQ("", game.UserlistPath()); - } + EXPECT_EQ("", settings_.GamePath()); + EXPECT_EQ("", settings_.DataPath()); + EXPECT_EQ("", settings_.MasterlistPath()); + EXPECT_EQ("", settings_.UserlistPath()); +} - TEST_P(GameSettingsTest, idConstructorShouldInitialiseSettingsToDefaultsForThatGame) { - game = GameSettings(GameType::tes5); +TEST_P(GameSettingsTest, idConstructorShouldInitialiseSettingsToDefaultsForThatGame) { + settings_ = GameSettings(GameType::tes5); - EXPECT_EQ(GameType::tes5, game.Type()); - EXPECT_EQ("TES V: Skyrim", game.Name()); - EXPECT_EQ("Skyrim", game.FolderName()); - EXPECT_EQ("Skyrim.esm", game.Master()); - EXPECT_EQ("Software\\Bethesda Softworks\\Skyrim\\Installed Path", game.RegistryKey()); - EXPECT_EQ("https://github.com/loot/skyrim.git", game.RepoURL()); - // Repo branch changes between LOOT versions, so don't check an exact value. - EXPECT_NE("", game.RepoBranch()); + EXPECT_EQ(GameType::tes5, settings_.Type()); + EXPECT_EQ("TES V: Skyrim", settings_.Name()); + EXPECT_EQ("Skyrim", settings_.FolderName()); + EXPECT_EQ("Skyrim.esm", settings_.Master()); + EXPECT_EQ("Software\\Bethesda Softworks\\Skyrim\\Installed Path", settings_.RegistryKey()); + EXPECT_EQ("https://github.com/loot/skyrim.git", settings_.RepoURL()); + // Repo branch changes between LOOT versions, so don't check an exact value. + EXPECT_NE("", settings_.RepoBranch()); - EXPECT_EQ("", game.GamePath()); - EXPECT_EQ("", game.DataPath()); - EXPECT_EQ(LootPaths::getLootDataPath() / "Skyrim" / "masterlist.yaml", game.MasterlistPath()); - EXPECT_EQ(LootPaths::getLootDataPath() / "Skyrim" / "userlist.yaml", game.UserlistPath()); - } + EXPECT_EQ("", settings_.GamePath()); + EXPECT_EQ("", settings_.DataPath()); + EXPECT_EQ(LootPaths::getLootDataPath() / "Skyrim" / "masterlist.yaml", settings_.MasterlistPath()); + EXPECT_EQ(LootPaths::getLootDataPath() / "Skyrim" / "userlist.yaml", settings_.UserlistPath()); +} - TEST_P(GameSettingsTest, idConstructorShouldSetGameFolderIfGiven) { - game = GameSettings(GameType::tes5, "folder"); +TEST_P(GameSettingsTest, idConstructorShouldSetGameFolderIfGiven) { + settings_ = GameSettings(GameType::tes5, "folder"); - EXPECT_EQ("folder", game.FolderName()); - EXPECT_EQ(LootPaths::getLootDataPath() / "folder" / "masterlist.yaml", game.MasterlistPath()); - EXPECT_EQ(LootPaths::getLootDataPath() / "folder" / "userlist.yaml", game.UserlistPath()); - } + EXPECT_EQ("folder", settings_.FolderName()); + EXPECT_EQ(LootPaths::getLootDataPath() / "folder" / "masterlist.yaml", settings_.MasterlistPath()); + EXPECT_EQ(LootPaths::getLootDataPath() / "folder" / "userlist.yaml", settings_.UserlistPath()); +} - TEST_P(GameSettingsTest, isInstalledShouldBeFalseIfGamePathIsNotSet) { - GameSettings game; - EXPECT_FALSE(game.IsInstalled()); - } +TEST_P(GameSettingsTest, isInstalledShouldBeFalseIfGamePathIsNotSet) { + GameSettings settings_; + EXPECT_FALSE(settings_.IsInstalled()); +} - TEST_P(GameSettingsTest, isInstalledShouldBeTrueIfGamePathIsValid) { - game = GameSettings(GameType::tes5); - game.SetGamePath(dataPath.parent_path()); - EXPECT_TRUE(game.IsInstalled()); - } +TEST_P(GameSettingsTest, isInstalledShouldBeTrueIfGamePathIsValid) { + settings_ = GameSettings(GameType::tes5); + settings_.SetGamePath(dataPath.parent_path()); + EXPECT_TRUE(settings_.IsInstalled()); +} - TEST_P(GameSettingsTest, gameSettingsWithTheSameIdsShouldBeEqual) { - GameSettings game1 = GameSettings(GameType::tes5, "game1") - .SetMaster("master1") - .SetRegistryKey("key1") - .SetRepoURL("url1") - .SetRepoBranch("branch1") - .SetGamePath("path1"); - GameSettings game2 = GameSettings(GameType::tes5, "game2") - .SetMaster("master2") - .SetRegistryKey("key2") - .SetRepoURL("url2") - .SetRepoBranch("branch2") - .SetGamePath("path2"); +TEST_P(GameSettingsTest, gameSettingsWithTheSameIdsShouldBeEqual) { + GameSettings game1 = GameSettings(GameType::tes5, "game1") + .SetMaster("master1") + .SetRegistryKey("key1") + .SetRepoURL("url1") + .SetRepoBranch("branch1") + .SetGamePath("path1"); + GameSettings game2 = GameSettings(GameType::tes5, "game2") + .SetMaster("master2") + .SetRegistryKey("key2") + .SetRepoURL("url2") + .SetRepoBranch("branch2") + .SetGamePath("path2"); - EXPECT_TRUE(game1 == game2); - } + EXPECT_TRUE(game1 == game2); +} - TEST_P(GameSettingsTest, gameSettingsWithTheSameNameShouldBeEqual) { - GameSettings game1 = GameSettings(GameType::tes4) - .SetName("name"); - GameSettings game2 = GameSettings(GameType::tes5) - .SetName("name"); +TEST_P(GameSettingsTest, gameSettingsWithTheSameNameShouldBeEqual) { + GameSettings game1 = GameSettings(GameType::tes4) + .SetName("name"); + GameSettings game2 = GameSettings(GameType::tes5) + .SetName("name"); - EXPECT_TRUE(game1 == game2); - } + EXPECT_TRUE(game1 == game2); +} - TEST_P(GameSettingsTest, gameSettingsWithDifferentIdsAndNamesShouldNotBeEqual) { - GameSettings game1 = GameSettings(GameType::tes4); - GameSettings game2 = GameSettings(GameType::tes5); +TEST_P(GameSettingsTest, gameSettingsWithDifferentIdsAndNamesShouldNotBeEqual) { + GameSettings game1 = GameSettings(GameType::tes4); + GameSettings game2 = GameSettings(GameType::tes5); - EXPECT_FALSE(game1 == game2); - } + EXPECT_FALSE(game1 == game2); +} - TEST_P(GameSettingsTest, getArchiveFileExtensionShouldReturnDotBa2IfGameIdIsFallout4) { - GameSettings game(GameType::fo4); - EXPECT_EQ(".ba2", game.GetArchiveFileExtension()); - } +TEST_P(GameSettingsTest, getArchiveFileExtensionShouldReturnDotBa2IfGameIdIsFallout4) { + GameSettings settings_(GameType::fo4); + EXPECT_EQ(".ba2", settings_.GetArchiveFileExtension()); +} - TEST_P(GameSettingsTest, getArchiveFileExtensionShouldReturnDotBsaIfGameIdIsNotFallout4) { - GameSettings game; - EXPECT_EQ(".bsa", game.GetArchiveFileExtension()); - } +TEST_P(GameSettingsTest, getArchiveFileExtensionShouldReturnDotBsaIfGameIdIsNotFallout4) { + GameSettings settings_; + EXPECT_EQ(".bsa", settings_.GetArchiveFileExtension()); +} - TEST_P(GameSettingsTest, setNameShouldStoreGivenValue) { - GameSettings game; - game.SetName("name"); - EXPECT_EQ("name", game.Name()); - } +TEST_P(GameSettingsTest, setNameShouldStoreGivenValue) { + GameSettings settings_; + settings_.SetName("name"); + EXPECT_EQ("name", settings_.Name()); +} - TEST_P(GameSettingsTest, setMasterShouldStoreGivenValue) { - GameSettings game; - game.SetMaster("master"); - EXPECT_EQ("master", game.Master()); - } +TEST_P(GameSettingsTest, setMasterShouldStoreGivenValue) { + GameSettings settings_; + settings_.SetMaster("master"); + EXPECT_EQ("master", settings_.Master()); +} - TEST_P(GameSettingsTest, setRegistryKeyShouldStoreGivenValue) { - GameSettings game; - game.SetRegistryKey("key"); - EXPECT_EQ("key", game.RegistryKey()); - } +TEST_P(GameSettingsTest, setRegistryKeyShouldStoreGivenValue) { + GameSettings settings_; + settings_.SetRegistryKey("key"); + EXPECT_EQ("key", settings_.RegistryKey()); +} - TEST_P(GameSettingsTest, setRepoUrlShouldStoreGivenValue) { - GameSettings game; - game.SetRepoURL("url"); - EXPECT_EQ("url", game.RepoURL()); - } +TEST_P(GameSettingsTest, setRepoUrlShouldStoreGivenValue) { + GameSettings settings_; + settings_.SetRepoURL("url"); + EXPECT_EQ("url", settings_.RepoURL()); +} - TEST_P(GameSettingsTest, setRepoBranchShouldStoreGivenValue) { - GameSettings game; - game.SetRepoBranch("branch"); - EXPECT_EQ("branch", game.RepoBranch()); - } +TEST_P(GameSettingsTest, setRepoBranchShouldStoreGivenValue) { + GameSettings settings_; + settings_.SetRepoBranch("branch"); + EXPECT_EQ("branch", settings_.RepoBranch()); +} - TEST_P(GameSettingsTest, setGamePathShouldStoreGivenValue) { - std::string pathValue = "path"; - GameSettings game; +TEST_P(GameSettingsTest, setGamePathShouldStoreGivenValue) { + std::string pathValue = "path"; + GameSettings settings_; - game.SetGamePath(pathValue); - EXPECT_EQ(pathValue, game.GamePath().string()); - EXPECT_EQ(boost::filesystem::path(pathValue) / "Data", game.DataPath()); - } + settings_.SetGamePath(pathValue); + EXPECT_EQ(pathValue, settings_.GamePath().string()); + EXPECT_EQ(boost::filesystem::path(pathValue) / "Data", settings_.DataPath()); +} - TEST_P(GameSettingsTest, emittingYamlShouldSerialiseDataCorrectly) { - GameSettings game(GameType::tes5, "folder1"); - game.SetName("name1") - .SetMaster("master1") - .SetRegistryKey("key1") - .SetRepoURL("url1") - .SetRepoBranch("branch1") - .SetGamePath("path1"); +TEST_P(GameSettingsTest, emittingYamlShouldSerialiseDataCorrectly) { + GameSettings settings_(GameType::tes5, "folder1"); + settings_.SetName("name1") + .SetMaster("master1") + .SetRegistryKey("key1") + .SetRepoURL("url1") + .SetRepoBranch("branch1") + .SetGamePath("path1"); - YAML::Emitter e; - e << game; - EXPECT_STREQ("type: 'Skyrim'\n" - "folder: 'folder1'\n" - "name: 'name1'\n" - "master: 'master1'\n" - "repo: 'url1'\n" - "branch: 'branch1'\n" - "path: 'path1'\n" - "registry: 'key1'", e.c_str()); - } + YAML::Emitter e; + e << settings_; + EXPECT_STREQ("type: 'Skyrim'\n" + "folder: 'folder1'\n" + "name: 'name1'\n" + "master: 'master1'\n" + "repo: 'url1'\n" + "branch: 'branch1'\n" + "path: 'path1'\n" + "registry: 'key1'", e.c_str()); +} - TEST_P(GameSettingsTest, encodingAsYamlShouldConvertDataCorrectly) { - GameSettings game(GameType::tes5, "folder1"); - game.SetName("name1") - .SetMaster("master1") - .SetRegistryKey("key1") - .SetRepoURL("url1") - .SetRepoBranch("branch1") - .SetGamePath("path1"); +TEST_P(GameSettingsTest, encodingAsYamlShouldConvertDataCorrectly) { + GameSettings settings_(GameType::tes5, "folder1"); + settings_.SetName("name1") + .SetMaster("master1") + .SetRegistryKey("key1") + .SetRepoURL("url1") + .SetRepoBranch("branch1") + .SetGamePath("path1"); - YAML::Node node; - node = game; - EXPECT_EQ("Skyrim", node["type"].as()); - EXPECT_EQ("folder1", node["folder"].as()); - EXPECT_EQ("name1", node["name"].as()); - EXPECT_EQ("master1", node["master"].as()); - EXPECT_EQ("url1", node["repo"].as()); - EXPECT_EQ("branch1", node["branch"].as()); - EXPECT_EQ("path1", node["path"].as()); - EXPECT_EQ("key1", node["registry"].as()); - } + YAML::Node node; + node = settings_; + EXPECT_EQ("Skyrim", node["type"].as()); + EXPECT_EQ("folder1", node["folder"].as()); + EXPECT_EQ("name1", node["name"].as()); + EXPECT_EQ("master1", node["master"].as()); + EXPECT_EQ("url1", node["repo"].as()); + EXPECT_EQ("branch1", node["branch"].as()); + EXPECT_EQ("path1", node["path"].as()); + EXPECT_EQ("key1", node["registry"].as()); +} - TEST_P(GameSettingsTest, decodingFromYamlShouldInterpretTheYamlCorrectly) { - YAML::Node node = YAML::Load("type: 'Skyrim'\n" - "folder: 'folder1'\n" - "name: 'name1'\n" - "master: 'master1'\n" - "repo: 'url1'\n" - "branch: 'branch1'\n" - "path: 'path1'\n" - "registry: 'key1'"); +TEST_P(GameSettingsTest, decodingFromYamlShouldInterpretTheYamlCorrectly) { + YAML::Node node = YAML::Load("type: 'Skyrim'\n" + "folder: 'folder1'\n" + "name: 'name1'\n" + "master: 'master1'\n" + "repo: 'url1'\n" + "branch: 'branch1'\n" + "path: 'path1'\n" + "registry: 'key1'"); - GameSettings game = node.as(); - EXPECT_EQ(GameType::tes5, game.Type()); - EXPECT_EQ("name1", game.Name()); - EXPECT_EQ("folder1", game.FolderName()); - EXPECT_EQ("master1", game.Master()); - EXPECT_EQ("key1", game.RegistryKey()); - EXPECT_EQ("url1", game.RepoURL()); - EXPECT_EQ("branch1", game.RepoBranch()); - EXPECT_EQ("path1", game.GamePath()); - } + GameSettings settings_ = node.as(); + EXPECT_EQ(GameType::tes5, settings_.Type()); + EXPECT_EQ("name1", settings_.Name()); + EXPECT_EQ("folder1", settings_.FolderName()); + EXPECT_EQ("master1", settings_.Master()); + EXPECT_EQ("key1", settings_.RegistryKey()); + EXPECT_EQ("url1", settings_.RepoURL()); + EXPECT_EQ("branch1", settings_.RepoBranch()); + EXPECT_EQ("path1", settings_.GamePath()); +} - TEST_P(GameSettingsTest, decodingFromAnInvalidYamlMapShouldThrowAnException) { - YAML::Node node = YAML::Load("type: 'Invalid'\n"); - EXPECT_ANY_THROW(node.as()); - } +TEST_P(GameSettingsTest, decodingFromAnInvalidYamlMapShouldThrowAnException) { + YAML::Node node = YAML::Load("type: 'Invalid'\n"); + EXPECT_ANY_THROW(node.as()); +} - TEST_P(GameSettingsTest, decodingFromAYamlScalarShouldThrowAnException) { - YAML::Node node = YAML::Load("scalar"); - EXPECT_ANY_THROW(node.as()); - } +TEST_P(GameSettingsTest, decodingFromAYamlScalarShouldThrowAnException) { + YAML::Node node = YAML::Load("scalar"); + EXPECT_ANY_THROW(node.as()); +} - TEST_P(GameSettingsTest, decodingFromAYamlListShouldThrowAnException) { - YAML::Node node = YAML::Load("[0, 1, 2]"); - EXPECT_ANY_THROW(node.as()); - } +TEST_P(GameSettingsTest, decodingFromAYamlListShouldThrowAnException) { + YAML::Node node = YAML::Load("[0, 1, 2]"); + EXPECT_ANY_THROW(node.as()); +} - TEST_P(GameSettingsTest, decodingFromAnIncompleteYamlMapShouldUseDefaultValuesForMissingSettings) { - // Test inheritance of unspecified settings. - YAML::Node node = YAML::Load("type: 'Skyrim'\n" - "folder: 'folder1'\n" - "master: 'master1'\n" - "repo: 'url1'\n" - "branch: 'branch1'\n"); +TEST_P(GameSettingsTest, decodingFromAnIncompleteYamlMapShouldUseDefaultValuesForMissingSettings) { + // Test inheritance of unspecified settings. + YAML::Node node = YAML::Load("type: 'Skyrim'\n" + "folder: 'folder1'\n" + "master: 'master1'\n" + "repo: 'url1'\n" + "branch: 'branch1'\n"); - game = node.as(); - EXPECT_EQ(GameType::tes5, game.Type()); - EXPECT_EQ("TES V: Skyrim", game.Name()); - EXPECT_EQ("Software\\Bethesda Softworks\\Skyrim\\Installed Path", game.RegistryKey()); - EXPECT_EQ("", game.GamePath()); - } - } + settings_ = node.as(); + EXPECT_EQ(GameType::tes5, settings_.Type()); + EXPECT_EQ("TES V: Skyrim", settings_.Name()); + EXPECT_EQ("Software\\Bethesda Softworks\\Skyrim\\Installed Path", settings_.RegistryKey()); + EXPECT_EQ("", settings_.GamePath()); +} +} } #endif diff --git a/src/tests/backend/game/game_test.h b/src/tests/backend/game/game_test.h index d7d8abff..b03e68d1 100644 --- a/src/tests/backend/game/game_test.h +++ b/src/tests/backend/game/game_test.h @@ -22,302 +22,302 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_GAME -#define LOOT_TEST_BACKEND_GAME +#ifndef LOOT_TESTS_BACKEND_GAME_GAME_TEST +#define LOOT_TESTS_BACKEND_GAME_GAME_TEST -#include "backend/error.h" -#include "backend/app/loot_paths.h" #include "backend/game/game.h" -#include "load_order_handler_test.h" +#include "backend/app/loot_paths.h" +#include "backend/error.h" +#include "tests/backend/game/load_order_handler_test.h" namespace loot { - namespace test { - class GameTest : public BaseGameTest { - protected: +namespace test { +class GameTest : public BaseGameTest { +protected: #ifndef _WIN32 - void TearDown() { - BaseGameTest::TearDown(); + void TearDown() { + BaseGameTest::TearDown(); - ASSERT_NO_THROW(boost::filesystem::remove_all(LootPaths::getLootDataPath())); - } + ASSERT_NO_THROW(boost::filesystem::remove_all(LootPaths::getLootDataPath())); + } #endif - }; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - GameTest, - ::testing::Values( - GameType::tes4, - GameType::tes5, - GameType::fo3, - GameType::fonv, - GameType::fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + GameTest, + ::testing::Values( + GameType::tes4, + GameType::tes5, + GameType::fo3, + GameType::fonv, + GameType::fo4)); - TEST_P(GameTest, defaultConstructorShouldConstructWithDefaultGameSettings) { - GameSettings settings; - Game game; +TEST_P(GameTest, defaultConstructorShouldConstructWithDefaultGameSettings) { + GameSettings settings; + Game game; - EXPECT_EQ(settings.Type(), game.Type()); - EXPECT_EQ(settings.FolderName(), game.FolderName()); - } + EXPECT_EQ(settings.Type(), game.Type()); + EXPECT_EQ(settings.FolderName(), game.FolderName()); +} - TEST_P(GameTest, constructingFromGameSettingsShouldUseTheirValues) { - GameSettings settings = GameSettings(GetParam(), "folder"); - settings.SetName("foo"); - settings.SetMaster(blankEsm); - settings.SetRegistryKey("foo"); - settings.SetRepoURL("foo"); - settings.SetRepoBranch("foo"); - settings.SetGamePath(localPath); - Game game = Game(settings); +TEST_P(GameTest, constructingFromGameSettingsShouldUseTheirValues) { + GameSettings settings = GameSettings(GetParam(), "folder"); + settings.SetName("foo"); + settings.SetMaster(blankEsm); + settings.SetRegistryKey("foo"); + settings.SetRepoURL("foo"); + settings.SetRepoBranch("foo"); + settings.SetGamePath(localPath); + Game game = Game(settings); - EXPECT_EQ(GetParam(), game.Type()); - EXPECT_EQ(settings.Name(), game.Name()); - EXPECT_EQ(settings.FolderName(), game.FolderName()); - EXPECT_EQ(settings.Master(), game.Master()); - EXPECT_EQ(settings.RegistryKey(), game.RegistryKey()); - EXPECT_EQ(settings.RepoURL(), game.RepoURL()); - EXPECT_EQ(settings.RepoBranch(), game.RepoBranch()); + EXPECT_EQ(GetParam(), game.Type()); + EXPECT_EQ(settings.Name(), game.Name()); + EXPECT_EQ(settings.FolderName(), game.FolderName()); + EXPECT_EQ(settings.Master(), game.Master()); + EXPECT_EQ(settings.RegistryKey(), game.RegistryKey()); + EXPECT_EQ(settings.RepoURL(), game.RepoURL()); + EXPECT_EQ(settings.RepoBranch(), game.RepoBranch()); - EXPECT_EQ(settings.GamePath(), game.GamePath()); - } + EXPECT_EQ(settings.GamePath(), game.GamePath()); +} - TEST_P(GameTest, constructingFromIdAndFolderShouldPassThemToGameSettingsConstructor) { - GameSettings settings = GameSettings(GetParam(), "folder"); - Game game = Game(GetParam(), "folder"); +TEST_P(GameTest, constructingFromIdAndFolderShouldPassThemToGameSettingsConstructor) { + GameSettings settings = GameSettings(GetParam(), "folder"); + Game game = Game(GetParam(), "folder"); - EXPECT_EQ(settings.Type(), game.Type()); - EXPECT_EQ(settings.FolderName(), game.FolderName()); - } + EXPECT_EQ(settings.Type(), game.Type()); + EXPECT_EQ(settings.FolderName(), game.FolderName()); +} - TEST_P(GameTest, initShouldThrowIfGameHasAnInvalidId) { - Game game; - EXPECT_THROW(game.Init(false), Error); - EXPECT_THROW(game.Init(true), Error); - EXPECT_THROW(game.Init(false, localPath), Error); - EXPECT_THROW(game.Init(true, localPath), Error); - } +TEST_P(GameTest, initShouldThrowIfGameHasAnInvalidId) { + Game game; + EXPECT_THROW(game.Init(false), Error); + EXPECT_THROW(game.Init(true), Error); + EXPECT_THROW(game.Init(false, localPath), Error); + EXPECT_THROW(game.Init(true, localPath), Error); +} #ifndef _WIN32 // Testing on Windows will find real game installs in the Registry, so cannot // test autodetection fully unless on Linux. - TEST_P(GameTest, initShouldThrowOnLinuxIfGamePathIsNotGiven) { - Game game = Game(GetParam()); - EXPECT_THROW(game.Init(false), Error); - EXPECT_THROW(game.Init(true), Error); - EXPECT_THROW(game.Init(false, localPath), Error); - EXPECT_THROW(game.Init(true, localPath), Error); - } +TEST_P(GameTest, initShouldThrowOnLinuxIfGamePathIsNotGiven) { + Game game = Game(GetParam()); + EXPECT_THROW(game.Init(false), Error); + EXPECT_THROW(game.Init(true), Error); + EXPECT_THROW(game.Init(false, localPath), Error); + EXPECT_THROW(game.Init(true, localPath), Error); +} - TEST_P(GameTest, initShouldThrowOnLinuxIfLocalPathIsNotGiven) { - Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); - ASSERT_FALSE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); - EXPECT_THROW(game.Init(false), Error); - } +TEST_P(GameTest, initShouldThrowOnLinuxIfLocalPathIsNotGiven) { + Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); + ASSERT_FALSE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); + EXPECT_THROW(game.Init(false), Error); +} - // Testing on Windows will find real LOOT installs, and they shouldn't be - // interfered with. - TEST_P(GameTest, initShouldNotCreateAGameFolderIfTheCreateFolderArgumentIsFalse) { - Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); +// Testing on Windows will find real LOOT installs, and they shouldn't be +// interfered with. +TEST_P(GameTest, initShouldNotCreateAGameFolderIfTheCreateFolderArgumentIsFalse) { + Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); - ASSERT_FALSE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); - EXPECT_NO_THROW(game.Init(false, localPath)); + ASSERT_FALSE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); + EXPECT_NO_THROW(game.Init(false, localPath)); - EXPECT_FALSE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); - } + EXPECT_FALSE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); +} - TEST_P(GameTest, initShouldCreateAGameFolderIfTheCreateFolderArgumentIsTrue) { - Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, initShouldCreateAGameFolderIfTheCreateFolderArgumentIsTrue) { + Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); - ASSERT_FALSE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); - EXPECT_NO_THROW(game.Init(true, localPath)); + ASSERT_FALSE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); + EXPECT_NO_THROW(game.Init(true, localPath)); - EXPECT_TRUE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); - } + EXPECT_TRUE(boost::filesystem::exists(LootPaths::getLootDataPath() / game.FolderName())); +} #else - TEST_P(GameTest, initShouldNotThrowOnWindowsIfLocalPathIsNotGiven) { - Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, initShouldNotThrowOnWindowsIfLocalPathIsNotGiven) { + Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); - EXPECT_NO_THROW(game.Init(false)); - } + EXPECT_NO_THROW(game.Init(false)); +} #endif - TEST_P(GameTest, initShouldNotThrowIfGameAndLocalPathsAreGiven) { - Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, initShouldNotThrowIfGameAndLocalPathsAreGiven) { + Game game = Game(GetParam()).SetGamePath(dataPath.parent_path()); - EXPECT_NO_THROW(game.Init(false, localPath)); - } + EXPECT_NO_THROW(game.Init(false, localPath)); +} - TEST_P(GameTest, redatePluginsShouldThrowIfTheGameHasNotYetBeenInitialisedForSkyrimAndNotForOtherGames) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, redatePluginsShouldThrowIfTheGameHasNotYetBeenInitialisedForSkyrimAndNotForOtherGames) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - if (GetParam() == GameType::tes5) - EXPECT_THROW(game.RedatePlugins(), Error); - else - EXPECT_NO_THROW(game.RedatePlugins()); - } + if (GetParam() == GameType::tes5) + EXPECT_THROW(game.RedatePlugins(), Error); + else + EXPECT_NO_THROW(game.RedatePlugins()); +} - TEST_P(GameTest, redatePluginsShouldRedatePluginsForSkyrimAndDoNothingForOtherGames) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - game.Init(false, localPath); +TEST_P(GameTest, redatePluginsShouldRedatePluginsForSkyrimAndDoNothingForOtherGames) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + game.Init(false, localPath); - std::vector> loadOrder = getInitialLoadOrder(); + std::vector> loadOrder = getInitialLoadOrder(); - // First set reverse timestamps to be sure. - time_t time = boost::filesystem::last_write_time(dataPath / masterFile); - for (size_t i = 1; i < loadOrder.size(); ++i) { - if (!boost::filesystem::exists(dataPath / loadOrder[i].first)) - loadOrder[i].first += ".ghost"; + // First set reverse timestamps to be sure. + time_t time = boost::filesystem::last_write_time(dataPath / masterFile); + for (size_t i = 1; i < loadOrder.size(); ++i) { + if (!boost::filesystem::exists(dataPath / loadOrder[i].first)) + loadOrder[i].first += ".ghost"; - boost::filesystem::last_write_time(dataPath / loadOrder[i].first, time - i * 60); - ASSERT_EQ(time - i * 60, boost::filesystem::last_write_time(dataPath / loadOrder[i].first)); - } + boost::filesystem::last_write_time(dataPath / loadOrder[i].first, time - i * 60); + ASSERT_EQ(time - i * 60, boost::filesystem::last_write_time(dataPath / loadOrder[i].first)); + } - EXPECT_NO_THROW(game.RedatePlugins()); + EXPECT_NO_THROW(game.RedatePlugins()); - time_t interval = 60; - if (GetParam() != GameType::tes5) - interval *= -1; - for (size_t i = 0; i < loadOrder.size(); ++i) { - EXPECT_EQ(time + i * interval, boost::filesystem::last_write_time(dataPath / loadOrder[i].first)); - } - } + time_t interval = 60; + if (GetParam() != GameType::tes5) + interval *= -1; + for (size_t i = 0; i < loadOrder.size(); ++i) { + EXPECT_EQ(time + i * interval, boost::filesystem::last_write_time(dataPath / loadOrder[i].first)); + } +} - TEST_P(GameTest, loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfAllInstalledPlugins) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfAllInstalledPlugins) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - EXPECT_NO_THROW(game.LoadPlugins(true)); - EXPECT_EQ(11, game.GetPlugins().size()); + EXPECT_NO_THROW(game.LoadPlugins(true)); + EXPECT_EQ(11, game.GetPlugins().size()); - // Check that one plugin's header has been read. - ASSERT_NO_THROW(game.GetPlugin(masterFile)); - Plugin plugin = game.GetPlugin(masterFile); - EXPECT_EQ("v5.0", plugin.getDescription()); + // Check that one plugin's header has been read. + ASSERT_NO_THROW(game.GetPlugin(masterFile)); + Plugin plugin = game.GetPlugin(masterFile); + EXPECT_EQ("v5.0", plugin.getDescription()); - // Check that only the header has been read. - EXPECT_EQ(0, plugin.Crc()); - } + // Check that only the header has been read. + EXPECT_EQ(0, plugin.Crc()); +} - TEST_P(GameTest, loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllInstalledPlugins) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, loadPluginsWithHeadersOnlyFalseShouldFullyLoadAllInstalledPlugins) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - EXPECT_NO_THROW(game.LoadPlugins(false)); - EXPECT_EQ(11, game.GetPlugins().size()); + EXPECT_NO_THROW(game.LoadPlugins(false)); + EXPECT_EQ(11, game.GetPlugins().size()); - // Check that one plugin's header has been read. - ASSERT_NO_THROW(game.GetPlugin(blankEsm)); - Plugin plugin = game.GetPlugin(blankEsm); - EXPECT_EQ("v5.0", plugin.getDescription()); + // Check that one plugin's header has been read. + ASSERT_NO_THROW(game.GetPlugin(blankEsm)); + Plugin plugin = game.GetPlugin(blankEsm); + EXPECT_EQ("v5.0", plugin.getDescription()); - // Check that not only the header has been read. - EXPECT_EQ(blankEsmCrc, plugin.Crc()); - } + // Check that not only the header has been read. + EXPECT_EQ(blankEsmCrc, plugin.Crc()); +} - TEST_P(GameTest, pluginsShouldNotBeFullyLoadedByDefault) { - EXPECT_FALSE(Game().ArePluginsFullyLoaded()); - EXPECT_FALSE(Game(GameSettings()).ArePluginsFullyLoaded()); - EXPECT_FALSE(Game(GetParam(), "folder").ArePluginsFullyLoaded()); - } +TEST_P(GameTest, pluginsShouldNotBeFullyLoadedByDefault) { + EXPECT_FALSE(Game().ArePluginsFullyLoaded()); + EXPECT_FALSE(Game(GameSettings()).ArePluginsFullyLoaded()); + EXPECT_FALSE(Game(GetParam(), "folder").ArePluginsFullyLoaded()); +} - TEST_P(GameTest, pluginsShouldNotBeFullyLoadedAfterLoadingHeadersOnly) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, pluginsShouldNotBeFullyLoadedAfterLoadingHeadersOnly) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.LoadPlugins(true)); + ASSERT_NO_THROW(game.LoadPlugins(true)); - EXPECT_FALSE(game.ArePluginsFullyLoaded()); - } + EXPECT_FALSE(game.ArePluginsFullyLoaded()); +} - TEST_P(GameTest, pluginsShouldBeFullyLoadedAfterFullyLoadingThem) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, pluginsShouldBeFullyLoadedAfterFullyLoadingThem) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.LoadPlugins(false)); + ASSERT_NO_THROW(game.LoadPlugins(false)); - EXPECT_TRUE(game.ArePluginsFullyLoaded()); - } + EXPECT_TRUE(game.ArePluginsFullyLoaded()); +} - TEST_P(GameTest, shouldThrowIfCheckingIfPluginThatIsntLoadedIsActiveAndGameHasNotBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(GameTest, shouldThrowIfCheckingIfPluginThatIsntLoadedIsActiveAndGameHasNotBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - EXPECT_ANY_THROW(game.IsPluginActive(blankEsm)); - } + EXPECT_ANY_THROW(game.IsPluginActive(blankEsm)); +} - TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItHasNotBeenLoadedAndTheGameHasBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); +TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItHasNotBeenLoadedAndTheGameHasBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); - EXPECT_TRUE(game.IsPluginActive(blankEsm)); - } + EXPECT_TRUE(game.IsPluginActive(blankEsm)); +} - TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItHasNotBeenLoadedAndTheGameHasBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); +TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItHasNotBeenLoadedAndTheGameHasBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); - EXPECT_FALSE(game.IsPluginActive(blankEsp)); - } + EXPECT_FALSE(game.IsPluginActive(blankEsp)); +} - TEST_P(GameTest, shouldShowBlankEsmAsInactiveIfItsHeaderHasBeenLoadedAndGameHasNotBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.LoadPlugins(true)); +TEST_P(GameTest, shouldShowBlankEsmAsInactiveIfItsHeaderHasBeenLoadedAndGameHasNotBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.LoadPlugins(true)); - EXPECT_FALSE(game.IsPluginActive(blankEsm)); - } + EXPECT_FALSE(game.IsPluginActive(blankEsm)); +} - TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItsHeaderHasBeenLoadedAndGameHasNotBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.LoadPlugins(true)); +TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItsHeaderHasBeenLoadedAndGameHasNotBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.LoadPlugins(true)); - EXPECT_FALSE(game.IsPluginActive(blankEsp)); - } + EXPECT_FALSE(game.IsPluginActive(blankEsp)); +} - TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItsHeaderHasBeenLoadedAndTheGameHasBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - ASSERT_NO_THROW(game.LoadPlugins(true)); +TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItsHeaderHasBeenLoadedAndTheGameHasBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); - EXPECT_TRUE(game.IsPluginActive(blankEsm)); - } + EXPECT_TRUE(game.IsPluginActive(blankEsm)); +} - TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItsHeaderHasBeenLoadedAndTheGameHasBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - ASSERT_NO_THROW(game.LoadPlugins(true)); +TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItsHeaderHasBeenLoadedAndTheGameHasBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(true)); - EXPECT_FALSE(game.IsPluginActive(blankEsp)); - } + EXPECT_FALSE(game.IsPluginActive(blankEsp)); +} - TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItHasBeenFullyLoadedAndTheGameHasBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - ASSERT_NO_THROW(game.LoadPlugins(false)); +TEST_P(GameTest, shouldShowBlankEsmAsActiveIfItHasBeenFullyLoadedAndTheGameHasBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(false)); - EXPECT_TRUE(game.IsPluginActive(blankEsm)); - } + EXPECT_TRUE(game.IsPluginActive(blankEsm)); +} - TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItHasBeenFullyLoadedAndTheGameHasBeenInitialised) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - ASSERT_NO_THROW(game.LoadPlugins(false)); +TEST_P(GameTest, shouldShowBlankEspAsInactiveIfItHasBeenFullyLoadedAndTheGameHasBeenInitialised) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + ASSERT_NO_THROW(game.LoadPlugins(false)); - EXPECT_FALSE(game.IsPluginActive(blankEsp)); - } - } + EXPECT_FALSE(game.IsPluginActive(blankEsp)); +} +} } #endif diff --git a/src/tests/backend/game/load_order_handler_test.h b/src/tests/backend/game/load_order_handler_test.h index b9b825b9..3d195dae 100644 --- a/src/tests/backend/game/load_order_handler_test.h +++ b/src/tests/backend/game/load_order_handler_test.h @@ -22,137 +22,137 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_LOAD_ORDER_HANDLER -#define LOOT_TEST_BACKEND_LOAD_ORDER_HANDLER +#ifndef LOOT_TESTS_BACKEND_LOAD_ORDER_HANDLER_TEST +#define LOOT_TESTS_BACKEND_LOAD_ORDER_HANDLER_TEST -#include "backend/error.h" #include "backend/game/load_order_handler.h" +#include "backend/error.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class LoadOrderHandlerTest : public BaseGameTest { - protected: - LoadOrderHandler loh; - }; +namespace test { +class LoadOrderHandlerTest : public BaseGameTest { +protected: + LoadOrderHandler loadOrderHandler_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - LoadOrderHandlerTest, - ::testing::Values( - GameType::tes4, - GameType::tes5, - GameType::fo3, - GameType::fonv, - GameType::fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + LoadOrderHandlerTest, + ::testing::Values( + GameType::tes4, + GameType::tes5, + GameType::fo3, + GameType::fonv, + GameType::fo4)); - TEST_P(LoadOrderHandlerTest, initShouldThrowForAnInvalidGameId) { - GameSettings game(GameType::autodetect); - game.SetGamePath(dataPath.parent_path()); +TEST_P(LoadOrderHandlerTest, initShouldThrowForAnInvalidGameId) { + GameSettings game(GameType::autodetect); + game.SetGamePath(dataPath.parent_path()); - EXPECT_THROW(loh.Init(game), Error); - EXPECT_THROW(loh.Init(game), Error); - EXPECT_THROW(loh.Init(game, localPath), Error); - EXPECT_THROW(loh.Init(game, localPath), Error); - } + EXPECT_THROW(loadOrderHandler_.Init(game), Error); + EXPECT_THROW(loadOrderHandler_.Init(game), Error); + EXPECT_THROW(loadOrderHandler_.Init(game, localPath), Error); + EXPECT_THROW(loadOrderHandler_.Init(game, localPath), Error); +} - TEST_P(LoadOrderHandlerTest, initShouldThrowIfNoGamePathIsSet) { - GameSettings game(GetParam()); +TEST_P(LoadOrderHandlerTest, initShouldThrowIfNoGamePathIsSet) { + GameSettings game(GetParam()); - EXPECT_THROW(loh.Init(game), Error); - EXPECT_THROW(loh.Init(game), Error); - EXPECT_THROW(loh.Init(game, localPath), Error); - EXPECT_THROW(loh.Init(game, localPath), Error); - } + EXPECT_THROW(loadOrderHandler_.Init(game), Error); + EXPECT_THROW(loadOrderHandler_.Init(game), Error); + EXPECT_THROW(loadOrderHandler_.Init(game, localPath), Error); + EXPECT_THROW(loadOrderHandler_.Init(game, localPath), Error); +} #ifndef _WIN32 - TEST_P(LoadOrderHandlerTest, initShouldThrowOnLinuxIfNoLocalPathIsSet) { - GameSettings game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(LoadOrderHandlerTest, initShouldThrowOnLinuxIfNoLocalPathIsSet) { + GameSettings game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - EXPECT_THROW(loh.Init(game), Error); - } + EXPECT_THROW(loadOrderHandler_.Init(game), Error); +} #endif - TEST_P(LoadOrderHandlerTest, initShouldNotThrowIfAValidGameIdAndGamePathAndLocalPathAreSet) { - GameSettings game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(LoadOrderHandlerTest, initShouldNotThrowIfAValidGameIdAndGamePathAndLocalPathAreSet) { + GameSettings game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - EXPECT_NO_THROW(loh.Init(game, localPath)); - } + EXPECT_NO_THROW(loadOrderHandler_.Init(game, localPath)); +} - TEST_P(LoadOrderHandlerTest, isPluginActiveShouldThrowIfTheHandlerHasNotBeenInitialised) { - EXPECT_THROW(loh.IsPluginActive(masterFile), Error); - } +TEST_P(LoadOrderHandlerTest, isPluginActiveShouldThrowIfTheHandlerHasNotBeenInitialised) { + EXPECT_THROW(loadOrderHandler_.IsPluginActive(masterFile), Error); +} - TEST_P(LoadOrderHandlerTest, isPluginActiveShouldReturnCorrectPluginStatesAfterInitialisation) { - GameSettings game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(loh.Init(game, localPath)); +TEST_P(LoadOrderHandlerTest, isPluginActiveShouldReturnCorrectPluginStatesAfterInitialisation) { + GameSettings game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(loadOrderHandler_.Init(game, localPath)); - EXPECT_TRUE(loh.IsPluginActive(masterFile)); - EXPECT_TRUE(loh.IsPluginActive(blankEsm)); - EXPECT_FALSE(loh.IsPluginActive(blankEsp)); - } + EXPECT_TRUE(loadOrderHandler_.IsPluginActive(masterFile)); + EXPECT_TRUE(loadOrderHandler_.IsPluginActive(blankEsm)); + EXPECT_FALSE(loadOrderHandler_.IsPluginActive(blankEsp)); +} - TEST_P(LoadOrderHandlerTest, getLoadOrderShouldThrowIfTheHandlerHasNotBeenInitialised) { - EXPECT_THROW(loh.GetLoadOrder(), Error); - } +TEST_P(LoadOrderHandlerTest, getLoadOrderShouldThrowIfTheHandlerHasNotBeenInitialised) { + EXPECT_THROW(loadOrderHandler_.GetLoadOrder(), Error); +} - TEST_P(LoadOrderHandlerTest, getLoadOrderShouldReturnTheCurrentLoadOrder) { - GameSettings game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(loh.Init(game, localPath)); +TEST_P(LoadOrderHandlerTest, getLoadOrderShouldReturnTheCurrentLoadOrder) { + GameSettings game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(loadOrderHandler_.Init(game, localPath)); - ASSERT_EQ(getLoadOrder(), loh.GetLoadOrder()); - } + ASSERT_EQ(getLoadOrder(), loadOrderHandler_.GetLoadOrder()); +} - TEST_P(LoadOrderHandlerTest, setLoadOrderShouldThrowIfTheHandlerHasNotBeenInitialised) { - std::list loadOrder({ - masterFile, - blankEsm, - blankMasterDependentEsm, - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - }); +TEST_P(LoadOrderHandlerTest, setLoadOrderShouldThrowIfTheHandlerHasNotBeenInitialised) { + std::list loadOrder({ + masterFile, + blankEsm, + blankMasterDependentEsm, + blankDifferentEsm, + blankDifferentMasterDependentEsm, + blankDifferentEsp, + blankDifferentPluginDependentEsp, + blankEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankPluginDependentEsp, + }); - EXPECT_THROW(loh.SetLoadOrder(std::list()), Error); - } + EXPECT_THROW(loadOrderHandler_.SetLoadOrder(std::list()), Error); +} - TEST_P(LoadOrderHandlerTest, setLoadOrderShouldSetTheLoadOrder) { - GameSettings game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(loh.Init(game, localPath)); +TEST_P(LoadOrderHandlerTest, setLoadOrderShouldSetTheLoadOrder) { + GameSettings game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(loadOrderHandler_.Init(game, localPath)); - std::list loadOrder({ - masterFile, - blankEsm, - blankMasterDependentEsm, - blankDifferentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankPluginDependentEsp, - }); - EXPECT_NO_THROW(loh.SetLoadOrder(loadOrder)); + std::list loadOrder({ + masterFile, + blankEsm, + blankMasterDependentEsm, + blankDifferentEsm, + blankDifferentMasterDependentEsm, + blankDifferentEsp, + blankDifferentPluginDependentEsp, + blankEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankPluginDependentEsp, + }); + EXPECT_NO_THROW(loadOrderHandler_.SetLoadOrder(loadOrder)); - if (GetParam() == GameType::fo4) - loadOrder.erase(begin(loadOrder)); + if (GetParam() == GameType::fo4) + loadOrder.erase(begin(loadOrder)); - EXPECT_EQ(loadOrder, getLoadOrder()); - } - } + EXPECT_EQ(loadOrder, getLoadOrder()); +} +} } #endif diff --git a/src/tests/backend/helpers/git_helper_test.h b/src/tests/backend/helpers/git_helper_test.h index aced1b84..e38f629c 100644 --- a/src/tests/backend/helpers/git_helper_test.h +++ b/src/tests/backend/helpers/git_helper_test.h @@ -22,120 +22,120 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_HELPERS_GIT_HELPER -#define LOOT_TEST_BACKEND_HELPERS_GIT_HELPER +#ifndef LOOT_TESTS_BACKEND_HELPERS_GIT_HELPER_TEST +#define LOOT_TESTS_BACKEND_HELPERS_GIT_HELPER_TEST -#include "backend/error.h" #include "backend/helpers/git_helper.h" #include +#include "backend/error.h" + namespace loot { - namespace test { - class GitHelperTest : public ::testing::Test { - protected: - GitHelperTest() : - parentRepoRoot(getRepoRoot()) {} +namespace test { +class GitHelperTest : public ::testing::Test { +protected: + GitHelperTest() : + parentRepoRoot(GetRepoRoot()) {} - inline virtual void SetUp() { - ASSERT_TRUE(boost::filesystem::exists(parentRepoRoot / "README.md")); + inline void SetUp() { + ASSERT_TRUE(boost::filesystem::exists(parentRepoRoot / "README.md")); - // Create a backup of CONTRIBUTING.md. - ASSERT_TRUE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md")); - ASSERT_FALSE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md.copy")); - ASSERT_NO_THROW(boost::filesystem::copy(parentRepoRoot / "CONTRIBUTING.md", parentRepoRoot / "CONTRIBUTING.md.copy")); - ASSERT_TRUE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md.copy")); + // Create a backup of CONTRIBUTING.md. + ASSERT_TRUE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md")); + ASSERT_FALSE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md.copy")); + ASSERT_NO_THROW(boost::filesystem::copy(parentRepoRoot / "CONTRIBUTING.md", parentRepoRoot / "CONTRIBUTING.md.copy")); + ASSERT_TRUE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md.copy")); - // Edit CONTRIBUTING.md - boost::filesystem::ofstream out(parentRepoRoot / "CONTRIBUTING.md"); - out.close(); - } + // Edit CONTRIBUTING.md + boost::filesystem::ofstream out(parentRepoRoot / "CONTRIBUTING.md"); + out.close(); + } - inline virtual void TearDown() { - // Restore original CONTRIBUTING.md - ASSERT_NO_THROW(boost::filesystem::remove(parentRepoRoot / "CONTRIBUTING.md")); - ASSERT_NO_THROW(boost::filesystem::rename(parentRepoRoot / "CONTRIBUTING.md.copy", parentRepoRoot / "CONTRIBUTING.md")); - ASSERT_TRUE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md")); - ASSERT_FALSE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md.copy")); - } + inline void TearDown() { + // Restore original CONTRIBUTING.md + ASSERT_NO_THROW(boost::filesystem::remove(parentRepoRoot / "CONTRIBUTING.md")); + ASSERT_NO_THROW(boost::filesystem::rename(parentRepoRoot / "CONTRIBUTING.md.copy", parentRepoRoot / "CONTRIBUTING.md")); + ASSERT_TRUE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md")); + ASSERT_FALSE(boost::filesystem::exists(parentRepoRoot / "CONTRIBUTING.md.copy")); + } - GitHelper git; + GitHelper git_; - const boost::filesystem::path parentRepoRoot; + const boost::filesystem::path parentRepoRoot; - private: - inline static boost::filesystem::path getRepoRoot() { - boost::filesystem::path dir = boost::filesystem::current_path(); - while (!boost::filesystem::exists(dir / ".git")) { - dir = dir.parent_path(); - } - - return dir; - } - }; - - TEST_F(GitHelperTest, repoShouldInitialiseAsANullPointer) { - EXPECT_EQ(nullptr, git.repo); - } - - TEST_F(GitHelperTest, destructorShouldCallLibgit2CleanupFunction) { - ASSERT_EQ(2, git_libgit2_init()); - - GitHelper * gitPointer = new GitHelper(); - ASSERT_EQ(4, git_libgit2_init()); - - delete gitPointer; - EXPECT_EQ(2, git_libgit2_shutdown()); - } - - TEST_F(GitHelperTest, callShouldNotThrowIfPassedAZeroValue) { - EXPECT_NO_THROW(git.Call(0)); - } - - TEST_F(GitHelperTest, callShouldThrowIfPassedANonZeroValue) { - EXPECT_THROW(git.Call(1), Error); - EXPECT_THROW(git.Call(-1), Error); - } - - TEST_F(GitHelperTest, setErrorMessageShouldSetTheMessageForThrownExceptions) { - const char * errorMessage = "test message"; - git.SetErrorMessage(errorMessage); - - try { - git.Call(1); - ADD_FAILURE() << "An exception should have been thrown."; - } - catch (Error& e) { - EXPECT_NE(nullptr, strstr(e.what(), errorMessage)); - } - } - - TEST_F(GitHelperTest, isRepositoryShouldReturnTrueForARepositoryRoot) { - EXPECT_TRUE(GitHelper::IsRepository(parentRepoRoot)); - } - - TEST_F(GitHelperTest, isRepositoryShouldReturnFalseForRepositorySubdirectory) { - EXPECT_FALSE(GitHelper::IsRepository(boost::filesystem::current_path())); - } - - TEST_F(GitHelperTest, isFileDifferentShouldThrowIfGivenANonRepositoryPath) { - EXPECT_THROW(GitHelper::IsFileDifferent(boost::filesystem::current_path(), "README.md"), Error); - } - - TEST_F(GitHelperTest, isFileDifferentShouldReturnFalseForAnUntrackedFile) { - // New files not in the index are not tracked by Git, so aren't considered - // different. - EXPECT_FALSE(GitHelper::IsFileDifferent(parentRepoRoot, "CONTRIBUTING.md.copy")); - } - - TEST_F(GitHelperTest, isFileDifferentShouldReturnFalseForAnUnchangedTrackedFile) { - EXPECT_FALSE(GitHelper::IsFileDifferent(parentRepoRoot, "README.md")); - } - - TEST_F(GitHelperTest, isFileDifferentShouldReturnTrueForAChangedTrackedFile) { - EXPECT_TRUE(GitHelper::IsFileDifferent(parentRepoRoot, "CONTRIBUTING.md")); - } +private: + inline static boost::filesystem::path GetRepoRoot() { + boost::filesystem::path dir = boost::filesystem::current_path(); + while (!boost::filesystem::exists(dir / ".git")) { + dir = dir.parent_path(); } + + return dir; + } +}; + +TEST_F(GitHelperTest, repoShouldInitialiseAsANullPointer) { + EXPECT_EQ(nullptr, git_.GetData().repo); +} + +TEST_F(GitHelperTest, destructorShouldCallLibgit2CleanupFunction) { + ASSERT_EQ(2, git_libgit2_init()); + + GitHelper * gitPointer = new GitHelper(); + ASSERT_EQ(4, git_libgit2_init()); + + delete gitPointer; + EXPECT_EQ(2, git_libgit2_shutdown()); +} + +TEST_F(GitHelperTest, callShouldNotThrowIfPassedAZeroValue) { + EXPECT_NO_THROW(git_.Call(0)); +} + +TEST_F(GitHelperTest, callShouldThrowIfPassedANonZeroValue) { + EXPECT_THROW(git_.Call(1), Error); + EXPECT_THROW(git_.Call(-1), Error); +} + +TEST_F(GitHelperTest, setErrorMessageShouldSetTheMessageForThrownExceptions) { + const char * errorMessage = "test message"; + git_.SetErrorMessage(errorMessage); + + try { + git_.Call(1); + ADD_FAILURE() << "An exception should have been thrown."; + } catch (Error& e) { + EXPECT_NE(nullptr, strstr(e.what(), errorMessage)); + } +} + +TEST_F(GitHelperTest, isRepositoryShouldReturnTrueForARepositoryRoot) { + EXPECT_TRUE(GitHelper::IsRepository(parentRepoRoot)); +} + +TEST_F(GitHelperTest, isRepositoryShouldReturnFalseForRepositorySubdirectory) { + EXPECT_FALSE(GitHelper::IsRepository(boost::filesystem::current_path())); +} + +TEST_F(GitHelperTest, isFileDifferentShouldThrowIfGivenANonRepositoryPath) { + EXPECT_THROW(GitHelper::IsFileDifferent(boost::filesystem::current_path(), "README.md"), Error); +} + +TEST_F(GitHelperTest, isFileDifferentShouldReturnFalseForAnUntrackedFile) { + // New files not in the index are not tracked by Git, so aren't considered + // different. + EXPECT_FALSE(GitHelper::IsFileDifferent(parentRepoRoot, "CONTRIBUTING.md.copy")); +} + +TEST_F(GitHelperTest, isFileDifferentShouldReturnFalseForAnUnchangedTrackedFile) { + EXPECT_FALSE(GitHelper::IsFileDifferent(parentRepoRoot, "README.md")); +} + +TEST_F(GitHelperTest, isFileDifferentShouldReturnTrueForAChangedTrackedFile) { + EXPECT_TRUE(GitHelper::IsFileDifferent(parentRepoRoot, "CONTRIBUTING.md")); +} +} } #endif diff --git a/src/tests/backend/helpers/helpers_test.h b/src/tests/backend/helpers/helpers_test.h index 4b4814ce..c0fbd4ad 100644 --- a/src/tests/backend/helpers/helpers_test.h +++ b/src/tests/backend/helpers/helpers_test.h @@ -22,43 +22,43 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_HELPERS -#define LOOT_TEST_BACKEND_HELPERS +#ifndef LOOT_TESTS_BACKEND_HELPERS_HELPERS_TEST +#define LOOT_TESTS_BACKEND_HELPERS_HELPERS_TEST #include "backend/helpers/helpers.h" -#include "backend/error.h" +#include "backend/error.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class GetCrc32Test : public BaseGameTest {}; +namespace test { +class GetCrc32Test : public BaseGameTest {}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - // Just test with one game because if it works for one it will work for them - // all. - INSTANTIATE_TEST_CASE_P(, - GetCrc32Test, - ::testing::Values( - GameType::tes5)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +// Just test with one game because if it works for one it will work for them +// all. +INSTANTIATE_TEST_CASE_P(, + GetCrc32Test, + ::testing::Values( + GameType::tes5)); - TEST_P(GetCrc32Test, gettingTheCrcOfAMissingFileShouldThrow) { - EXPECT_THROW(GetCrc32(dataPath / missingEsp), Error); - } +TEST_P(GetCrc32Test, gettingTheCrcOfAMissingFileShouldThrow) { + EXPECT_THROW(GetCrc32(dataPath / missingEsp), Error); +} - TEST_P(GetCrc32Test, gettingTheCrcOfAFileShouldReturnTheCorrectValue) { - EXPECT_EQ(blankEsmCrc, GetCrc32(dataPath / blankEsm)); - } +TEST_P(GetCrc32Test, gettingTheCrcOfAFileShouldReturnTheCorrectValue) { + EXPECT_EQ(blankEsmCrc, GetCrc32(dataPath / blankEsm)); +} - TEST(IntToHexString, intToHexStringShouldOutputANonZeroPositiveIntegerCorrectly) { - EXPECT_EQ("14", IntToHexString(20)); - } +TEST(IntToHexString, intToHexStringShouldOutputANonZeroPositiveIntegerCorrectly) { + EXPECT_EQ("14", IntToHexString(20)); +} - TEST(IntToHexString, intToHexStringShouldOutputZeroCorrectly) { - EXPECT_EQ("0", IntToHexString(0)); - } - } +TEST(IntToHexString, intToHexStringShouldOutputZeroCorrectly) { + EXPECT_EQ("0", IntToHexString(0)); +} +} } #endif diff --git a/src/tests/backend/helpers/language_test.h b/src/tests/backend/helpers/language_test.h index 57a069f0..629a9aee 100644 --- a/src/tests/backend/helpers/language_test.h +++ b/src/tests/backend/helpers/language_test.h @@ -22,71 +22,71 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_HELPERS_LANGUAGE -#define LOOT_TEST_BACKEND_HELPERS_LANGUAGE +#ifndef LOOT_TESTS_BACKEND_HELPERS_LANGUAGE_TEST +#define LOOT_TESTS_BACKEND_HELPERS_LANGUAGE_TEST #include "backend/helpers/language.h" #include namespace loot { - namespace test { - TEST(Language, codeConstructorShouldSetTheCorrectData) { - Language lang(Language::Code::english); - EXPECT_EQ(Language::Code::english, lang.GetCode()); - EXPECT_EQ("English", lang.GetName()); - EXPECT_EQ("en", lang.GetLocale()); +namespace test { +TEST(Language, codeConstructorShouldSetTheCorrectData) { + Language lang(Language::Code::english); + EXPECT_EQ(Language::Code::english, lang.GetCode()); + EXPECT_EQ("English", lang.GetName()); + EXPECT_EQ("en", lang.GetLocale()); - lang = Language(Language::Code::polish); - EXPECT_EQ(Language::Code::polish, lang.GetCode()); - EXPECT_EQ("Polski", lang.GetName()); - EXPECT_EQ("pl", lang.GetLocale()); - } + lang = Language(Language::Code::polish); + EXPECT_EQ(Language::Code::polish, lang.GetCode()); + EXPECT_EQ("Polski", lang.GetName()); + EXPECT_EQ("pl", lang.GetLocale()); +} - TEST(Language, localeConstructorShouldSetTheCorrectData) { - Language lang("en"); - EXPECT_EQ(Language::Code::english, lang.GetCode()); - EXPECT_EQ("English", lang.GetName()); - EXPECT_EQ("en", lang.GetLocale()); +TEST(Language, localeConstructorShouldSetTheCorrectData) { + Language lang("en"); + EXPECT_EQ(Language::Code::english, lang.GetCode()); + EXPECT_EQ("English", lang.GetName()); + EXPECT_EQ("en", lang.GetLocale()); - lang = Language("de"); - EXPECT_EQ(Language::Code::german, lang.GetCode()); - EXPECT_EQ("Deutsch", lang.GetName()); - EXPECT_EQ("de", lang.GetLocale()); - } + lang = Language("de"); + EXPECT_EQ(Language::Code::german, lang.GetCode()); + EXPECT_EQ("Deutsch", lang.GetName()); + EXPECT_EQ("de", lang.GetLocale()); +} - TEST(Language, codeConstructorShouldTreatAnInvalidCodeAsEnglish) { - Language lang(Language::Code(1000)); - EXPECT_EQ(Language::Code::english, lang.GetCode()); - EXPECT_EQ("English", lang.GetName()); - EXPECT_EQ("en", lang.GetLocale()); - } +TEST(Language, codeConstructorShouldTreatAnInvalidCodeAsEnglish) { + Language lang(Language::Code(1000)); + EXPECT_EQ(Language::Code::english, lang.GetCode()); + EXPECT_EQ("English", lang.GetName()); + EXPECT_EQ("en", lang.GetLocale()); +} - TEST(Language, localeConstructorShouldTreatAnInvalidLocaleAsEnglish) { - Language lang("foo"); - EXPECT_EQ(Language::Code::english, lang.GetCode()); - EXPECT_EQ("English", lang.GetName()); - EXPECT_EQ("en", lang.GetLocale()); - } +TEST(Language, localeConstructorShouldTreatAnInvalidLocaleAsEnglish) { + Language lang("foo"); + EXPECT_EQ(Language::Code::english, lang.GetCode()); + EXPECT_EQ("English", lang.GetName()); + EXPECT_EQ("en", lang.GetLocale()); +} - TEST(Language, codesShouldContainAllExpectedLanguageCodes) { - std::vector codes = { - Language::Code::english, - Language::Code::spanish, - Language::Code::russian, - Language::Code::french, - Language::Code::chinese, - Language::Code::polish, - Language::Code::brazilian_portuguese, - Language::Code::finnish, - Language::Code::german, - Language::Code::danish, - Language::Code::korean - }; +TEST(Language, codesShouldContainAllExpectedLanguageCodes) { + std::vector codes = { + Language::Code::english, + Language::Code::spanish, + Language::Code::russian, + Language::Code::french, + Language::Code::chinese, + Language::Code::polish, + Language::Code::brazilian_portuguese, + Language::Code::finnish, + Language::Code::german, + Language::Code::danish, + Language::Code::korean + }; - EXPECT_EQ(codes, Language::Codes); - } - } + EXPECT_EQ(codes, Language::codes); +} +} } #endif diff --git a/src/tests/backend/helpers/version_test.h b/src/tests/backend/helpers/version_test.h index 989ab257..b13f80df 100644 --- a/src/tests/backend/helpers/version_test.h +++ b/src/tests/backend/helpers/version_test.h @@ -22,8 +22,8 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_HELPERS_VERSION -#define LOOT_TEST_BACKEND_HELPERS_VERSION +#ifndef LOOT_TESTS_BACKEND_HELPERS_VERSION_TEST +#define LOOT_TESTS_BACKEND_HELPERS_VERSION_TEST #include "backend/app/loot_version.h" #include "backend/helpers/version.h" @@ -31,232 +31,232 @@ along with LOOT. If not, see #include namespace loot { - namespace test { +namespace test { #ifdef _WIN32 - TEST(Version, shouldExtractVersionFromApiDll) { - // Use the API DLL built. - Version version(boost::filesystem::path("loot_api.dll")); - std::string expected(LootVersion::string() + ".0"); - EXPECT_EQ(expected, version.AsString()); - } +TEST(Version, shouldExtractVersionFromApiDll) { + // Use the API DLL built. + Version version(boost::filesystem::path("loot_api.dll")); + std::string expected(LootVersion::string() + ".0"); + EXPECT_EQ(expected, version.AsString()); +} #endif - TEST(Version, defaultConstructorShouldSetEmptyVersionString) { - EXPECT_EQ("", Version().AsString()); - } +TEST(Version, defaultConstructorShouldSetEmptyVersionString) { + EXPECT_EQ("", Version().AsString()); +} - TEST(Version, shouldExtractAVersionContainingASingleDigit) { - Version version(std::string("5")); - EXPECT_EQ("5", version.AsString()); - } +TEST(Version, shouldExtractAVersionContainingASingleDigit) { + Version version(std::string("5")); + EXPECT_EQ("5", version.AsString()); +} - TEST(Version, shouldExtractAVersionContainingMultipleDigits) { - Version version(std::string("10")); - EXPECT_EQ("10", version.AsString()); - } +TEST(Version, shouldExtractAVersionContainingMultipleDigits) { + Version version(std::string("10")); + EXPECT_EQ("10", version.AsString()); +} - TEST(Version, shouldExtractAVersionContainingMultipleNumbers) { - Version version(std::string("10.11.12.13")); - EXPECT_EQ("10.11.12.13", version.AsString()); - } +TEST(Version, shouldExtractAVersionContainingMultipleNumbers) { + Version version(std::string("10.11.12.13")); + EXPECT_EQ("10.11.12.13", version.AsString()); +} - TEST(Version, shouldExtractASemanticVersion) { - Version version(std::string("1.0.0-x.7.z.92+exp.sha.5114f85")); - EXPECT_EQ("1.0.0-x.7.z.92", version.AsString()); - } +TEST(Version, shouldExtractASemanticVersion) { + Version version(std::string("1.0.0-x.7.z.92+exp.sha.5114f85")); + EXPECT_EQ("1.0.0-x.7.z.92", version.AsString()); +} - TEST(Version, shouldExtractAPseudosemExtendedVersionStoppingAtTheFirstSpaceSeparator) { - Version version(std::string("01.0.0_alpha:1-2 3")); - EXPECT_EQ("01.0.0_alpha:1-2", version.AsString()); - } +TEST(Version, shouldExtractAPseudosemExtendedVersionStoppingAtTheFirstSpaceSeparator) { + Version version(std::string("01.0.0_alpha:1-2 3")); + EXPECT_EQ("01.0.0_alpha:1-2", version.AsString()); +} - TEST(Version, shouldExtractAVersionSubstring) { - Version version(std::string("v5.0")); - EXPECT_EQ("5.0", version.AsString()); - } +TEST(Version, shouldExtractAVersionSubstring) { + Version version(std::string("v5.0")); + EXPECT_EQ("5.0", version.AsString()); +} - TEST(Version, shouldBeEmptyIfInputStringContainedNoVersion) { - Version version(std::string("The quick brown fox jumped over the lazy dog.")); - EXPECT_EQ("", version.AsString()); - } +TEST(Version, shouldBeEmptyIfInputStringContainedNoVersion) { + Version version(std::string("The quick brown fox jumped over the lazy dog.")); + EXPECT_EQ("", version.AsString()); +} - TEST(Version, shouldExtractTimestampWithForwardslashDateSeparators) { - // Found in a Bashed Patch. Though the timestamp isn't useful to - // LOOT, it is semantically a version, and extracting it is far - // easier than trying to skip it and the number of records changed. - Version version(std::string("Updated: 10/09/2016 13:15:18\r\n\r\nRecords Changed: 43")); - EXPECT_EQ("10/09/2016 13:15:18", version.AsString()); - } +TEST(Version, shouldExtractTimestampWithForwardslashDateSeparators) { + // Found in a Bashed Patch. Though the timestamp isn't useful to + // LOOT, it is semantically a version, and extracting it is far + // easier than trying to skip it and the number of records changed. + Version version(std::string("Updated: 10/09/2016 13:15:18\r\n\r\nRecords Changed: 43")); + EXPECT_EQ("10/09/2016 13:15:18", version.AsString()); +} - TEST(Version, shouldNotExtractTrailingPeriods) { - // Found in . - Version version(std::string("Version 0.2.")); - EXPECT_EQ("0.2", version.AsString()); - } +TEST(Version, shouldNotExtractTrailingPeriods) { + // Found in . + Version version(std::string("Version 0.2.")); + EXPECT_EQ("0.2", version.AsString()); +} - TEST(Version, shouldExtractVersionAfterTextWhenPrecededByVersionColonString) { - // Found in . - std::string testText("Legendary Edition\r\n\r\nVersion: 3.0.0"); - EXPECT_EQ("3.0.0", Version(testText).AsString()); - } +TEST(Version, shouldExtractVersionAfterTextWhenPrecededByVersionColonString) { + // Found in . + std::string testText("Legendary Edition\r\n\r\nVersion: 3.0.0"); + EXPECT_EQ("3.0.0", Version(testText).AsString()); +} - TEST(Version, shouldIgnoreNumbersContainingCommas) { - // Found in . - std::string testText("fixing over 2,300 bugs so far! Version: 3.5.3"); - EXPECT_EQ("3.5.3", Version(testText).AsString()); - } +TEST(Version, shouldIgnoreNumbersContainingCommas) { + // Found in . + std::string testText("fixing over 2,300 bugs so far! Version: 3.5.3"); + EXPECT_EQ("3.5.3", Version(testText).AsString()); +} - TEST(Version, shouldExtractVersionBeforeText) { - // Found in . - std::string testText("Version: 2.1 The Unofficial Fallout 3 Patch"); - EXPECT_EQ("2.1", Version(testText).AsString()); - } +TEST(Version, shouldExtractVersionBeforeText) { + // Found in . + std::string testText("Version: 2.1 The Unofficial Fallout 3 Patch"); + EXPECT_EQ("2.1", Version(testText).AsString()); +} - TEST(Version, shouldExtractVersionWithPrecedingV) { - // Found in . - std::string testText("V2.11\r\n\r\n{{BASH:Invent}}"); - EXPECT_EQ("2.11", Version(testText).AsString()); - } +TEST(Version, shouldExtractVersionWithPrecedingV) { + // Found in . + std::string testText("V2.11\r\n\r\n{{BASH:Invent}}"); + EXPECT_EQ("2.11", Version(testText).AsString()); +} - TEST(Version, shouldExtractVersionWithPrecedingColonPeriodWhitespace) { - // Found in . - std::string testText("Version:. 1.09"); - EXPECT_EQ("1.09", Version(testText).AsString()); - } +TEST(Version, shouldExtractVersionWithPrecedingColonPeriodWhitespace) { + // Found in . + std::string testText("Version:. 1.09"); + EXPECT_EQ("1.09", Version(testText).AsString()); +} - TEST(Version, shouldExtractVersionWithLettersImmediatelyAfterNumbers) { - // Found in . - std::string testText("comprehensive bugfixing mod for The Elder Scrolls V: Skyrim\r\n\r\nVersion: 2.1.3b\r\n\r\n"); - EXPECT_EQ("2.1.3b", Version(testText).AsString()); - } +TEST(Version, shouldExtractVersionWithLettersImmediatelyAfterNumbers) { + // Found in . + std::string testText("comprehensive bugfixing mod for The Elder Scrolls V: Skyrim\r\n\r\nVersion: 2.1.3b\r\n\r\n"); + EXPECT_EQ("2.1.3b", Version(testText).AsString()); +} - TEST(Version, shouldExtractVersionWithPeriodAndNoPrecedingIdentifier) { - // Found in . - std::string testText("SkyUI 5.1"); - EXPECT_EQ("5.1", Version(testText).AsString()); - } +TEST(Version, shouldExtractVersionWithPeriodAndNoPrecedingIdentifier) { + // Found in . + std::string testText("SkyUI 5.1"); + EXPECT_EQ("5.1", Version(testText).AsString()); +} - TEST(Version, shouldNotExtractSingleDigitInSentence) { - // Found in . - std::string testText("Adds 8 variants of Triss Merigold's outfit from \"The Witcher 2\""); - EXPECT_EQ("", Version(testText).AsString()); - } +TEST(Version, shouldNotExtractSingleDigitInSentence) { + // Found in . + std::string testText("Adds 8 variants of Triss Merigold's outfit from \"The Witcher 2\""); + EXPECT_EQ("", Version(testText).AsString()); +} - TEST(Version, shouldPreferVersionPrefixedNumbersOverVersionsInSentence) { - // Found in - std::string testText("Requires Skyrim patch 1.9.32.0.8 or greater.\n" - "Requires Unofficial Skyrim Legendary Edition Patch 3.0.0 or greater.\n" - "Version 2.0.0"); - EXPECT_EQ("2.0.0", Version(testText).AsString()); - } +TEST(Version, shouldPreferVersionPrefixedNumbersOverVersionsInSentence) { + // Found in + std::string testText("Requires Skyrim patch 1.9.32.0.8 or greater.\n" + "Requires Unofficial Skyrim Legendary Edition Patch 3.0.0 or greater.\n" + "Version 2.0.0"); + EXPECT_EQ("2.0.0", Version(testText).AsString()); +} - TEST(Version, shouldExtractSingleDigitVersionPrecededByV) { - // Found in - std::string testText("Immersive Armors v8 Main Plugin"); - EXPECT_EQ("8", Version(testText).AsString()); - } +TEST(Version, shouldExtractSingleDigitVersionPrecededByV) { + // Found in + std::string testText("Immersive Armors v8 Main Plugin"); + EXPECT_EQ("8", Version(testText).AsString()); +} - TEST(Version, shouldPreferVersionPrefixedNumbersOverVPrefixedNumber) { - // Found in - std::string testText("Compatibility patch for AOS v2.5 and True Storms v1.5 (or later),\nPatch Version: 1.0"); - EXPECT_EQ("1.0", Version(testText).AsString()); - } +TEST(Version, shouldPreferVersionPrefixedNumbersOverVPrefixedNumber) { + // Found in + std::string testText("Compatibility patch for AOS v2.5 and True Storms v1.5 (or later),\nPatch Version: 1.0"); + EXPECT_EQ("1.0", Version(testText).AsString()); +} - TEST(Version, GreaterThan) { - Version version1, version2; - EXPECT_FALSE(version1 > version2); - EXPECT_FALSE(version2 > version1); +TEST(Version, GreaterThan) { + Version version1, version2; + EXPECT_FALSE(version1 > version2); + EXPECT_FALSE(version2 > version1); - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 > version2); - EXPECT_FALSE(version2 > version1); + version1 = Version(std::string("5")); + version2 = Version(std::string("5")); + EXPECT_FALSE(version1 > version2); + EXPECT_FALSE(version2 > version1); - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 > version2); - EXPECT_TRUE(version2 > version1); - } + version1 = Version(std::string("4")); + version2 = Version(std::string("5")); + EXPECT_FALSE(version1 > version2); + EXPECT_TRUE(version2 > version1); +} - TEST(Version, LessThan) { - Version version1, version2; - EXPECT_FALSE(version1 < version2); - EXPECT_FALSE(version2 < version1); +TEST(Version, LessThan) { + Version version1, version2; + EXPECT_FALSE(version1 < version2); + EXPECT_FALSE(version2 < version1); - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 < version2); - EXPECT_FALSE(version2 < version1); + version1 = Version(std::string("5")); + version2 = Version(std::string("5")); + EXPECT_FALSE(version1 < version2); + EXPECT_FALSE(version2 < version1); - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 < version2); - EXPECT_FALSE(version2 < version1); - } + version1 = Version(std::string("4")); + version2 = Version(std::string("5")); + EXPECT_TRUE(version1 < version2); + EXPECT_FALSE(version2 < version1); +} - TEST(Version, GreaterThanEqual) { - Version version1, version2; - EXPECT_TRUE(version1 >= version2); - EXPECT_TRUE(version2 >= version1); +TEST(Version, GreaterThanEqual) { + Version version1, version2; + EXPECT_TRUE(version1 >= version2); + EXPECT_TRUE(version2 >= version1); - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 >= version2); - EXPECT_TRUE(version2 >= version1); + version1 = Version(std::string("5")); + version2 = Version(std::string("5")); + EXPECT_TRUE(version1 >= version2); + EXPECT_TRUE(version2 >= version1); - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 >= version2); - EXPECT_TRUE(version2 >= version1); - } + version1 = Version(std::string("4")); + version2 = Version(std::string("5")); + EXPECT_FALSE(version1 >= version2); + EXPECT_TRUE(version2 >= version1); +} - TEST(Version, LessThanEqual) { - Version version1, version2; - EXPECT_TRUE(version1 <= version2); - EXPECT_TRUE(version2 <= version1); +TEST(Version, LessThanEqual) { + Version version1, version2; + EXPECT_TRUE(version1 <= version2); + EXPECT_TRUE(version2 <= version1); - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 <= version2); - EXPECT_TRUE(version2 <= version1); + version1 = Version(std::string("5")); + version2 = Version(std::string("5")); + EXPECT_TRUE(version1 <= version2); + EXPECT_TRUE(version2 <= version1); - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 <= version2); - EXPECT_FALSE(version2 <= version1); - } + version1 = Version(std::string("4")); + version2 = Version(std::string("5")); + EXPECT_TRUE(version1 <= version2); + EXPECT_FALSE(version2 <= version1); +} - TEST(Version, Equal) { - Version version1, version2; - EXPECT_TRUE(version1 == version2); - EXPECT_TRUE(version2 == version1); +TEST(Version, Equal) { + Version version1, version2; + EXPECT_TRUE(version1 == version2); + EXPECT_TRUE(version2 == version1); - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 == version2); - EXPECT_TRUE(version2 == version1); + version1 = Version(std::string("5")); + version2 = Version(std::string("5")); + EXPECT_TRUE(version1 == version2); + EXPECT_TRUE(version2 == version1); - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 == version2); - EXPECT_FALSE(version2 == version1); - } + version1 = Version(std::string("4")); + version2 = Version(std::string("5")); + EXPECT_FALSE(version1 == version2); + EXPECT_FALSE(version2 == version1); +} - TEST(Version, NotEqual) { - Version version1, version2; - EXPECT_FALSE(version1 != version2); - EXPECT_FALSE(version2 != version1); +TEST(Version, NotEqual) { + Version version1, version2; + EXPECT_FALSE(version1 != version2); + EXPECT_FALSE(version2 != version1); - version1 = Version(std::string("5")); - version2 = Version(std::string("5")); - EXPECT_FALSE(version1 != version2); - EXPECT_FALSE(version2 != version1); + version1 = Version(std::string("5")); + version2 = Version(std::string("5")); + EXPECT_FALSE(version1 != version2); + EXPECT_FALSE(version2 != version1); - version1 = Version(std::string("4")); - version2 = Version(std::string("5")); - EXPECT_TRUE(version1 != version2); - EXPECT_TRUE(version2 != version1); - } - } + version1 = Version(std::string("4")); + version2 = Version(std::string("5")); + EXPECT_TRUE(version1 != version2); + EXPECT_TRUE(version2 != version1); +} +} } #endif diff --git a/src/tests/backend/helpers/yaml_set_helpers_test.h b/src/tests/backend/helpers/yaml_set_helpers_test.h index 86361865..b4a87d54 100644 --- a/src/tests/backend/helpers/yaml_set_helpers_test.h +++ b/src/tests/backend/helpers/yaml_set_helpers_test.h @@ -22,117 +22,117 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_HELPERS_YAML_SET_HELPERS -#define LOOT_TEST_BACKEND_HELPERS_YAML_SET_HELPERS +#ifndef LOOT_TESTS_BACKEND_HELPERS_YAML_SET_HELPERS_TEST +#define LOOT_TESTS_BACKEND_HELPERS_YAML_SET_HELPERS_TEST #include "backend/helpers/yaml_set_helpers.h" #include namespace loot { - namespace test { - TEST(set, encodingAsYamlShouldStoreAllValuesInSetOrder) { - std::set stringSet({"a", "b", "c"}); - YAML::Node node; - node = stringSet; +namespace test { +TEST(set, encodingAsYamlShouldStoreAllValuesInSetOrder) { + std::set stringSet({"a", "b", "c"}); + YAML::Node node; + node = stringSet; - EXPECT_TRUE(node.IsSequence()); - ASSERT_EQ(3, node.size()); - EXPECT_EQ("a", node[0].as()); - EXPECT_EQ("b", node[1].as()); - EXPECT_EQ("c", node[2].as()); - } + EXPECT_TRUE(node.IsSequence()); + ASSERT_EQ(3, node.size()); + EXPECT_EQ("a", node[0].as()); + EXPECT_EQ("b", node[1].as()); + EXPECT_EQ("c", node[2].as()); +} - TEST(set, decodingFromAYamlListShouldStoreValuesCorrectly) { - YAML::Node node = YAML::Load("[a, b, c]"); - std::set stringSet = node.as>(); - std::set expectedStringSet({"a", "b", "c"}); +TEST(set, decodingFromAYamlListShouldStoreValuesCorrectly) { + YAML::Node node = YAML::Load("[a, b, c]"); + std::set stringSet = node.as>(); + std::set expectedStringSet({"a", "b", "c"}); - EXPECT_EQ(expectedStringSet, stringSet); - } + EXPECT_EQ(expectedStringSet, stringSet); +} - TEST(set, decodingFromAYamlListThatContainsDuplicateElementsShouldThrow) { - YAML::Node node = YAML::Load("[a, b, c, c]"); - EXPECT_ANY_THROW(node.as>()); - } +TEST(set, decodingFromAYamlListThatContainsDuplicateElementsShouldThrow) { + YAML::Node node = YAML::Load("[a, b, c, c]"); + EXPECT_ANY_THROW(node.as>()); +} - TEST(set, emittingAsYamlShouldOutputAYamlListContainingAllValues) { - std::set stringSet({"a", "b", "c"}); - YAML::Emitter e1; - e1 << stringSet; +TEST(set, emittingAsYamlShouldOutputAYamlListContainingAllValues) { + std::set stringSet({"a", "b", "c"}); + YAML::Emitter e1; + e1 << stringSet; - YAML::Node node = YAML::Load(e1.c_str()); - EXPECT_TRUE(node.IsSequence()); - ASSERT_EQ(3, node.size()); - EXPECT_EQ("a", node[0].as()); - EXPECT_EQ("b", node[1].as()); - EXPECT_EQ("c", node[2].as()); - } + YAML::Node node = YAML::Load(e1.c_str()); + EXPECT_TRUE(node.IsSequence()); + ASSERT_EQ(3, node.size()); + EXPECT_EQ("a", node[0].as()); + EXPECT_EQ("b", node[1].as()); + EXPECT_EQ("c", node[2].as()); +} - class unordered_set : public ::testing::Test { - protected: - static bool nodeContains(const YAML::Node& node, const std::string& value) { - for (const auto& element : node) { - if (element.as() == value) - return true; - } - return false; - } - - static bool isSequenceOf(const std::string& sequence, const std::vector& values) { - std::set sortedValues(std::begin(values), std::end(values)); - - std::set found; - size_t i = 2; - while (i < sequence.length()) { - size_t separatorPos = sequence.find("\n- ", i); - found.insert(sequence.substr(i, separatorPos)); - } - - return sortedValues == found; - } - }; - - TEST_F(unordered_set, encodingAsYamlShouldStoreAllValuesInUndefinedOrder) { - std::unordered_set stringSet({"a", "b", "c"}); - YAML::Node node; - node = stringSet; - - EXPECT_TRUE(node.IsSequence()); - ASSERT_EQ(3, node.size()); - EXPECT_PRED2(&nodeContains, node, "a"); - EXPECT_PRED2(&nodeContains, node, "b"); - EXPECT_PRED2(&nodeContains, node, "c"); - } - - TEST_F(unordered_set, decodingFromAYamlListShouldStoreValuesCorrectly) { - YAML::Node node = YAML::Load("[a, b, c]"); - std::unordered_set stringSet = node.as>(); - - EXPECT_EQ(1, stringSet.count("a")); - EXPECT_EQ(1, stringSet.count("b")); - EXPECT_EQ(1, stringSet.count("c")); - } - - TEST_F(unordered_set, decodingFromAYamlListThatContainsDuplicateElementsShouldThrow) { - YAML::Node node = YAML::Load("[a, b, c, c]"); - - EXPECT_ANY_THROW(node.as>()); - } - - TEST_F(unordered_set, emittingAsYamlShouldOutputAYamlListContainingAllValues) { - std::unordered_set stringSet({"a", "b", "c"}); - YAML::Emitter e1; - e1 << stringSet; - - YAML::Node node = YAML::Load(e1.c_str()); - EXPECT_TRUE(node.IsSequence()); - ASSERT_EQ(3, node.size()); - EXPECT_PRED2(&nodeContains, node, "a"); - EXPECT_PRED2(&nodeContains, node, "b"); - EXPECT_PRED2(&nodeContains, node, "c"); - } +class unordered_set : public ::testing::Test { +protected: + static bool nodeContains(const YAML::Node& node, const std::string& value) { + for (const auto& element : node) { + if (element.as() == value) + return true; } + return false; + } + + static bool isSequenceOf(const std::string& sequence, const std::vector& values) { + std::set sortedValues(std::begin(values), std::end(values)); + + std::set found; + size_t i = 2; + while (i < sequence.length()) { + size_t separatorPos = sequence.find("\n- ", i); + found.insert(sequence.substr(i, separatorPos)); + } + + return sortedValues == found; + } +}; + +TEST_F(unordered_set, encodingAsYamlShouldStoreAllValuesInUndefinedOrder) { + std::unordered_set stringSet({"a", "b", "c"}); + YAML::Node node; + node = stringSet; + + EXPECT_TRUE(node.IsSequence()); + ASSERT_EQ(3, node.size()); + EXPECT_PRED2(&nodeContains, node, "a"); + EXPECT_PRED2(&nodeContains, node, "b"); + EXPECT_PRED2(&nodeContains, node, "c"); +} + +TEST_F(unordered_set, decodingFromAYamlListShouldStoreValuesCorrectly) { + YAML::Node node = YAML::Load("[a, b, c]"); + std::unordered_set stringSet = node.as>(); + + EXPECT_EQ(1, stringSet.count("a")); + EXPECT_EQ(1, stringSet.count("b")); + EXPECT_EQ(1, stringSet.count("c")); +} + +TEST_F(unordered_set, decodingFromAYamlListThatContainsDuplicateElementsShouldThrow) { + YAML::Node node = YAML::Load("[a, b, c, c]"); + + EXPECT_ANY_THROW(node.as>()); +} + +TEST_F(unordered_set, emittingAsYamlShouldOutputAYamlListContainingAllValues) { + std::unordered_set stringSet({"a", "b", "c"}); + YAML::Emitter e1; + e1 << stringSet; + + YAML::Node node = YAML::Load(e1.c_str()); + EXPECT_TRUE(node.IsSequence()); + ASSERT_EQ(3, node.size()); + EXPECT_PRED2(&nodeContains, node, "a"); + EXPECT_PRED2(&nodeContains, node, "b"); + EXPECT_PRED2(&nodeContains, node, "c"); +} +} } #endif diff --git a/src/tests/backend/main.cpp b/src/tests/backend/main.cpp index 8925843b..39ead0f7 100644 --- a/src/tests/backend/main.cpp +++ b/src/tests/backend/main.cpp @@ -18,40 +18,39 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License - along with LOOT. If not, see + along with LOOT. If not, see . */ -#include "tests/api/loot_db_test.h" - -#include "app/loot_paths_test.h" -#include "app/loot_settings_test.h" -#include "app/loot_state_test.h" -#include "game/game_test.h" -#include "game/game_cache_test.h" -#include "game/game_settings_test.h" -#include "game/load_order_handler_test.h" -#include "helpers/git_helper_test.h" -#include "helpers/helpers_test.h" -#include "helpers/language_test.h" -#include "helpers/version_test.h" -#include "helpers/yaml_set_helpers_test.h" -#include "metadata/condition_grammar_test.h" -#include "metadata/conditional_metadata_test.h" -#include "metadata/file_test.h" -#include "metadata/location_test.h" -#include "metadata/message_test.h" -#include "metadata/message_content_test.h" -#include "metadata/plugin_dirty_info_test.h" -#include "metadata/plugin_metadata_test.h" -#include "metadata/tag_test.h" -#include "plugin/plugin_test.h" -#include "plugin/plugin_sorter_test.h" -#include "masterlist_test.h" -#include "metadata_list_test.h" - #include +#include "tests/api/loot_db_test.h" +#include "tests/backend/app/loot_paths_test.h" +#include "tests/backend/app/loot_settings_test.h" +#include "tests/backend/app/loot_state_test.h" +#include "tests/backend/game/game_test.h" +#include "tests/backend/game/game_cache_test.h" +#include "tests/backend/game/game_settings_test.h" +#include "tests/backend/game/load_order_handler_test.h" +#include "tests/backend/helpers/git_helper_test.h" +#include "tests/backend/helpers/helpers_test.h" +#include "tests/backend/helpers/language_test.h" +#include "tests/backend/helpers/version_test.h" +#include "tests/backend/helpers/yaml_set_helpers_test.h" +#include "tests/backend/metadata/condition_grammar_test.h" +#include "tests/backend/metadata/conditional_metadata_test.h" +#include "tests/backend/metadata/file_test.h" +#include "tests/backend/metadata/location_test.h" +#include "tests/backend/metadata/message_test.h" +#include "tests/backend/metadata/message_content_test.h" +#include "tests/backend/metadata/plugin_dirty_info_test.h" +#include "tests/backend/metadata/plugin_metadata_test.h" +#include "tests/backend/metadata/tag_test.h" +#include "tests/backend/plugin/plugin_test.h" +#include "tests/backend/plugin/plugin_sorter_test.h" +#include "tests/backend/masterlist_test.h" +#include "tests/backend/metadata_list_test.h" + TEST(ModuloOperator, shouldConformToTheCpp11Standard) { // C++11 defines the modulo operator more strongly // (only x % 0 is left undefined), whereas C++03 @@ -59,25 +58,25 @@ TEST(ModuloOperator, shouldConformToTheCpp11Standard) { // Test that the modulo operator has been implemented // according to C++11. - EXPECT_EQ(0, 20 % 5); - EXPECT_EQ(0, 20 % -5); - EXPECT_EQ(0, -20 % 5); - EXPECT_EQ(0, -20 % -5); + EXPECT_EQ(0, 20 % 5); + EXPECT_EQ(0, 20 % -5); + EXPECT_EQ(0, -20 % 5); + EXPECT_EQ(0, -20 % -5); - EXPECT_EQ(2, 9 % 7); - EXPECT_EQ(2, 9 % -7); - EXPECT_EQ(-2, -9 % 7); - EXPECT_EQ(-2, -9 % -7); + EXPECT_EQ(2, 9 % 7); + EXPECT_EQ(2, 9 % -7); + EXPECT_EQ(-2, -9 % 7); + EXPECT_EQ(-2, -9 % -7); } int main(int argc, char **argv) { //Set the locale to get encoding conversions working correctly. - std::locale::global(boost::locale::generator().generate("")); - boost::filesystem::path::imbue(std::locale()); + std::locale::global(boost::locale::generator().generate("")); + boost::filesystem::path::imbue(std::locale()); - //Disable logging or else stdout will get overrun. - boost::log::core::get()->set_logging_enabled(false); + //Disable logging or else stdout will get overrun. + boost::log::core::get()->set_logging_enabled(false); - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/tests/backend/masterlist_test.h b/src/tests/backend/masterlist_test.h index 00c7bfb4..c85a713a 100644 --- a/src/tests/backend/masterlist_test.h +++ b/src/tests/backend/masterlist_test.h @@ -22,195 +22,196 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_MASTERLIST -#define LOOT_TEST_BACKEND_MASTERLIST +#ifndef LOOT_TESTS_BACKEND_MASTERLIST_TEST +#define LOOT_TESTS_BACKEND_MASTERLIST_TEST #include "backend/masterlist.h" + #include "backend/app/loot_paths.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class MasterlistTest : public BaseGameTest { - protected: - MasterlistTest() : - repoBranch("master"), - repoUrl("https://github.com/loot/testing-metadata.git"), - masterlistPath(localPath / "masterlist.yaml") {} +namespace test { +class MasterlistTest : public BaseGameTest { +protected: + MasterlistTest() : + repoBranch("master"), + repoUrl("https://github.com/loot/testing-metadata.git"), + masterlistPath(localPath / "masterlist.yaml") {} - void SetUp() { - BaseGameTest::SetUp(); + void SetUp() { + BaseGameTest::SetUp(); - ASSERT_FALSE(boost::filesystem::exists(masterlistPath)); - ASSERT_FALSE(boost::filesystem::exists(localPath / ".git")); + ASSERT_FALSE(boost::filesystem::exists(masterlistPath)); + ASSERT_FALSE(boost::filesystem::exists(localPath / ".git")); - ASSERT_NO_THROW(boost::filesystem::create_directories(LootPaths::getLootDataPath() / Game(GetParam()).FolderName())); - } + ASSERT_NO_THROW(boost::filesystem::create_directories(LootPaths::getLootDataPath() / Game(GetParam()).FolderName())); + } - void TearDown() { - BaseGameTest::TearDown(); + void TearDown() { + BaseGameTest::TearDown(); - ASSERT_NO_THROW(boost::filesystem::remove(masterlistPath)); - ASSERT_NO_THROW(boost::filesystem::remove_all(localPath / ".git")); + ASSERT_NO_THROW(boost::filesystem::remove(masterlistPath)); + ASSERT_NO_THROW(boost::filesystem::remove_all(localPath / ".git")); - ASSERT_NO_THROW(boost::filesystem::remove(LootPaths::getLootDataPath() / Game(GetParam()).FolderName() / "masterlist.yaml")); - ASSERT_NO_THROW(boost::filesystem::remove_all(LootPaths::getLootDataPath() / Game(GetParam()).FolderName() / ".git")); - } + ASSERT_NO_THROW(boost::filesystem::remove(LootPaths::getLootDataPath() / Game(GetParam()).FolderName() / "masterlist.yaml")); + ASSERT_NO_THROW(boost::filesystem::remove_all(LootPaths::getLootDataPath() / Game(GetParam()).FolderName() / ".git")); + } - const std::string repoUrl; - const std::string repoBranch; + const std::string repoUrl; + const std::string repoBranch; - const boost::filesystem::path masterlistPath; - }; + const boost::filesystem::path masterlistPath; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - MasterlistTest, - ::testing::Values( - GameType::tes4, - GameType::tes5, - GameType::fo3, - GameType::fonv, - GameType::fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + MasterlistTest, + ::testing::Values( + GameType::tes4, + GameType::tes5, + GameType::fo3, + GameType::fonv, + GameType::fo4)); - TEST_P(MasterlistTest, updateWithGameParameterShouldReturnTrueIfNoMasterlistExists) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - game.SetRepoURL(repoUrl); - game.SetRepoBranch(repoBranch); - ASSERT_NO_THROW(game.Init(false, localPath)); +TEST_P(MasterlistTest, updateWithGameParameterShouldReturnTrueIfNoMasterlistExists) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + game.SetRepoURL(repoUrl); + game.SetRepoBranch(repoBranch); + ASSERT_NO_THROW(game.Init(false, localPath)); - // This may fail on Windows if a 'real' LOOT install is also present. - Masterlist masterlist; - EXPECT_TRUE(masterlist.Update(game)); - EXPECT_TRUE(boost::filesystem::exists(game.MasterlistPath())); - } + // This may fail on Windows if a 'real' LOOT install is also present. + Masterlist masterlist; + EXPECT_TRUE(masterlist.Update(game)); + EXPECT_TRUE(boost::filesystem::exists(game.MasterlistPath())); +} - TEST_P(MasterlistTest, updateWithGameParameterShouldReturnFalseIfAnUpToDateMasterlistExists) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - game.SetRepoURL(repoUrl); - game.SetRepoBranch(repoBranch); - ASSERT_NO_THROW(game.Init(false, localPath)); +TEST_P(MasterlistTest, updateWithGameParameterShouldReturnFalseIfAnUpToDateMasterlistExists) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + game.SetRepoURL(repoUrl); + game.SetRepoBranch(repoBranch); + ASSERT_NO_THROW(game.Init(false, localPath)); - // This may fail on Windows if a 'real' LOOT install is also present. - Masterlist masterlist; - EXPECT_TRUE(masterlist.Update(game)); - EXPECT_TRUE(boost::filesystem::exists(game.MasterlistPath())); + // This may fail on Windows if a 'real' LOOT install is also present. + Masterlist masterlist; + EXPECT_TRUE(masterlist.Update(game)); + EXPECT_TRUE(boost::filesystem::exists(game.MasterlistPath())); - EXPECT_FALSE(masterlist.Update(game)); - EXPECT_TRUE(boost::filesystem::exists(game.MasterlistPath())); - } + EXPECT_FALSE(masterlist.Update(game)); + EXPECT_TRUE(boost::filesystem::exists(game.MasterlistPath())); +} - TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfAnInvalidPathIsGiven) { - Masterlist masterlist; +TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfAnInvalidPathIsGiven) { + Masterlist masterlist; - EXPECT_ANY_THROW(masterlist.Update(";//\?", repoUrl, repoBranch)); - } + EXPECT_ANY_THROW(masterlist.Update(";//\?", repoUrl, repoBranch)); +} - TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfABlankPathIsGiven) { - Masterlist masterlist; +TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfABlankPathIsGiven) { + Masterlist masterlist; - EXPECT_ANY_THROW(masterlist.Update("", repoUrl, repoBranch)); - } + EXPECT_ANY_THROW(masterlist.Update("", repoUrl, repoBranch)); +} - TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfABranchThatDoesNotExistIsGiven) { - Masterlist masterlist; +TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfABranchThatDoesNotExistIsGiven) { + Masterlist masterlist; - EXPECT_ANY_THROW(masterlist.Update(masterlistPath, - repoUrl, - "missing-branch")); - } + EXPECT_ANY_THROW(masterlist.Update(masterlistPath, + repoUrl, + "missing-branch")); +} - TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfABlankBranchIsGiven) { - Masterlist masterlist; +TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfABlankBranchIsGiven) { + Masterlist masterlist; - EXPECT_ANY_THROW(masterlist.Update(masterlistPath, repoUrl, "")); - } + EXPECT_ANY_THROW(masterlist.Update(masterlistPath, repoUrl, "")); +} - TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfAUrlThatDoesNotExistIsGiven) { - Masterlist masterlist; +TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfAUrlThatDoesNotExistIsGiven) { + Masterlist masterlist; - EXPECT_ANY_THROW(masterlist.Update(masterlistPath, - "https://github.com/loot/does-not-exist.git", - repoBranch)); - } + EXPECT_ANY_THROW(masterlist.Update(masterlistPath, + "https://github.com/loot/does-not-exist.git", + repoBranch)); +} - TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfABlankUrlIsGiven) { - Masterlist masterlist; - EXPECT_ANY_THROW(masterlist.Update(masterlistPath, "", repoBranch)); - } +TEST_P(MasterlistTest, updateWithSeparateParametersShouldThrowIfABlankUrlIsGiven) { + Masterlist masterlist; + EXPECT_ANY_THROW(masterlist.Update(masterlistPath, "", repoBranch)); +} - TEST_P(MasterlistTest, updateWithSeparateParametersShouldReturnTrueIfNoMasterlistExists) { - Masterlist masterlist; - EXPECT_TRUE(masterlist.Update(masterlistPath, - repoUrl, - repoBranch)); - } +TEST_P(MasterlistTest, updateWithSeparateParametersShouldReturnTrueIfNoMasterlistExists) { + Masterlist masterlist; + EXPECT_TRUE(masterlist.Update(masterlistPath, + repoUrl, + repoBranch)); +} - TEST_P(MasterlistTest, updateWithSeparateParametersShouldReturnFalseIfAnUpToDateMasterlistExists) { - Masterlist masterlist; +TEST_P(MasterlistTest, updateWithSeparateParametersShouldReturnFalseIfAnUpToDateMasterlistExists) { + Masterlist masterlist; - EXPECT_TRUE(masterlist.Update(masterlistPath, - repoUrl, - repoBranch)); + EXPECT_TRUE(masterlist.Update(masterlistPath, + repoUrl, + repoBranch)); - EXPECT_FALSE(masterlist.Update(masterlistPath, - repoUrl, - repoBranch)); - } + EXPECT_FALSE(masterlist.Update(masterlistPath, + repoUrl, + repoBranch)); +} - TEST_P(MasterlistTest, getInfoShouldThrowIfNoMasterlistExistsAtTheGivenPath) { - Masterlist masterlist; - EXPECT_ANY_THROW(masterlist.GetInfo(masterlistPath, false)); - } +TEST_P(MasterlistTest, getInfoShouldThrowIfNoMasterlistExistsAtTheGivenPath) { + Masterlist masterlist; + EXPECT_ANY_THROW(masterlist.GetInfo(masterlistPath, false)); +} - TEST_P(MasterlistTest, getInfoShouldThrowIfTheGivenPathDoesNotBelongToAGitRepository) { - ASSERT_NO_THROW(boost::filesystem::copy("./testing-metadata/masterlist.yaml", masterlistPath)); +TEST_P(MasterlistTest, getInfoShouldThrowIfTheGivenPathDoesNotBelongToAGitRepository) { + ASSERT_NO_THROW(boost::filesystem::copy("./testing-metadata/masterlist.yaml", masterlistPath)); - Masterlist masterlist; - EXPECT_ANY_THROW(masterlist.GetInfo(masterlistPath, false)); - } + Masterlist masterlist; + EXPECT_ANY_THROW(masterlist.GetInfo(masterlistPath, false)); +} - TEST_P(MasterlistTest, getInfoShouldReturnRevisionAndDateStringsOfTheCorrectLengthsWhenRequestingALongId) { - Masterlist masterlist; - ASSERT_TRUE(masterlist.Update(masterlistPath, - repoUrl, - repoBranch)); +TEST_P(MasterlistTest, getInfoShouldReturnRevisionAndDateStringsOfTheCorrectLengthsWhenRequestingALongId) { + Masterlist masterlist; + ASSERT_TRUE(masterlist.Update(masterlistPath, + repoUrl, + repoBranch)); - Masterlist::Info info = masterlist.GetInfo(masterlistPath, false); - EXPECT_EQ(40, info.revision.length()); - EXPECT_EQ(10, info.date.length()); - } + Masterlist::Info info = masterlist.GetInfo(masterlistPath, false); + EXPECT_EQ(40, info.revision.length()); + EXPECT_EQ(10, info.date.length()); +} - TEST_P(MasterlistTest, getInfoShouldReturnRevisionAndDateStringsOfTheCorrectLengthsWhenRequestingAShortId) { - Masterlist masterlist; - ASSERT_TRUE(masterlist.Update(masterlistPath, - repoUrl, - repoBranch)); +TEST_P(MasterlistTest, getInfoShouldReturnRevisionAndDateStringsOfTheCorrectLengthsWhenRequestingAShortId) { + Masterlist masterlist; + ASSERT_TRUE(masterlist.Update(masterlistPath, + repoUrl, + repoBranch)); - Masterlist::Info info = masterlist.GetInfo(masterlistPath, true); - EXPECT_GE((unsigned)40, info.revision.length()); - EXPECT_LE((unsigned)7, info.revision.length()); - EXPECT_EQ(10, info.date.length()); - } + Masterlist::Info info = masterlist.GetInfo(masterlistPath, true); + EXPECT_GE((unsigned)40, info.revision.length()); + EXPECT_LE((unsigned)7, info.revision.length()); + EXPECT_EQ(10, info.date.length()); +} - TEST_P(MasterlistTest, getInfoShouldAppendSuffixesToReturnedStringsIfTheMasterlistHasBeenEdited) { - Masterlist masterlist; - ASSERT_TRUE(masterlist.Update(masterlistPath, - repoUrl, - repoBranch)); - boost::filesystem::ofstream out(masterlistPath); - out.close(); +TEST_P(MasterlistTest, getInfoShouldAppendSuffixesToReturnedStringsIfTheMasterlistHasBeenEdited) { + Masterlist masterlist; + ASSERT_TRUE(masterlist.Update(masterlistPath, + repoUrl, + repoBranch)); + boost::filesystem::ofstream out(masterlistPath); + out.close(); - Masterlist::Info info = masterlist.GetInfo(masterlistPath, false); - EXPECT_EQ(49, info.revision.length()); - EXPECT_EQ(" (edited)", info.revision.substr(40)); - EXPECT_EQ(19, info.date.length()); - EXPECT_EQ(" (edited)", info.date.substr(10)); - } - } + Masterlist::Info info = masterlist.GetInfo(masterlistPath, false); + EXPECT_EQ(49, info.revision.length()); + EXPECT_EQ(" (edited)", info.revision.substr(40)); + EXPECT_EQ(19, info.date.length()); + EXPECT_EQ(" (edited)", info.date.substr(10)); +} +} } #endif diff --git a/src/tests/backend/metadata/condition_grammar_test.h b/src/tests/backend/metadata/condition_grammar_test.h index 75654af0..0c134765 100644 --- a/src/tests/backend/metadata/condition_grammar_test.h +++ b/src/tests/backend/metadata/condition_grammar_test.h @@ -22,654 +22,655 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_CONDITION_GRAMMAR -#define LOOT_TEST_BACKEND_METADATA_CONDITION_GRAMMAR +#ifndef LOOT_TESTS_BACKEND_METADATA_CONDITION_GRAMMAR_TEST +#define LOOT_TESTS_BACKEND_METADATA_CONDITION_GRAMMAR_TEST + +#include "backend/metadata/condition_grammar.h" #include "backend/error.h" -#include "backend/metadata/condition_grammar.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class ConditionGrammarTest : public BaseGameTest { - protected: - ConditionGrammarTest() : - resourcePath(dataPath / "resource" / "detail" / "resource.txt"), - game(Game(GetParam()).SetGamePath(dataPath.parent_path())), - result(false), - success(false) {} - - inline void SetUp() { - BaseGameTest::SetUp(); - - // Write out an empty resource file. - ASSERT_NO_THROW(boost::filesystem::create_directories(resourcePath.parent_path())); - boost::filesystem::ofstream out(resourcePath); - out.close(); - ASSERT_TRUE(boost::filesystem::exists(resourcePath)); - } - - inline void TearDown() { - BaseGameTest::TearDown(); - - ASSERT_NO_THROW(boost::filesystem::remove(resourcePath)); - } - - typedef ConditionGrammar Grammar; - - const boost::filesystem::path resourcePath; - - Game game; - boost::spirit::qi::space_type skipper; - bool result; - bool success; - }; - - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - ConditionGrammarTest, - ::testing::Values( - GameType::tes4, - GameType::tes5, - GameType::fo3, - GameType::fonv, - GameType::fo4)); - - TEST_P(ConditionGrammarTest, parsingInvalidSyntaxShouldThrow) { - Grammar grammar(nullptr); - std::string condition("file(foo)"); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result), Error); - } - - TEST_P(ConditionGrammarTest, evaluatingInvalidSyntaxShouldThrow) { - Grammar grammar(&game); - std::string condition("file(foo)"); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result), Error); - } - - TEST_P(ConditionGrammarTest, parsingAnEmptyConditionShouldThrow) { - Grammar grammar(nullptr); - std::string condition(""); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result), Error); - } - - TEST_P(ConditionGrammarTest, evaluatingAnEmptyConditionShouldThrow) { - Grammar grammar(&game); - std::string condition(""); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result), Error); - } - - TEST_P(ConditionGrammarTest, aFileConditionWithAPluginThatExistsShouldEvaluateToTrue) { - Grammar grammar(&game); - std::string condition("file(\"" + blankEsm + "\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aFileConditionWithAPluginThatDoesNotExistShouldEvaluateToFalse) { - Grammar grammar(&game); - std::string condition("file(\"" + missingEsp + "\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, evaluatingAFileConditionForAnUnsafePathShouldThrow) { - Grammar grammar(&game); - std::string condition("file(\"../../" + blankEsm + "\")"); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result), Error); - } - - TEST_P(ConditionGrammarTest, aRegexConditionWithAnInvalidRegexShouldThrow) { - Grammar grammar(&game); - std::string condition("regex(\"RagnvaldBook(Farengar(+Ragnvald)?)?\\.esp\")"); - - EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result), Error); - } - - TEST_P(ConditionGrammarTest, aRegexConditionWithARegexMatchingAPluginThatExistsShouldEvaluateToTrue) { - Grammar grammar(&game); - std::string condition("regex(\"Blank.+\\.esm\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aRegexConditionWithARegexMatchingAPluginThatDoesNotExistShouldEvaluateToFalse) { - Grammar grammar(&game); - std::string condition("regex(\"Blank\\.m.+\\.esm\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aRegexConditionWithARegexMatchingAFileInASubfolderThatExistsShouldEvaluateToTrue) { - Grammar grammar(&game); - std::string condition("regex(\"resource\\\\detail\\\\resource\\.txt\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aManyConditionWithARegexMatchingMoreThanOnePluginShouldEvaluateToTrue) { - Grammar grammar(&game); - std::string condition("many(\"Blank.+\\.esm\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aManyConditionWithARegexMatchingOnlyOnePluginShouldEvaluateToFalse) { - Grammar grammar(&game); - std::string condition("many(\"Blank\\.esm\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aChecksumConditionWithACrcThatMatchesTheActualPluginCrcShouldEvaluateToTrue) { - Grammar grammar(&game); - std::string condition("checksum(\"" + blankEsm + "\", " + IntToHexString(blankEsmCrc) + ")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aChecksumConditionWithACrcThatDoesNotMatchTheActualPluginCrcShouldEvaluateToFalse) { - Grammar grammar(&game); - std::string condition("checksum(\"" + blankEsm + "\", DEADBEEF)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionEqualityConditionWithAVersionThatEqualsTheActualPluginVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", ==)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionEqualityConditionWithAVersionThatDoesNotEqualTheActualPluginVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"6.0\", ==)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionEqualityConditionForAPluginWithNoVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsp + "\", \"6.0\", ==)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionInequalityConditionWithAVersionThatDoesNotEqualTheActualPluginVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"6.0\", !=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionInequalityConditionWithAVersionThatEqualsTheActualPluginVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", !=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionInequalityConditionForAPluginWithNoVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsp + "\", \"6.0\", !=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionLessThanConditionWithAnActualPluginVersionLessThanTheGivenVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"6.0\", <)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionLessThanConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", <)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionLessThanConditionForAPluginWithNoVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsp + "\", \"5.0\", <)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionGreaterThanConditionWithAnActualPluginVersionGreaterThanTheGivenVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"4.0\", >)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionGreaterThanConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", >)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionGreaterThanConditionForAPluginWithNoVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsp + "\", \"5.0\", >)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionLessThanOrEqualToConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", <=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionLessThanOrEqualToConditionWithAnActualPluginVersionGreaterThanTheGivenVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"4.0\", <=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionLessThanOrEqualToConditionForAPluginWithNoVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsp + "\", \"5.0\", <=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionGreaterThanOrEqualToConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.Init(false, localPath)); - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"5.0\", >=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aVersionGreaterThanOrEqualToConditionWithAnActualPluginVersionLessThanTheGivenVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsm + "\", \"6.0\", >=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aVersionGreaterThanOrEqualToConditionForAPluginWithNoVersionShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.LoadPlugins(true)); - - Grammar grammar(&game); - std::string condition("version(\"" + blankEsp + "\", \"5.0\", >=)"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, anActiveConditionWithAPluginThatIsActiveShouldEvaluateToTrue) { - ASSERT_NO_THROW(game.Init(false, localPath)); - - Grammar grammar(&game); - std::string condition("active(\"" + blankEsm + "\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, anActiveConditionWithAPluginThatIsNotActiveShouldEvaluateToFalse) { - ASSERT_NO_THROW(game.Init(false, localPath)); - - Grammar grammar(&game); - std::string condition("active(\"" + blankEsp + "\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aFalseConditionPrecededByANegatorShouldEvaluateToTrue) { - Grammar grammar(&game); - std::string condition("not file(\"" + missingEsp + "\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aTrueConditionPrecededByANegatorShouldEvaluateToFalse) { - Grammar grammar(&game); - std::string condition("not file(\"" + blankEsm + "\")"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(condition), - std::cend(condition), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, twoTrueConditionsJoinedByAnAndShouldEvaluateToTrue) { - Grammar grammar(&game); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound(condition + " and " + condition); - - success = boost::spirit::qi::phrase_parse(std::cbegin(compound), - std::cend(compound), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, aTrueAndAFalseConditionJoinedByAnAndShouldEvaluateToFalse) { - Grammar grammar(&game); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound(condition + " and not " + condition); - - success = boost::spirit::qi::phrase_parse(std::cbegin(compound), - std::cend(compound), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, aFalseAndATrueConditionJoinedByAnOrShouldEvaluateToTrue) { - Grammar grammar(&game); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound("not " + condition + " or " + condition); - - success = boost::spirit::qi::phrase_parse(std::cbegin(compound), - std::cend(compound), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, twoFalseConditionsJoinedByAnOrShouldEvaluateToFalse) { - Grammar grammar(&game); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound("not " + condition + " or not " + condition); - - success = boost::spirit::qi::phrase_parse(std::cbegin(compound), - std::cend(compound), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - - TEST_P(ConditionGrammarTest, andOperatorsShouldTakePrecedenceOverOrOperators) { - Grammar grammar(&game); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound("not " + condition + " and " + condition + " or " + condition); - - success = boost::spirit::qi::phrase_parse(std::cbegin(compound), - std::cend(compound), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_TRUE(result); - } - - TEST_P(ConditionGrammarTest, parenthesesShouldTakePrecedenceOverAndOperators) { - Grammar grammar(&game); - std::string condition("file(\"" + blankEsm + "\")"); - std::string compound("not " + condition + " and ( " + condition + " or " + condition + " )"); - - success = boost::spirit::qi::phrase_parse(std::cbegin(compound), - std::cend(compound), - grammar, - skipper, - result); - EXPECT_TRUE(success); - EXPECT_FALSE(result); - } - } +namespace test { +class ConditionGrammarTest : public BaseGameTest { +protected: + typedef ConditionGrammar Grammar; + + ConditionGrammarTest() : + resourcePath(dataPath / "resource" / "detail" / "resource.txt"), + game_(Game(GetParam()).SetGamePath(dataPath.parent_path())), + result_(false), + success_(false) {} + + inline void SetUp() { + BaseGameTest::SetUp(); + + // Write out an empty resource file. + ASSERT_NO_THROW(boost::filesystem::create_directories(resourcePath.parent_path())); + boost::filesystem::ofstream out(resourcePath); + out.close(); + ASSERT_TRUE(boost::filesystem::exists(resourcePath)); + } + + inline void TearDown() { + BaseGameTest::TearDown(); + + ASSERT_NO_THROW(boost::filesystem::remove(resourcePath)); + } + + const boost::filesystem::path resourcePath; + + Game game_; + boost::spirit::qi::space_type skipper_; + bool result_; + bool success_; +}; + +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + ConditionGrammarTest, + ::testing::Values( + GameType::tes4, + GameType::tes5, + GameType::fo3, + GameType::fonv, + GameType::fo4)); + +TEST_P(ConditionGrammarTest, parsingInvalidSyntaxShouldThrow) { + Grammar grammar(nullptr); + std::string condition("file(foo)"); + + EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_), Error); +} + +TEST_P(ConditionGrammarTest, evaluatingInvalidSyntaxShouldThrow) { + Grammar grammar(&game_); + std::string condition("file(foo)"); + + EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_), Error); +} + +TEST_P(ConditionGrammarTest, parsingAnEmptyConditionShouldThrow) { + Grammar grammar(nullptr); + std::string condition(""); + + EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_), Error); +} + +TEST_P(ConditionGrammarTest, evaluatingAnEmptyConditionShouldThrow) { + Grammar grammar(&game_); + std::string condition(""); + + EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_), Error); +} + +TEST_P(ConditionGrammarTest, aFileConditionWithAPluginThatExistsShouldEvaluateToTrue) { + Grammar grammar(&game_); + std::string condition("file(\"" + blankEsm + "\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aFileConditionWithAPluginThatDoesNotExistShouldEvaluateToFalse) { + Grammar grammar(&game_); + std::string condition("file(\"" + missingEsp + "\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, evaluatingAFileConditionForAnUnsafePathShouldThrow) { + Grammar grammar(&game_); + std::string condition("file(\"../../" + blankEsm + "\")"); + + EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_), Error); +} + +TEST_P(ConditionGrammarTest, aRegexConditionWithAnInvalidRegexShouldThrow) { + Grammar grammar(&game_); + std::string condition("regex(\"RagnvaldBook(Farengar(+Ragnvald)?)?\\.esp\")"); + + EXPECT_THROW(boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_), Error); +} + +TEST_P(ConditionGrammarTest, aRegexConditionWithARegexMatchingAPluginThatExistsShouldEvaluateToTrue) { + Grammar grammar(&game_); + std::string condition("regex(\"Blank.+\\.esm\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aRegexConditionWithARegexMatchingAPluginThatDoesNotExistShouldEvaluateToFalse) { + Grammar grammar(&game_); + std::string condition("regex(\"Blank\\.m.+\\.esm\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aRegexConditionWithARegexMatchingAFileInASubfolderThatExistsShouldEvaluateToTrue) { + Grammar grammar(&game_); + std::string condition("regex(\"resource\\\\detail\\\\resource\\.txt\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aManyConditionWithARegexMatchingMoreThanOnePluginShouldEvaluateToTrue) { + Grammar grammar(&game_); + std::string condition("many(\"Blank.+\\.esm\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aManyConditionWithARegexMatchingOnlyOnePluginShouldEvaluateToFalse) { + Grammar grammar(&game_); + std::string condition("many(\"Blank\\.esm\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aChecksumConditionWithACrcThatMatchesTheActualPluginCrcShouldEvaluateToTrue) { + Grammar grammar(&game_); + std::string condition("checksum(\"" + blankEsm + "\", " + IntToHexString(blankEsmCrc) + ")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aChecksumConditionWithACrcThatDoesNotMatchTheActualPluginCrcShouldEvaluateToFalse) { + Grammar grammar(&game_); + std::string condition("checksum(\"" + blankEsm + "\", DEADBEEF)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionEqualityConditionWithAVersionThatEqualsTheActualPluginVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"5.0\", ==)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionEqualityConditionWithAVersionThatDoesNotEqualTheActualPluginVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"6.0\", ==)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionEqualityConditionForAPluginWithNoVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsp + "\", \"6.0\", ==)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionInequalityConditionWithAVersionThatDoesNotEqualTheActualPluginVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"6.0\", !=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionInequalityConditionWithAVersionThatEqualsTheActualPluginVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"5.0\", !=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionInequalityConditionForAPluginWithNoVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsp + "\", \"6.0\", !=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionLessThanConditionWithAnActualPluginVersionLessThanTheGivenVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"6.0\", <)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionLessThanConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"5.0\", <)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionLessThanConditionForAPluginWithNoVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsp + "\", \"5.0\", <)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionGreaterThanConditionWithAnActualPluginVersionGreaterThanTheGivenVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"4.0\", >)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionGreaterThanConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"5.0\", >)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionGreaterThanConditionForAPluginWithNoVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsp + "\", \"5.0\", >)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionLessThanOrEqualToConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"5.0\", <=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionLessThanOrEqualToConditionWithAnActualPluginVersionGreaterThanTheGivenVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"4.0\", <=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionLessThanOrEqualToConditionForAPluginWithNoVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsp + "\", \"5.0\", <=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionGreaterThanOrEqualToConditionWithAnActualPluginVersionEqualToTheGivenVersionShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.Init(false, localPath)); + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"5.0\", >=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionGreaterThanOrEqualToConditionWithAnActualPluginVersionLessThanTheGivenVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsm + "\", \"6.0\", >=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aVersionGreaterThanOrEqualToConditionForAPluginWithNoVersionShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.LoadPlugins(true)); + + Grammar grammar(&game_); + std::string condition("version(\"" + blankEsp + "\", \"5.0\", >=)"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, anActiveConditionWithAPluginThatIsActiveShouldEvaluateToTrue) { + ASSERT_NO_THROW(game_.Init(false, localPath)); + + Grammar grammar(&game_); + std::string condition("active(\"" + blankEsm + "\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, anActiveConditionWithAPluginThatIsNotActiveShouldEvaluateToFalse) { + ASSERT_NO_THROW(game_.Init(false, localPath)); + + Grammar grammar(&game_); + std::string condition("active(\"" + blankEsp + "\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aFalseConditionPrecededByANegatorShouldEvaluateToTrue) { + Grammar grammar(&game_); + std::string condition("not file(\"" + missingEsp + "\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aTrueConditionPrecededByANegatorShouldEvaluateToFalse) { + Grammar grammar(&game_); + std::string condition("not file(\"" + blankEsm + "\")"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(condition), + std::cend(condition), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, twoTrueConditionsJoinedByAnAndShouldEvaluateToTrue) { + Grammar grammar(&game_); + std::string condition("file(\"" + blankEsm + "\")"); + std::string compound(condition + " and " + condition); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(compound), + std::cend(compound), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, aTrueAndAFalseConditionJoinedByAnAndShouldEvaluateToFalse) { + Grammar grammar(&game_); + std::string condition("file(\"" + blankEsm + "\")"); + std::string compound(condition + " and not " + condition); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(compound), + std::cend(compound), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, aFalseAndATrueConditionJoinedByAnOrShouldEvaluateToTrue) { + Grammar grammar(&game_); + std::string condition("file(\"" + blankEsm + "\")"); + std::string compound("not " + condition + " or " + condition); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(compound), + std::cend(compound), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, twoFalseConditionsJoinedByAnOrShouldEvaluateToFalse) { + Grammar grammar(&game_); + std::string condition("file(\"" + blankEsm + "\")"); + std::string compound("not " + condition + " or not " + condition); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(compound), + std::cend(compound), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} + +TEST_P(ConditionGrammarTest, andOperatorsShouldTakePrecedenceOverOrOperators) { + Grammar grammar(&game_); + std::string condition("file(\"" + blankEsm + "\")"); + std::string compound("not " + condition + " and " + condition + " or " + condition); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(compound), + std::cend(compound), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_TRUE(result_); +} + +TEST_P(ConditionGrammarTest, parenthesesShouldTakePrecedenceOverAndOperators) { + Grammar grammar(&game_); + std::string condition("file(\"" + blankEsm + "\")"); + std::string compound("not " + condition + " and ( " + condition + " or " + condition + " )"); + + success_ = boost::spirit::qi::phrase_parse(std::cbegin(compound), + std::cend(compound), + grammar, + skipper_, + result_); + EXPECT_TRUE(success_); + EXPECT_FALSE(result_); +} +} } #endif diff --git a/src/tests/backend/metadata/conditional_metadata_test.h b/src/tests/backend/metadata/conditional_metadata_test.h index f24297a0..1cdb5a7a 100644 --- a/src/tests/backend/metadata/conditional_metadata_test.h +++ b/src/tests/backend/metadata/conditional_metadata_test.h @@ -22,101 +22,102 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_CONDITIONAL_METADATA -#define LOOT_TEST_BACKEND_METADATA_CONDITIONAL_METADATA +#ifndef LOOT_TESTS_BACKEND_METADATA_CONDITIONAL_METADATA_TEST +#define LOOT_TESTS_BACKEND_METADATA_CONDITIONAL_METADATA_TEST + +#include "backend/metadata/conditional_metadata.h" #include "backend/error.h" -#include "backend/metadata/conditional_metadata.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class ConditionalMetadataTest : public BaseGameTest { - protected: - ConditionalMetadata conditionalMetadata; - }; +namespace test { +class ConditionalMetadataTest : public BaseGameTest { +protected: + ConditionalMetadata conditionalMetadata_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - ConditionalMetadataTest, - ::testing::Values( - GameType::tes4, - GameType::tes5, - GameType::fo3, - GameType::fonv, - GameType::fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + ConditionalMetadataTest, + ::testing::Values( + GameType::tes4, + GameType::tes5, + GameType::fo3, + GameType::fonv, + GameType::fo4)); - TEST_P(ConditionalMetadataTest, defaultConstructorShouldSetEmptyConditionString) { - EXPECT_TRUE(conditionalMetadata.Condition().empty()); - } +TEST_P(ConditionalMetadataTest, defaultConstructorShouldSetEmptyConditionString) { + EXPECT_TRUE(conditionalMetadata_.Condition().empty()); +} - TEST_P(ConditionalMetadataTest, stringConstructorShouldSetConditionToGivenString) { - std::string condition("condition"); - conditionalMetadata = ConditionalMetadata(condition); +TEST_P(ConditionalMetadataTest, stringConstructorShouldSetConditionToGivenString) { + std::string condition("condition"); + conditionalMetadata_ = ConditionalMetadata(condition); - EXPECT_EQ(condition, conditionalMetadata.Condition()); - } + EXPECT_EQ(condition, conditionalMetadata_.Condition()); +} - TEST_P(ConditionalMetadataTest, isConditionalShouldBeFalseForAnEmptyConditionString) { - EXPECT_FALSE(conditionalMetadata.IsConditional()); - } +TEST_P(ConditionalMetadataTest, isConditionalShouldBeFalseForAnEmptyConditionString) { + EXPECT_FALSE(conditionalMetadata_.IsConditional()); +} - TEST_P(ConditionalMetadataTest, isConditionalShouldBeTrueForANonEmptyConditionString) { - conditionalMetadata = ConditionalMetadata("condition"); - EXPECT_TRUE(conditionalMetadata.IsConditional()); - } +TEST_P(ConditionalMetadataTest, isConditionalShouldBeTrueForANonEmptyConditionString) { + conditionalMetadata_ = ConditionalMetadata("condition"); + EXPECT_TRUE(conditionalMetadata_.IsConditional()); +} - TEST_P(ConditionalMetadataTest, evalConditionShouldReturnTrueForAnEmptyCondition) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(ConditionalMetadataTest, evalConditionShouldReturnTrueForAnEmptyCondition) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - EXPECT_TRUE(conditionalMetadata.EvalCondition(game)); - } + EXPECT_TRUE(conditionalMetadata_.EvalCondition(game)); +} - TEST_P(ConditionalMetadataTest, evalConditionShouldThrowForAnInvalidCondition) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(ConditionalMetadataTest, evalConditionShouldThrowForAnInvalidCondition) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - conditionalMetadata = ConditionalMetadata("condition"); - EXPECT_THROW(conditionalMetadata.EvalCondition(game), Error); - } + conditionalMetadata_ = ConditionalMetadata("condition"); + EXPECT_THROW(conditionalMetadata_.EvalCondition(game), Error); +} - TEST_P(ConditionalMetadataTest, evalConditionShouldReturnTrueForAConditionThatIsTrue) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(ConditionalMetadataTest, evalConditionShouldReturnTrueForAConditionThatIsTrue) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - conditionalMetadata = ConditionalMetadata("file(\"" + blankEsm + "\")"); - EXPECT_TRUE(conditionalMetadata.EvalCondition(game)); - } + conditionalMetadata_ = ConditionalMetadata("file(\"" + blankEsm + "\")"); + EXPECT_TRUE(conditionalMetadata_.EvalCondition(game)); +} - TEST_P(ConditionalMetadataTest, evalConditionShouldReturnFalseForAConditionThatIsFalse) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(ConditionalMetadataTest, evalConditionShouldReturnFalseForAConditionThatIsFalse) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - conditionalMetadata = ConditionalMetadata("file(\"" + missingEsp + "\")"); - EXPECT_FALSE(conditionalMetadata.EvalCondition(game)); - } + conditionalMetadata_ = ConditionalMetadata("file(\"" + missingEsp + "\")"); + EXPECT_FALSE(conditionalMetadata_.EvalCondition(game)); +} - TEST_P(ConditionalMetadataTest, parseConditionShouldNotThrowForAnEmptyCondition) { - EXPECT_NO_THROW(conditionalMetadata.ParseCondition()); - } +TEST_P(ConditionalMetadataTest, parseConditionShouldNotThrowForAnEmptyCondition) { + EXPECT_NO_THROW(conditionalMetadata_.ParseCondition()); +} - TEST_P(ConditionalMetadataTest, parseConditionShouldThrowForAnInvalidCondition) { - conditionalMetadata = ConditionalMetadata("condition"); - EXPECT_THROW(conditionalMetadata.ParseCondition(), Error); - } +TEST_P(ConditionalMetadataTest, parseConditionShouldThrowForAnInvalidCondition) { + conditionalMetadata_ = ConditionalMetadata("condition"); + EXPECT_THROW(conditionalMetadata_.ParseCondition(), Error); +} - TEST_P(ConditionalMetadataTest, parseConditionShouldNotThrowForATrueCondition) { - conditionalMetadata = ConditionalMetadata("file(\"" + blankEsm + "\")"); - EXPECT_NO_THROW(conditionalMetadata.ParseCondition()); - } +TEST_P(ConditionalMetadataTest, parseConditionShouldNotThrowForATrueCondition) { + conditionalMetadata_ = ConditionalMetadata("file(\"" + blankEsm + "\")"); + EXPECT_NO_THROW(conditionalMetadata_.ParseCondition()); +} - TEST_P(ConditionalMetadataTest, parseConditionShouldNotThrowForAFalseCondition) { - conditionalMetadata = ConditionalMetadata("file(\"" + missingEsp + "\")"); - EXPECT_NO_THROW(conditionalMetadata.ParseCondition()); - } - } +TEST_P(ConditionalMetadataTest, parseConditionShouldNotThrowForAFalseCondition) { + conditionalMetadata_ = ConditionalMetadata("file(\"" + missingEsp + "\")"); + EXPECT_NO_THROW(conditionalMetadata_.ParseCondition()); +} +} } #endif diff --git a/src/tests/backend/metadata/file_test.h b/src/tests/backend/metadata/file_test.h index 0b54013e..18046173 100644 --- a/src/tests/backend/metadata/file_test.h +++ b/src/tests/backend/metadata/file_test.h @@ -22,165 +22,165 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_FILE -#define LOOT_TEST_BACKEND_METADATA_FILE +#ifndef LOOT_TESTS_BACKEND_METADATA_FILE_TEST +#define LOOT_TESTS_BACKEND_METADATA_FILE_TEST #include "backend/metadata/file.h" #include namespace loot { - namespace test { - TEST(File, defaultConstructorShouldInitialiseEmptyStrings) { - File file; +namespace test { +TEST(File, defaultConstructorShouldInitialiseEmptyStrings) { + File file; - EXPECT_EQ("", file.Name()); - EXPECT_EQ("", file.DisplayName()); - EXPECT_EQ("", file.Condition()); - } + EXPECT_EQ("", file.Name()); + EXPECT_EQ("", file.DisplayName()); + EXPECT_EQ("", file.Condition()); +} - TEST(File, stringsConstructorShouldStoreGivenStrings) { - File file("name", "display", "condition"); +TEST(File, stringsConstructorShouldStoreGivenStrings) { + File file("name", "display", "condition"); - EXPECT_EQ("name", file.Name()); - EXPECT_EQ("display", file.DisplayName()); - EXPECT_EQ("condition", file.Condition()); - } + EXPECT_EQ("name", file.Name()); + EXPECT_EQ("display", file.DisplayName()); + EXPECT_EQ("condition", file.Condition()); +} - TEST(File, filesWithCaseInsensitiveEqualNameStringsShouldBeEqual) { - File file1("name", "display1", "condition1"); - File file2("Name", "display2", "condition2"); +TEST(File, filesWithCaseInsensitiveEqualNameStringsShouldBeEqual) { + File file1("name", "display1", "condition1"); + File file2("Name", "display2", "condition2"); - EXPECT_TRUE(file1 == file2); - } + EXPECT_TRUE(file1 == file2); +} - TEST(File, filesWithDifferentNamesShouldBeUnequal) { - File file1("name1"); - File file2("name2"); +TEST(File, filesWithDifferentNamesShouldBeUnequal) { + File file1("name1"); + File file2("name2"); - EXPECT_FALSE(file1 == file2); - } + EXPECT_FALSE(file1 == file2); +} - TEST(File, lessThanOperatorShouldUseCaseInsensitiveLexicographicalNameComparison) { - File file1("name", "display1", "condition1"); - File file2("Name", "display2", "condition2"); +TEST(File, lessThanOperatorShouldUseCaseInsensitiveLexicographicalNameComparison) { + File file1("name", "display1", "condition1"); + File file2("Name", "display2", "condition2"); - EXPECT_FALSE(file1 < file2); - EXPECT_FALSE(file2 < file1); + EXPECT_FALSE(file1 < file2); + EXPECT_FALSE(file2 < file1); - file1 = File("name1"); - file2 = File("name2"); + file1 = File("name1"); + file2 = File("name2"); - EXPECT_TRUE(file1 < file2); - EXPECT_FALSE(file2 < file1); - } + EXPECT_TRUE(file1 < file2); + EXPECT_FALSE(file2 < file1); +} - TEST(File, emittingAsYamlShouldSingleQuoteValues) { - File file("name1", "display1", "condition1"); - YAML::Emitter emitter; - emitter << file; - std::string expected = "name: '" + file.Name() + - "'\ncondition: '" + file.Condition() + - "'\ndisplay: '" + file.DisplayName() + "'"; +TEST(File, emittingAsYamlShouldSingleQuoteValues) { + File file("name1", "display1", "condition1"); + YAML::Emitter emitter; + emitter << file; + std::string expected = "name: '" + file.Name() + + "'\ncondition: '" + file.Condition() + + "'\ndisplay: '" + file.DisplayName() + "'"; - EXPECT_EQ(expected, emitter.c_str()); - } + EXPECT_EQ(expected, emitter.c_str()); +} - TEST(File, emittingAsYamlShouldOutputAsAScalarIfOnlyTheNameStringIsNotEmpty) { - File file("name1"); - YAML::Emitter emitter; - emitter << file; +TEST(File, emittingAsYamlShouldOutputAsAScalarIfOnlyTheNameStringIsNotEmpty) { + File file("name1"); + YAML::Emitter emitter; + emitter << file; - EXPECT_EQ("'" + file.Name() + "'", emitter.c_str()); - } + EXPECT_EQ("'" + file.Name() + "'", emitter.c_str()); +} - TEST(File, emittingAsYamlShouldOmitDisplayFieldIfItMatchesTheNameField) { - File file("name1", "name1"); - YAML::Emitter emitter; - emitter << file; +TEST(File, emittingAsYamlShouldOmitDisplayFieldIfItMatchesTheNameField) { + File file("name1", "name1"); + YAML::Emitter emitter; + emitter << file; - EXPECT_EQ("'" + file.Name() + "'", emitter.c_str()); - } + EXPECT_EQ("'" + file.Name() + "'", emitter.c_str()); +} - TEST(File, emittingAsYamlShouldOmitAnEmptyConditionString) { - File file("name1", "display1"); - YAML::Emitter emitter; - emitter << file; - std::string expected = "name: '" + file.Name() + - "'\ndisplay: '" + file.DisplayName() + "'"; +TEST(File, emittingAsYamlShouldOmitAnEmptyConditionString) { + File file("name1", "display1"); + YAML::Emitter emitter; + emitter << file; + std::string expected = "name: '" + file.Name() + + "'\ndisplay: '" + file.DisplayName() + "'"; - EXPECT_EQ(expected, emitter.c_str()); - } + EXPECT_EQ(expected, emitter.c_str()); +} - TEST(File, encodingAsYamlShouldStoreDataCorrectly) { - File file("name1", "display1", "condition1"); - YAML::Node node; - node = file; +TEST(File, encodingAsYamlShouldStoreDataCorrectly) { + File file("name1", "display1", "condition1"); + YAML::Node node; + node = file; - EXPECT_EQ(file.Name(), node["name"].as()); - EXPECT_EQ(file.DisplayName(), node["display"].as()); - EXPECT_EQ(file.Condition(), node["condition"].as()); - } + EXPECT_EQ(file.Name(), node["name"].as()); + EXPECT_EQ(file.DisplayName(), node["display"].as()); + EXPECT_EQ(file.Condition(), node["condition"].as()); +} - TEST(File, encodingAsYamlShouldOmitEmptyFields) { - File file("name1"); - YAML::Node node; - node = file; +TEST(File, encodingAsYamlShouldOmitEmptyFields) { + File file("name1"); + YAML::Node node; + node = file; - EXPECT_EQ(file.Name(), node["name"].as()); - EXPECT_FALSE(node["display"]); - EXPECT_FALSE(node["condition"]); - } + EXPECT_EQ(file.Name(), node["name"].as()); + EXPECT_FALSE(node["display"]); + EXPECT_FALSE(node["condition"]); +} - TEST(File, encodingAsYamlShouldOmitDisplayFieldIfItMatchesTheNameField) { - File file("name1", "name1"); - YAML::Node node; - node = file; +TEST(File, encodingAsYamlShouldOmitDisplayFieldIfItMatchesTheNameField) { + File file("name1", "name1"); + YAML::Node node; + node = file; - EXPECT_EQ(file.Name(), node["name"].as()); - EXPECT_FALSE(node["display"]); - EXPECT_FALSE(node["condition"]); - } + EXPECT_EQ(file.Name(), node["name"].as()); + EXPECT_FALSE(node["display"]); + EXPECT_FALSE(node["condition"]); +} - TEST(File, decodingFromYamlShouldSetDataCorrectly) { - YAML::Node node = YAML::Load("{name: name1, display: display1, condition: 'file(\"Foo.esp\")'}"); - File file = node.as(); +TEST(File, decodingFromYamlShouldSetDataCorrectly) { + YAML::Node node = YAML::Load("{name: name1, display: display1, condition: 'file(\"Foo.esp\")'}"); + File file = node.as(); - EXPECT_EQ(node["name"].as(), file.Name()); - EXPECT_EQ(node["display"].as(), file.DisplayName()); - EXPECT_EQ(node["condition"].as(), file.Condition()); - } + EXPECT_EQ(node["name"].as(), file.Name()); + EXPECT_EQ(node["display"].as(), file.DisplayName()); + EXPECT_EQ(node["condition"].as(), file.Condition()); +} - TEST(File, decodingFromYamlWithMissingConditionFieldShouldLeaveConditionStringEmpty) { - YAML::Node node = YAML::Load("{name: name1, display: display1}"); - File file = node.as(); +TEST(File, decodingFromYamlWithMissingConditionFieldShouldLeaveConditionStringEmpty) { + YAML::Node node = YAML::Load("{name: name1, display: display1}"); + File file = node.as(); - EXPECT_EQ(node["name"].as(), file.Name()); - EXPECT_EQ(node["display"].as(), file.DisplayName()); - EXPECT_TRUE(file.Condition().empty()); - } + EXPECT_EQ(node["name"].as(), file.Name()); + EXPECT_EQ(node["display"].as(), file.DisplayName()); + EXPECT_TRUE(file.Condition().empty()); +} - TEST(File, decodingFromYamlScalarShouldUseNameValueForDisplayNameAndLeaveConditionEmpty) { - YAML::Node node = YAML::Load("name1"); - File file = node.as(); +TEST(File, decodingFromYamlScalarShouldUseNameValueForDisplayNameAndLeaveConditionEmpty) { + YAML::Node node = YAML::Load("name1"); + File file = node.as(); - EXPECT_EQ(node.as(), file.Name()); - EXPECT_EQ(node.as(), file.DisplayName()); - EXPECT_TRUE(file.Condition().empty()); - } + EXPECT_EQ(node.as(), file.Name()); + EXPECT_EQ(node.as(), file.DisplayName()); + EXPECT_TRUE(file.Condition().empty()); +} - TEST(File, decodingFromYamlShouldThrowIfAnInvalidMapIsGiven) { - YAML::Node node = YAML::Load("{name: name1, condition: invalid}"); +TEST(File, decodingFromYamlShouldThrowIfAnInvalidMapIsGiven) { + YAML::Node node = YAML::Load("{name: name1, condition: invalid}"); - EXPECT_THROW(node.as(), YAML::RepresentationException); - } + EXPECT_THROW(node.as(), YAML::RepresentationException); +} - TEST(File, decodingFromYamlShouldThrowIfAListIsGiven) { - YAML::Node node = YAML::Load("[0, 1, 2]"); +TEST(File, decodingFromYamlShouldThrowIfAListIsGiven) { + YAML::Node node = YAML::Load("[0, 1, 2]"); - EXPECT_ANY_THROW(node.as()); - } - } + EXPECT_ANY_THROW(node.as()); +} +} } #endif diff --git a/src/tests/backend/metadata/location_test.h b/src/tests/backend/metadata/location_test.h index 7fc6d6e9..c03e0212 100644 --- a/src/tests/backend/metadata/location_test.h +++ b/src/tests/backend/metadata/location_test.h @@ -22,113 +22,113 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_LOCATION -#define LOOT_TEST_BACKEND_METADATA_LOCATION +#ifndef LOOT_TESTS_BACKEND_METADATA_LOCATION_TEST +#define LOOT_TESTS_BACKEND_METADATA_LOCATION_TEST #include "backend/metadata/location.h" #include namespace loot { - namespace test { - TEST(Location, defaultConstructorShouldInitialiseEmptyStrings) { - Location location; +namespace test { +TEST(Location, defaultConstructorShouldInitialiseEmptyStrings) { + Location location; - EXPECT_EQ("", location.URL()); - EXPECT_EQ("", location.Name()); - } + EXPECT_EQ("", location.URL()); + EXPECT_EQ("", location.Name()); +} - TEST(Location, stringsConstructorShouldStoreGivenStrings) { - Location location("http://www.example.com", "example"); +TEST(Location, stringsConstructorShouldStoreGivenStrings) { + Location location("http://www.example.com", "example"); - EXPECT_EQ("http://www.example.com", location.URL()); - EXPECT_EQ("example", location.Name()); - } + EXPECT_EQ("http://www.example.com", location.URL()); + EXPECT_EQ("example", location.Name()); +} - TEST(Location, locationsWithCaseInsensitiveEqualUrlsShouldBeEqual) { - Location location1("http://www.example.com", "example1"); - Location location2("HTTP://WWW.EXAMPLE.COM", "example2"); +TEST(Location, locationsWithCaseInsensitiveEqualUrlsShouldBeEqual) { + Location location1("http://www.example.com", "example1"); + Location location2("HTTP://WWW.EXAMPLE.COM", "example2"); - EXPECT_TRUE(location1 == location2); - } + EXPECT_TRUE(location1 == location2); +} - TEST(Location, locationsWithDifferentUrlsShouldBeUnequal) { - Location location1("http://www.example1.com"); - Location location2("http://www.example2.com"); +TEST(Location, locationsWithDifferentUrlsShouldBeUnequal) { + Location location1("http://www.example1.com"); + Location location2("http://www.example2.com"); - EXPECT_FALSE(location1 == location2); - } + EXPECT_FALSE(location1 == location2); +} - TEST(Location, lessThanOperatorShouldUseCaseInsensitiveLexicographicalUrlComparison) { - Location location1("http://www.example.com", "example1"); - Location location2("HTTP://WWW.EXAMPLE.COM", "example2"); +TEST(Location, lessThanOperatorShouldUseCaseInsensitiveLexicographicalUrlComparison) { + Location location1("http://www.example.com", "example1"); + Location location2("HTTP://WWW.EXAMPLE.COM", "example2"); - EXPECT_FALSE(location1 < location2); - EXPECT_FALSE(location2 < location1); + EXPECT_FALSE(location1 < location2); + EXPECT_FALSE(location2 < location1); - location1 = Location("http://www.example1.com"); - location2 = Location("http://www.example2.com"); + location1 = Location("http://www.example1.com"); + location2 = Location("http://www.example2.com"); - EXPECT_TRUE(location1 < location2); - EXPECT_FALSE(location2 < location1); - } + EXPECT_TRUE(location1 < location2); + EXPECT_FALSE(location2 < location1); +} - TEST(Location, emittingAsYamlShouldOutputAScalarIfTheNameStringIsEmpty) { - Location location("http://www.example.com"); - YAML::Emitter emitter; - emitter << location; +TEST(Location, emittingAsYamlShouldOutputAScalarIfTheNameStringIsEmpty) { + Location location("http://www.example.com"); + YAML::Emitter emitter; + emitter << location; - EXPECT_EQ("'" + location.URL() + "'", emitter.c_str()); - } + EXPECT_EQ("'" + location.URL() + "'", emitter.c_str()); +} - TEST(Location, emittingAsYamlShouldOutputAMapIfTheNameStringIsNotEmpty) { - Location location("http://www.example.com", "example"); - YAML::Emitter emitter; - emitter << location; +TEST(Location, emittingAsYamlShouldOutputAMapIfTheNameStringIsNotEmpty) { + Location location("http://www.example.com", "example"); + YAML::Emitter emitter; + emitter << location; - EXPECT_EQ("link: '" + location.URL() + "'\nname: '" + location.Name() + "'", emitter.c_str()); - } + EXPECT_EQ("link: '" + location.URL() + "'\nname: '" + location.Name() + "'", emitter.c_str()); +} - TEST(Location, encodingAsYamlShouldStoreDataCorrectly) { - Location location("http://www.example.com", "example"); - YAML::Node node; - node = location; +TEST(Location, encodingAsYamlShouldStoreDataCorrectly) { + Location location("http://www.example.com", "example"); + YAML::Node node; + node = location; - EXPECT_EQ(location.URL(), node["link"].as()); - EXPECT_EQ(location.Name(), node["name"].as()); - } + EXPECT_EQ(location.URL(), node["link"].as()); + EXPECT_EQ(location.Name(), node["name"].as()); +} - TEST(Location, encodingAsYamlShouldOmitEmptyFields) { - Location location("http://www.example.com"); - YAML::Node node; - node = location; +TEST(Location, encodingAsYamlShouldOmitEmptyFields) { + Location location("http://www.example.com"); + YAML::Node node; + node = location; - EXPECT_EQ(location.URL(), node["link"].as()); - EXPECT_FALSE(node["name"]); - } + EXPECT_EQ(location.URL(), node["link"].as()); + EXPECT_FALSE(node["name"]); +} - TEST(Location, decodingFromYamlShouldSetDataCorrectly) { - YAML::Node node = YAML::Load("{link: http://www.example.com, name: example}"); - Location location = node.as(); +TEST(Location, decodingFromYamlShouldSetDataCorrectly) { + YAML::Node node = YAML::Load("{link: http://www.example.com, name: example}"); + Location location = node.as(); - EXPECT_EQ(node["link"].as(), location.URL()); - EXPECT_EQ(node["name"].as(), location.Name()); - } + EXPECT_EQ(node["link"].as(), location.URL()); + EXPECT_EQ(node["name"].as(), location.Name()); +} - TEST(Location, decodingFromYamlScalarShouldSetUrlToScalarValueAndLeaveNameEmpty) { - YAML::Node node = YAML::Load("http://www.example.com"); - Location location = node.as(); +TEST(Location, decodingFromYamlScalarShouldSetUrlToScalarValueAndLeaveNameEmpty) { + YAML::Node node = YAML::Load("http://www.example.com"); + Location location = node.as(); - EXPECT_EQ(node.as(), location.URL()); - EXPECT_TRUE(location.Name().empty()); - } + EXPECT_EQ(node.as(), location.URL()); + EXPECT_TRUE(location.Name().empty()); +} - TEST(Location, decodingFromYamlShouldThrowIfAListIsGiven) { - YAML::Node node = YAML::Load("[0, 1, 2]"); +TEST(Location, decodingFromYamlShouldThrowIfAListIsGiven) { + YAML::Node node = YAML::Load("[0, 1, 2]"); - EXPECT_ANY_THROW(node.as()); - } - } + EXPECT_ANY_THROW(node.as()); +} +} } #endif diff --git a/src/tests/backend/metadata/message_content_test.h b/src/tests/backend/metadata/message_content_test.h index 3e4d5408..b7c1d436 100644 --- a/src/tests/backend/metadata/message_content_test.h +++ b/src/tests/backend/metadata/message_content_test.h @@ -22,95 +22,95 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_MESSAGE_CONTENT -#define LOOT_TEST_BACKEND_METADATA_MESSAGE_CONTENT +#ifndef LOOT_TESTS_BACKEND_METADATA_MESSAGE_CONTENT_TEST +#define LOOT_TESTS_BACKEND_METADATA_MESSAGE_CONTENT_TEST #include "backend/metadata/message_content.h" #include namespace loot { - namespace test { - TEST(MessageContent, defaultConstructorShouldSetEmptyEnglishLanguageString) { - MessageContent content; +namespace test { +TEST(MessageContent, defaultConstructorShouldSetEmptyEnglishLanguageString) { + MessageContent content; - EXPECT_TRUE(content.GetText().empty()); - EXPECT_EQ(Language::Code::english, content.GetLanguage()); - } + EXPECT_TRUE(content.GetText().empty()); + EXPECT_EQ(Language::Code::english, content.GetLanguage()); +} - TEST(MessageContent, contentConstructorShouldStoreGivenStringAndLanguage) { - MessageContent content("content", Language::Code::french); +TEST(MessageContent, contentConstructorShouldStoreGivenStringAndLanguage) { + MessageContent content("content", Language::Code::french); - EXPECT_EQ("content", content.GetText()); - EXPECT_EQ(Language::Code::french, content.GetLanguage()); - } + EXPECT_EQ("content", content.GetText()); + EXPECT_EQ(Language::Code::french, content.GetLanguage()); +} - TEST(MessageContent, contentShouldBeEqualIfStringsAreCaseInsensitivelyEqual) { - MessageContent content1("content", Language::Code::english); - MessageContent content2("Content", Language::Code::french); +TEST(MessageContent, contentShouldBeEqualIfStringsAreCaseInsensitivelyEqual) { + MessageContent content1("content", Language::Code::english); + MessageContent content2("Content", Language::Code::french); - EXPECT_TRUE(content1 == content2); - } + EXPECT_TRUE(content1 == content2); +} - TEST(MessageContent, contentShouldBeUnequalIfStringsAreNotCaseInsensitivelyEqual) { - MessageContent content1("content1", Language::Code::french); - MessageContent content2("content2", Language::Code::french); +TEST(MessageContent, contentShouldBeUnequalIfStringsAreNotCaseInsensitivelyEqual) { + MessageContent content1("content1", Language::Code::french); + MessageContent content2("content2", Language::Code::french); - EXPECT_FALSE(content1 == content2); - } + EXPECT_FALSE(content1 == content2); +} - TEST(MessageContent, LessThanOperatorShouldUseCaseInsensitiveLexicographicalComparison) { - MessageContent content1("content", Language::Code::english); - MessageContent content2("Content", Language::Code::french); +TEST(MessageContent, LessThanOperatorShouldUseCaseInsensitiveLexicographicalComparison) { + MessageContent content1("content", Language::Code::english); + MessageContent content2("Content", Language::Code::french); - EXPECT_FALSE(content1 < content2); - EXPECT_FALSE(content2 < content1); + EXPECT_FALSE(content1 < content2); + EXPECT_FALSE(content2 < content1); - content1 = MessageContent("content1", Language::Code::french); - content2 = MessageContent("content2", Language::Code::english); + content1 = MessageContent("content1", Language::Code::french); + content2 = MessageContent("content2", Language::Code::english); - EXPECT_TRUE(content1 < content2); - EXPECT_FALSE(content2 < content1); - } + EXPECT_TRUE(content1 < content2); + EXPECT_FALSE(content2 < content1); +} - TEST(MessageContent, emittingAsYamlShouldOutputDataCorrectly) { - MessageContent content("content", Language::Code::french); - YAML::Emitter emitter; - emitter << content; +TEST(MessageContent, emittingAsYamlShouldOutputDataCorrectly) { + MessageContent content("content", Language::Code::french); + YAML::Emitter emitter; + emitter << content; - EXPECT_EQ("lang: " + Language(content.GetLanguage()).GetLocale() + - "\nstr: '" + content.GetText() + "'", emitter.c_str()); - } + EXPECT_EQ("lang: " + Language(content.GetLanguage()).GetLocale() + + "\nstr: '" + content.GetText() + "'", emitter.c_str()); +} - TEST(MessageContent, encodingAsYamlShouldOutputDataCorrectly) { - MessageContent content("content", Language::Code::french); - YAML::Node node; - node = content; +TEST(MessageContent, encodingAsYamlShouldOutputDataCorrectly) { + MessageContent content("content", Language::Code::french); + YAML::Node node; + node = content; - EXPECT_EQ(content.GetText(), node["str"].as()); - EXPECT_EQ(Language(Language::Code::french).GetLocale(), node["lang"].as()); - } + EXPECT_EQ(content.GetText(), node["str"].as()); + EXPECT_EQ(Language(Language::Code::french).GetLocale(), node["lang"].as()); +} - TEST(MessageContent, decodingFromYamlShouldSetDataCorrectly) { - YAML::Node node = YAML::Load("{str: content, lang: de}"); - MessageContent content = node.as(); +TEST(MessageContent, decodingFromYamlShouldSetDataCorrectly) { + YAML::Node node = YAML::Load("{str: content, lang: de}"); + MessageContent content = node.as(); - EXPECT_EQ("content", content.GetText()); - EXPECT_EQ(Language::Code::german, content.GetLanguage()); - } + EXPECT_EQ("content", content.GetText()); + EXPECT_EQ(Language::Code::german, content.GetLanguage()); +} - TEST(MessageContent, decodingFromYamlScalarShouldThrow) { - YAML::Node node = YAML::Load("scalar"); +TEST(MessageContent, decodingFromYamlScalarShouldThrow) { + YAML::Node node = YAML::Load("scalar"); - EXPECT_ANY_THROW(node.as()); - } + EXPECT_ANY_THROW(node.as()); +} - TEST(MessageContent, decodingFromYamlListShouldThrow) { - YAML::Node node = YAML::Load("[0, 1, 2]"); +TEST(MessageContent, decodingFromYamlListShouldThrow) { + YAML::Node node = YAML::Load("[0, 1, 2]"); - EXPECT_ANY_THROW(node.as()); - } - } + EXPECT_ANY_THROW(node.as()); +} +} } #endif diff --git a/src/tests/backend/metadata/message_test.h b/src/tests/backend/metadata/message_test.h index 0f6bd7f6..e11aaea4 100644 --- a/src/tests/backend/metadata/message_test.h +++ b/src/tests/backend/metadata/message_test.h @@ -22,418 +22,419 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_MESSAGE -#define LOOT_TEST_BACKEND_METADATA_MESSAGE +#ifndef LOOT_TESTS_BACKEND_METADATA_MESSAGE_TEST +#define LOOT_TESTS_BACKEND_METADATA_MESSAGE_TEST + +#include "backend/metadata/message.h" #include "backend/game/game.h" -#include "backend/metadata/message.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class MessageTest : public BaseGameTest { - protected: - - typedef std::vector MessageContents; - }; - - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - MessageTest, - ::testing::Values( - GameType::tes4)); - - TEST_P(MessageTest, defaultConstructorShouldCreateNoteWithNoContent) { - Message message; - EXPECT_EQ(Message::Type::say, message.GetType()); - EXPECT_EQ(MessageContents(), message.GetContent()); - } - - TEST_P(MessageTest, scalarContentConstructorShouldCreateAMessageWithASingleContentString) { - MessageContent content = MessageContent("content1", Language::Code::english); - Message message(Message::Type::warn, content.GetText(), "condition1"); - - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ(MessageContents({content}), message.GetContent()); - EXPECT_EQ("condition1", message.Condition()); - } - - TEST_P(MessageTest, vectorContentConstructorShouldCreateAMessageWithGivenContentStrings) { - MessageContents contents({ - MessageContent("content1", Language::Code::english), - MessageContent("content2", Language::Code::french), - }); - Message message(Message::Type::error, contents, "condition1"); - - EXPECT_EQ(Message::Type::error, message.GetType()); - EXPECT_EQ(contents, message.GetContent()); - EXPECT_EQ("condition1", message.Condition()); - } - - TEST_P(MessageTest, vectorContentConstructorShouldThrowIfMultipleContentStringsAreGivenAndNoneAreEnglish) { - MessageContents contents({ - MessageContent("content1", Language::Code::german), - MessageContent("content2", Language::Code::french), - }); - EXPECT_ANY_THROW(Message(Message::Type::error, contents, "condition1")); - } - - TEST_P(MessageTest, messagesWithDifferentContentStringsShouldBeUnequal) { - Message message1(Message::Type::say, "content1", "condition1"); - Message message2(Message::Type::say, "content2", "condition1"); - - EXPECT_FALSE(message1 == message2); - } - - TEST_P(MessageTest, messagesWithEqualContentStringsShouldBeEqual) { - Message message1(Message::Type::say, MessageContents({MessageContent("content1", Language::Code::english)}), "condition1"); - Message message2(Message::Type::warn, MessageContents({MessageContent("content1", Language::Code::french)}), "condition2"); - - EXPECT_TRUE(message1 == message2); - } - - TEST_P(MessageTest, LessThanOperatorShouldUseCaseInsensitiveLexicographicalContentStringComparison) { - Message message1(Message::Type::say, MessageContents({MessageContent("content1", Language::Code::english)}), "condition1"); - Message message2(Message::Type::warn, MessageContents({MessageContent("content1", Language::Code::french)}), "condition2"); - EXPECT_FALSE(message1 < message2); - EXPECT_FALSE(message2 < message1); - - message1 = Message(Message::Type::say, "content1", "condition1"); - message2 = Message(Message::Type::say, "content2", "condition1"); - EXPECT_TRUE(message1 < message2); - EXPECT_FALSE(message2 < message1); - } - - TEST_P(MessageTest, evalConditionShouldCreateADefaultContentObjectIfNoneExists) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - - Message message; - EXPECT_TRUE(message.EvalCondition(game, Language::Code::english)); - EXPECT_EQ(MessageContents({MessageContent()}), message.GetContent()); - } - - TEST_P(MessageTest, evalConditionShouldPickOneContentStringIfMoreThanOneExists) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - - Message message(Message::Type::say, MessageContents({ - MessageContent("content1", Language::Code::german), - MessageContent("content2", Language::Code::english), - MessageContent("content3", Language::Code::french), - })); - - EXPECT_TRUE(message.EvalCondition(game, Language::Code::french)); - EXPECT_EQ(1, message.GetContent().size()); - EXPECT_EQ(MessageContent("content3", Language::Code::french), message.GetContent()[0]); - } - - TEST_P(MessageTest, chooseContentShouldCreateADefaultContentObjectIfNoneExists) { - EXPECT_EQ(MessageContent(), Message().ChooseContent(Language::Code::english)); - } - - TEST_P(MessageTest, chooseContentShouldLeaveTheContentUnchangedIfOnlyOneStringExists) { - MessageContent content("content1", Language::Code::english); - Message message(Message::Type::say, MessageContents({content})); - - EXPECT_EQ(content, message.ChooseContent(Language::Code::french)); - EXPECT_EQ(content, message.ChooseContent(Language::Code::english)); - } - - TEST_P(MessageTest, chooseContentShouldSelectTheEnglishStringIfNoStringExistsForTheGivenLanguage) { - MessageContent content("content1", Language::Code::english); - Message message(Message::Type::say, MessageContents({ - content, - MessageContent("content1", Language::Code::german), - })); - EXPECT_EQ(content, message.ChooseContent(Language::Code::french)); - } - - TEST_P(MessageTest, chooseContentShouldSelectTheStringForTheGivenLanguageIfOneExists) { - MessageContent french("content3", Language::Code::french); - Message message(Message::Type::say, MessageContents({ - MessageContent("content1", Language::Code::german), - MessageContent("content2", Language::Code::english), - french, - })); - - EXPECT_EQ(french, message.ChooseContent(Language::Code::french)); - } - - TEST_P(MessageTest, emittingAsYamlShouldOutputNoteMessageTypeCorrectly) { - Message message(Message::Type::say, "content1"); - YAML::Emitter emitter; - emitter << message; - - EXPECT_STREQ("type: say\n" - "content: 'content1'", emitter.c_str()); - } - - TEST_P(MessageTest, emittingAsYamlShouldOutputWarnMessageTypeCorrectly) { - Message message(Message::Type::warn, "content1"); - YAML::Emitter emitter; - emitter << message; - - EXPECT_STREQ("type: warn\n" - "content: 'content1'", emitter.c_str()); - } - - TEST_P(MessageTest, emittingAsYamlShouldOutputErrorMessageTypeCorrectly) { - Message message(Message::Type::error, "content1"); - YAML::Emitter emitter; - emitter << message; - - EXPECT_STREQ("type: error\n" - "content: 'content1'", emitter.c_str()); - } - - TEST_P(MessageTest, emittingAsYamlShouldOutputConditionIfItIsNotEmpty) { - Message message(Message::Type::say, "content1", "condition1"); - YAML::Emitter emitter; - emitter << message; - - EXPECT_STREQ("type: say\n" - "content: 'content1'\n" - "condition: 'condition1'", emitter.c_str()); - } - - TEST_P(MessageTest, emittingAsYamlShouldOutputMultipleContentStringsAsAList) { - Message message(Message::Type::say, MessageContents({ - MessageContent("content1", Language::Code::english), - MessageContent("content2", Language::Code::german) - })); - YAML::Emitter emitter; - emitter << message; - - EXPECT_STREQ("type: say\n" - "content:\n" - " - lang: en\n" - " str: 'content1'\n" - " - lang: de\n" - " str: 'content2'", emitter.c_str()); - } - - TEST_P(MessageTest, encodingAsYamlShouldStoreNoteMessageTypeCorrectly) { - Message message(Message::Type::say, "content1"); - YAML::Node node; - node = message; - - EXPECT_EQ("say", node["type"].as()); - } - - TEST_P(MessageTest, encodingAsYamlShouldStoreWarningMessageTypeCorrectly) { - Message message(Message::Type::warn, "content1"); - YAML::Node node; - node = message; - - EXPECT_EQ("warn", node["type"].as()); - } - - TEST_P(MessageTest, encodingAsYamlShouldStoreErrorMessageTypeCorrectly) { - Message message(Message::Type::error, "content1"); - YAML::Node node; - node = message; - - EXPECT_EQ("error", node["type"].as()); - } - - TEST_P(MessageTest, encodingAsYamlShouldOmitConditionFieldIfItIsEmpty) { - Message message(Message::Type::say, "content1"); - YAML::Node node; - node = message; - - EXPECT_FALSE(node["condition"]); - } - - TEST_P(MessageTest, encodingAsYamlShouldStoreConditionFieldIfItIsNotEmpty) { - Message message(Message::Type::say, "content1", "condition1"); - YAML::Node node; - node = message; - - EXPECT_EQ("condition1", node["condition"].as()); - } - - TEST_P(MessageTest, encodingAsYamlShouldStoreASingleContentStringInAVector) { - Message message(Message::Type::say, "content1"); - YAML::Node node; - node = message; - - EXPECT_EQ(message.GetContent(), node["content"].as()); - } - - TEST_P(MessageTest, encodingAsYamlShouldMultipleContentStringsInAVector) { - MessageContents contents({ - MessageContent("content1", Language::Code::english), - MessageContent("content2", Language::Code::french), - }); - Message message(Message::Type::say, contents); - YAML::Node node; - node = message; - - EXPECT_EQ(contents, node["content"].as()); - } - - TEST_P(MessageTest, decodingFromYamlShouldSetNoteTypeCorrectly) { - YAML::Node node = YAML::Load("type: say\n" - "content: content1"); - Message message = node.as(); - - EXPECT_EQ(Message::Type::say, message.GetType()); - } - - TEST_P(MessageTest, decodingFromYamlShouldSetWarningTypeCorrectly) { - YAML::Node node = YAML::Load("type: warn\n" - "content: content1"); - Message message = node.as(); - - EXPECT_EQ(Message::Type::warn, message.GetType()); - } - - TEST_P(MessageTest, decodingFromYamlShouldSetErrorTypeCorrectly) { - YAML::Node node = YAML::Load("type: error\n" - "content: content1"); - Message message = node.as(); - - EXPECT_EQ(Message::Type::error, message.GetType()); - } - - TEST_P(MessageTest, decodingFromYamlShouldHandleAnUnrecognisedTypeAsANote) { - YAML::Node node = YAML::Load("type: invalid\n" - "content: content1"); - Message message = node.as(); - - EXPECT_EQ(Message::Type::say, message.GetType()); - } - - TEST_P(MessageTest, decodingFromYamlShouldLeaveTheConditionEmptyIfNoneIsPresent) { - YAML::Node node = YAML::Load("type: say\n" - "content: content1"); - Message message = node.as(); - - EXPECT_TRUE(message.Condition().empty()); - } - - TEST_P(MessageTest, decodingFromYamlShouldStoreANonEmptyConditionField) { - YAML::Node node = YAML::Load("type: say\n" - "content: content1\n" - "condition: 'file(\"Foo.esp\")'"); - Message message = node.as(); - - EXPECT_EQ("file(\"Foo.esp\")", message.Condition()); - } - - TEST_P(MessageTest, decodingFromYamlShouldStoreAScalarContentValueCorrectly) { - YAML::Node node = YAML::Load("type: say\n" - "content: content1\n"); - Message message = node.as(); - MessageContents expectedContent({MessageContent("content1", Language::Code::english)}); - - EXPECT_EQ(expectedContent, message.GetContent()); - } - - TEST_P(MessageTest, decodingFromYamlShouldStoreAListOfContentStringsCorrectly) { - YAML::Node node = YAML::Load("type: say\n" - "content:\n" - " - lang: en\n" - " str: content1\n" - " - lang: de\n" - " str: content2"); - Message message = node.as(); - - EXPECT_EQ(MessageContents({ - MessageContent("content1", Language::Code::english), - MessageContent("content2", Language::Code::german), - }), message.GetContent()); - } - - TEST_P(MessageTest, decodingFromYamlShouldNotThrowIfTheOnlyContentStringIsNotEnglish) { - YAML::Node node = YAML::Load("type: say\n" - "content:\n" - " - lang: fr\n" - " str: content1"); - - EXPECT_NO_THROW(Message message = node.as()); - } - - TEST_P(MessageTest, decodingFromYamlShouldThrowIfMultipleContentStringsAreGivenAndNoneAreEnglish) { - YAML::Node node = YAML::Load("type: say\n" - "content:\n" - " - lang: de\n" - " str: content1\n" - " - lang: fr\n" - " str: content2"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - - TEST_P(MessageTest, decodingFromYamlShouldApplySubstitutionsWhenThereIsOnlyOneContentString) { - YAML::Node node = YAML::Load("type: say\n" - "content: con%1%tent1\n" - "subs:\n" - " - sub1"); - Message message = node.as(); - - EXPECT_EQ(MessageContents({MessageContent("consub1tent1", Language::Code::english)}), message.GetContent()); - } - - TEST_P(MessageTest, decodingFromYamlShouldApplySubstitutionsToAllContentStrings) { - YAML::Node node = YAML::Load("type: say\n" - "content:\n" - " - lang: en\n" - " str: content1 %1%\n" - " - lang: de\n" - " str: content2 %1%\n" - "subs:\n" - " - sub"); - Message message = node.as(); - - EXPECT_EQ(MessageContents({ - MessageContent("content1 sub", Language::Code::english), - MessageContent("content2 sub", Language::Code::german), - }), message.GetContent()); - } - - TEST_P(MessageTest, decodingFromYamlShouldThrowIfTheContentStringExpectsMoreSubstitutionsThanExist) { - YAML::Node node = YAML::Load("type: say\n" - "content: '%1% %2%'\n" - "subs:\n" - " - sub1"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - - // Don't throw because no subs are given, so none are expected in the content string. - TEST_P(MessageTest, decodingFromYamlShouldIgnoreSubstitutionSyntaxIfNoSubstitutionsExist) { - YAML::Node node = YAML::Load("type: say\n" - "content: con%1%tent1\n"); - Message message = node.as(); - - EXPECT_EQ(MessageContents({MessageContent("con%1%tent1", Language::Code::english)}), message.GetContent()); - } - - TEST_P(MessageTest, decodingFromYamlShouldThrowIfAnInvalidConditionIsGiven) { - YAML::Node node = YAML::Load("type: say\n" - "content: content1\n" - "condition: invalid"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - - TEST_P(MessageTest, decodingFromYamlShouldThrowIfAScalarIsGiven) { - YAML::Node node = YAML::Load("scalar"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - - TEST_P(MessageTest, decodingFromYamlShouldThrowIfAListIsGiven) { - YAML::Node node = YAML::Load("[0, 1, 2]"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - } +namespace test { +class MessageTest : public BaseGameTest { +protected: + + typedef std::vector MessageContents; +}; + +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + MessageTest, + ::testing::Values( + GameType::tes4)); + +TEST_P(MessageTest, defaultConstructorShouldCreateNoteWithNoContent) { + Message message; + EXPECT_EQ(Message::Type::say, message.GetType()); + EXPECT_EQ(MessageContents(), message.GetContent()); +} + +TEST_P(MessageTest, scalarContentConstructorShouldCreateAMessageWithASingleContentString) { + MessageContent content = MessageContent("content1", Language::Code::english); + Message message(Message::Type::warn, content.GetText(), "condition1"); + + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ(MessageContents({content}), message.GetContent()); + EXPECT_EQ("condition1", message.Condition()); +} + +TEST_P(MessageTest, vectorContentConstructorShouldCreateAMessageWithGivenContentStrings) { + MessageContents contents({ + MessageContent("content1", Language::Code::english), + MessageContent("content2", Language::Code::french), + }); + Message message(Message::Type::error, contents, "condition1"); + + EXPECT_EQ(Message::Type::error, message.GetType()); + EXPECT_EQ(contents, message.GetContent()); + EXPECT_EQ("condition1", message.Condition()); +} + +TEST_P(MessageTest, vectorContentConstructorShouldThrowIfMultipleContentStringsAreGivenAndNoneAreEnglish) { + MessageContents contents({ + MessageContent("content1", Language::Code::german), + MessageContent("content2", Language::Code::french), + }); + EXPECT_ANY_THROW(Message(Message::Type::error, contents, "condition1")); +} + +TEST_P(MessageTest, messagesWithDifferentContentStringsShouldBeUnequal) { + Message message1(Message::Type::say, "content1", "condition1"); + Message message2(Message::Type::say, "content2", "condition1"); + + EXPECT_FALSE(message1 == message2); +} + +TEST_P(MessageTest, messagesWithEqualContentStringsShouldBeEqual) { + Message message1(Message::Type::say, MessageContents({MessageContent("content1", Language::Code::english)}), "condition1"); + Message message2(Message::Type::warn, MessageContents({MessageContent("content1", Language::Code::french)}), "condition2"); + + EXPECT_TRUE(message1 == message2); +} + +TEST_P(MessageTest, LessThanOperatorShouldUseCaseInsensitiveLexicographicalContentStringComparison) { + Message message1(Message::Type::say, MessageContents({MessageContent("content1", Language::Code::english)}), "condition1"); + Message message2(Message::Type::warn, MessageContents({MessageContent("content1", Language::Code::french)}), "condition2"); + EXPECT_FALSE(message1 < message2); + EXPECT_FALSE(message2 < message1); + + message1 = Message(Message::Type::say, "content1", "condition1"); + message2 = Message(Message::Type::say, "content2", "condition1"); + EXPECT_TRUE(message1 < message2); + EXPECT_FALSE(message2 < message1); +} + +TEST_P(MessageTest, evalConditionShouldCreateADefaultContentObjectIfNoneExists) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + + Message message; + EXPECT_TRUE(message.EvalCondition(game, Language::Code::english)); + EXPECT_EQ(MessageContents({MessageContent()}), message.GetContent()); +} + +TEST_P(MessageTest, evalConditionShouldPickOneContentStringIfMoreThanOneExists) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + + Message message(Message::Type::say, MessageContents({ + MessageContent("content1", Language::Code::german), + MessageContent("content2", Language::Code::english), + MessageContent("content3", Language::Code::french), + })); + + EXPECT_TRUE(message.EvalCondition(game, Language::Code::french)); + EXPECT_EQ(1, message.GetContent().size()); + EXPECT_EQ(MessageContent("content3", Language::Code::french), message.GetContent()[0]); +} + +TEST_P(MessageTest, chooseContentShouldCreateADefaultContentObjectIfNoneExists) { + EXPECT_EQ(MessageContent(), Message().ChooseContent(Language::Code::english)); +} + +TEST_P(MessageTest, chooseContentShouldLeaveTheContentUnchangedIfOnlyOneStringExists) { + MessageContent content("content1", Language::Code::english); + Message message(Message::Type::say, MessageContents({content})); + + EXPECT_EQ(content, message.ChooseContent(Language::Code::french)); + EXPECT_EQ(content, message.ChooseContent(Language::Code::english)); +} + +TEST_P(MessageTest, chooseContentShouldSelectTheEnglishStringIfNoStringExistsForTheGivenLanguage) { + MessageContent content("content1", Language::Code::english); + Message message(Message::Type::say, MessageContents({ + content, + MessageContent("content1", Language::Code::german), + })); + EXPECT_EQ(content, message.ChooseContent(Language::Code::french)); +} + +TEST_P(MessageTest, chooseContentShouldSelectTheStringForTheGivenLanguageIfOneExists) { + MessageContent french("content3", Language::Code::french); + Message message(Message::Type::say, MessageContents({ + MessageContent("content1", Language::Code::german), + MessageContent("content2", Language::Code::english), + french, + })); + + EXPECT_EQ(french, message.ChooseContent(Language::Code::french)); +} + +TEST_P(MessageTest, emittingAsYamlShouldOutputNoteMessageTypeCorrectly) { + Message message(Message::Type::say, "content1"); + YAML::Emitter emitter; + emitter << message; + + EXPECT_STREQ("type: say\n" + "content: 'content1'", emitter.c_str()); +} + +TEST_P(MessageTest, emittingAsYamlShouldOutputWarnMessageTypeCorrectly) { + Message message(Message::Type::warn, "content1"); + YAML::Emitter emitter; + emitter << message; + + EXPECT_STREQ("type: warn\n" + "content: 'content1'", emitter.c_str()); +} + +TEST_P(MessageTest, emittingAsYamlShouldOutputErrorMessageTypeCorrectly) { + Message message(Message::Type::error, "content1"); + YAML::Emitter emitter; + emitter << message; + + EXPECT_STREQ("type: error\n" + "content: 'content1'", emitter.c_str()); +} + +TEST_P(MessageTest, emittingAsYamlShouldOutputConditionIfItIsNotEmpty) { + Message message(Message::Type::say, "content1", "condition1"); + YAML::Emitter emitter; + emitter << message; + + EXPECT_STREQ("type: say\n" + "content: 'content1'\n" + "condition: 'condition1'", emitter.c_str()); +} + +TEST_P(MessageTest, emittingAsYamlShouldOutputMultipleContentStringsAsAList) { + Message message(Message::Type::say, MessageContents({ + MessageContent("content1", Language::Code::english), + MessageContent("content2", Language::Code::german) + })); + YAML::Emitter emitter; + emitter << message; + + EXPECT_STREQ("type: say\n" + "content:\n" + " - lang: en\n" + " str: 'content1'\n" + " - lang: de\n" + " str: 'content2'", emitter.c_str()); +} + +TEST_P(MessageTest, encodingAsYamlShouldStoreNoteMessageTypeCorrectly) { + Message message(Message::Type::say, "content1"); + YAML::Node node; + node = message; + + EXPECT_EQ("say", node["type"].as()); +} + +TEST_P(MessageTest, encodingAsYamlShouldStoreWarningMessageTypeCorrectly) { + Message message(Message::Type::warn, "content1"); + YAML::Node node; + node = message; + + EXPECT_EQ("warn", node["type"].as()); +} + +TEST_P(MessageTest, encodingAsYamlShouldStoreErrorMessageTypeCorrectly) { + Message message(Message::Type::error, "content1"); + YAML::Node node; + node = message; + + EXPECT_EQ("error", node["type"].as()); +} + +TEST_P(MessageTest, encodingAsYamlShouldOmitConditionFieldIfItIsEmpty) { + Message message(Message::Type::say, "content1"); + YAML::Node node; + node = message; + + EXPECT_FALSE(node["condition"]); +} + +TEST_P(MessageTest, encodingAsYamlShouldStoreConditionFieldIfItIsNotEmpty) { + Message message(Message::Type::say, "content1", "condition1"); + YAML::Node node; + node = message; + + EXPECT_EQ("condition1", node["condition"].as()); +} + +TEST_P(MessageTest, encodingAsYamlShouldStoreASingleContentStringInAVector) { + Message message(Message::Type::say, "content1"); + YAML::Node node; + node = message; + + EXPECT_EQ(message.GetContent(), node["content"].as()); +} + +TEST_P(MessageTest, encodingAsYamlShouldMultipleContentStringsInAVector) { + MessageContents contents({ + MessageContent("content1", Language::Code::english), + MessageContent("content2", Language::Code::french), + }); + Message message(Message::Type::say, contents); + YAML::Node node; + node = message; + + EXPECT_EQ(contents, node["content"].as()); +} + +TEST_P(MessageTest, decodingFromYamlShouldSetNoteTypeCorrectly) { + YAML::Node node = YAML::Load("type: say\n" + "content: content1"); + Message message = node.as(); + + EXPECT_EQ(Message::Type::say, message.GetType()); +} + +TEST_P(MessageTest, decodingFromYamlShouldSetWarningTypeCorrectly) { + YAML::Node node = YAML::Load("type: warn\n" + "content: content1"); + Message message = node.as(); + + EXPECT_EQ(Message::Type::warn, message.GetType()); +} + +TEST_P(MessageTest, decodingFromYamlShouldSetErrorTypeCorrectly) { + YAML::Node node = YAML::Load("type: error\n" + "content: content1"); + Message message = node.as(); + + EXPECT_EQ(Message::Type::error, message.GetType()); +} + +TEST_P(MessageTest, decodingFromYamlShouldHandleAnUnrecognisedTypeAsANote) { + YAML::Node node = YAML::Load("type: invalid\n" + "content: content1"); + Message message = node.as(); + + EXPECT_EQ(Message::Type::say, message.GetType()); +} + +TEST_P(MessageTest, decodingFromYamlShouldLeaveTheConditionEmptyIfNoneIsPresent) { + YAML::Node node = YAML::Load("type: say\n" + "content: content1"); + Message message = node.as(); + + EXPECT_TRUE(message.Condition().empty()); +} + +TEST_P(MessageTest, decodingFromYamlShouldStoreANonEmptyConditionField) { + YAML::Node node = YAML::Load("type: say\n" + "content: content1\n" + "condition: 'file(\"Foo.esp\")'"); + Message message = node.as(); + + EXPECT_EQ("file(\"Foo.esp\")", message.Condition()); +} + +TEST_P(MessageTest, decodingFromYamlShouldStoreAScalarContentValueCorrectly) { + YAML::Node node = YAML::Load("type: say\n" + "content: content1\n"); + Message message = node.as(); + MessageContents expectedContent({MessageContent("content1", Language::Code::english)}); + + EXPECT_EQ(expectedContent, message.GetContent()); +} + +TEST_P(MessageTest, decodingFromYamlShouldStoreAListOfContentStringsCorrectly) { + YAML::Node node = YAML::Load("type: say\n" + "content:\n" + " - lang: en\n" + " str: content1\n" + " - lang: de\n" + " str: content2"); + Message message = node.as(); + + EXPECT_EQ(MessageContents({ + MessageContent("content1", Language::Code::english), + MessageContent("content2", Language::Code::german), + }), message.GetContent()); +} + +TEST_P(MessageTest, decodingFromYamlShouldNotThrowIfTheOnlyContentStringIsNotEnglish) { + YAML::Node node = YAML::Load("type: say\n" + "content:\n" + " - lang: fr\n" + " str: content1"); + + EXPECT_NO_THROW(Message message = node.as()); +} + +TEST_P(MessageTest, decodingFromYamlShouldThrowIfMultipleContentStringsAreGivenAndNoneAreEnglish) { + YAML::Node node = YAML::Load("type: say\n" + "content:\n" + " - lang: de\n" + " str: content1\n" + " - lang: fr\n" + " str: content2"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} + +TEST_P(MessageTest, decodingFromYamlShouldApplySubstitutionsWhenThereIsOnlyOneContentString) { + YAML::Node node = YAML::Load("type: say\n" + "content: con%1%tent1\n" + "subs:\n" + " - sub1"); + Message message = node.as(); + + EXPECT_EQ(MessageContents({MessageContent("consub1tent1", Language::Code::english)}), message.GetContent()); +} + +TEST_P(MessageTest, decodingFromYamlShouldApplySubstitutionsToAllContentStrings) { + YAML::Node node = YAML::Load("type: say\n" + "content:\n" + " - lang: en\n" + " str: content1 %1%\n" + " - lang: de\n" + " str: content2 %1%\n" + "subs:\n" + " - sub"); + Message message = node.as(); + + EXPECT_EQ(MessageContents({ + MessageContent("content1 sub", Language::Code::english), + MessageContent("content2 sub", Language::Code::german), + }), message.GetContent()); +} + +TEST_P(MessageTest, decodingFromYamlShouldThrowIfTheContentStringExpectsMoreSubstitutionsThanExist) { + YAML::Node node = YAML::Load("type: say\n" + "content: '%1% %2%'\n" + "subs:\n" + " - sub1"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} + +// Don't throw because no subs are given, so none are expected in the content string. +TEST_P(MessageTest, decodingFromYamlShouldIgnoreSubstitutionSyntaxIfNoSubstitutionsExist) { + YAML::Node node = YAML::Load("type: say\n" + "content: con%1%tent1\n"); + Message message = node.as(); + + EXPECT_EQ(MessageContents({MessageContent("con%1%tent1", Language::Code::english)}), message.GetContent()); +} + +TEST_P(MessageTest, decodingFromYamlShouldThrowIfAnInvalidConditionIsGiven) { + YAML::Node node = YAML::Load("type: say\n" + "content: content1\n" + "condition: invalid"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} + +TEST_P(MessageTest, decodingFromYamlShouldThrowIfAScalarIsGiven) { + YAML::Node node = YAML::Load("scalar"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} + +TEST_P(MessageTest, decodingFromYamlShouldThrowIfAListIsGiven) { + YAML::Node node = YAML::Load("[0, 1, 2]"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} +} } #endif diff --git a/src/tests/backend/metadata/plugin_dirty_info_test.h b/src/tests/backend/metadata/plugin_dirty_info_test.h index d2150e1e..bc508308 100644 --- a/src/tests/backend/metadata/plugin_dirty_info_test.h +++ b/src/tests/backend/metadata/plugin_dirty_info_test.h @@ -22,195 +22,196 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_PLUGIN_DIRTY_INFO -#define LOOT_TEST_BACKEND_METADATA_PLUGIN_DIRTY_INFO +#ifndef LOOT_TESTS_BACKEND_METADATA_PLUGIN_DIRTY_INFO_TEST +#define LOOT_TESTS_BACKEND_METADATA_PLUGIN_DIRTY_INFO_TEST #include "backend/metadata/plugin_dirty_info.h" + #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class PluginDirtyInfoTest : public BaseGameTest {}; +namespace test { +class PluginDirtyInfoTest : public BaseGameTest {}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - PluginDirtyInfoTest, - ::testing::Values( - GameType::tes4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + PluginDirtyInfoTest, + ::testing::Values( + GameType::tes4)); - TEST_P(PluginDirtyInfoTest, defaultConstructorShouldLeaveAllCountsAtZeroAndTheUtilityStringEmpty) { - PluginDirtyInfo info; - EXPECT_EQ(0, info.CRC()); - EXPECT_EQ(0, info.ITMs()); - EXPECT_EQ(0, info.DeletedRefs()); - EXPECT_EQ(0, info.DeletedNavmeshes()); - EXPECT_TRUE(info.CleaningUtility().empty()); - } +TEST_P(PluginDirtyInfoTest, defaultConstructorShouldLeaveAllCountsAtZeroAndTheUtilityStringEmpty) { + PluginDirtyInfo info; + EXPECT_EQ(0, info.CRC()); + EXPECT_EQ(0, info.ITMs()); + EXPECT_EQ(0, info.DeletedRefs()); + EXPECT_EQ(0, info.DeletedNavmeshes()); + EXPECT_TRUE(info.CleaningUtility().empty()); +} - TEST_P(PluginDirtyInfoTest, contentConstructorShouldStoreAllGivenData) { - PluginDirtyInfo info(0x12345678, 2, 10, 30, "cleaner"); - EXPECT_EQ(0x12345678, info.CRC()); - EXPECT_EQ(2, info.ITMs()); - EXPECT_EQ(10, info.DeletedRefs()); - EXPECT_EQ(30, info.DeletedNavmeshes()); - EXPECT_EQ("cleaner", info.CleaningUtility()); - } +TEST_P(PluginDirtyInfoTest, contentConstructorShouldStoreAllGivenData) { + PluginDirtyInfo info(0x12345678, 2, 10, 30, "cleaner"); + EXPECT_EQ(0x12345678, info.CRC()); + EXPECT_EQ(2, info.ITMs()); + EXPECT_EQ(10, info.DeletedRefs()); + EXPECT_EQ(30, info.DeletedNavmeshes()); + EXPECT_EQ("cleaner", info.CleaningUtility()); +} - TEST_P(PluginDirtyInfoTest, asMessageShouldOutputAllNonZeroCounts) { - Message message = PluginDirtyInfo(0x12345678, 2, 10, 30, "cleaner").AsMessage(); - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 2 ITM records, 10 deleted references and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); +TEST_P(PluginDirtyInfoTest, asMessageShouldOutputAllNonZeroCounts) { + Message message = PluginDirtyInfo(0x12345678, 2, 10, 30, "cleaner").AsMessage(); + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ("Contains 2 ITM records, 10 deleted references and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); - message = PluginDirtyInfo(0x12345678, 0, 0, 0, "cleaner").AsMessage(); - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + message = PluginDirtyInfo(0x12345678, 0, 0, 0, "cleaner").AsMessage(); + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ("Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); - message = PluginDirtyInfo(0x12345678, 0, 10, 30, "cleaner").AsMessage(); - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 10 deleted references and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + message = PluginDirtyInfo(0x12345678, 0, 10, 30, "cleaner").AsMessage(); + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ("Contains 10 deleted references and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); - message = PluginDirtyInfo(0x12345678, 0, 0, 30, "cleaner").AsMessage(); - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + message = PluginDirtyInfo(0x12345678, 0, 0, 30, "cleaner").AsMessage(); + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ("Contains 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); - message = PluginDirtyInfo(0x12345678, 0, 10, 0, "cleaner").AsMessage(); - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 10 deleted references. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + message = PluginDirtyInfo(0x12345678, 0, 10, 0, "cleaner").AsMessage(); + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ("Contains 10 deleted references. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); - message = PluginDirtyInfo(0x12345678, 2, 0, 30, "cleaner").AsMessage(); - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 2 ITM records and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + message = PluginDirtyInfo(0x12345678, 2, 0, 30, "cleaner").AsMessage(); + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ("Contains 2 ITM records and 30 deleted navmeshes. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); - message = PluginDirtyInfo(0x12345678, 2, 0, 0, "cleaner").AsMessage(); - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 2 ITM records. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); + message = PluginDirtyInfo(0x12345678, 2, 0, 0, "cleaner").AsMessage(); + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ("Contains 2 ITM records. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); - message = PluginDirtyInfo(0x12345678, 2, 10, 0, "cleaner").AsMessage(); - EXPECT_EQ(Message::Type::warn, message.GetType()); - EXPECT_EQ("Contains 2 ITM records and 10 deleted references. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); - } + message = PluginDirtyInfo(0x12345678, 2, 10, 0, "cleaner").AsMessage(); + EXPECT_EQ(Message::Type::warn, message.GetType()); + EXPECT_EQ("Contains 2 ITM records and 10 deleted references. Clean with cleaner.", message.ChooseContent(Language::Code::english).GetText()); +} - TEST_P(PluginDirtyInfoTest, dirtyInfoShouldBeEqualIfCrcValuesAreEqual) { - PluginDirtyInfo info1(0x12345678, 2, 10, 30, "cleaner1"); - PluginDirtyInfo info2(0x12345678, 4, 20, 60, "cleaner2"); - EXPECT_TRUE(info1 == info2); +TEST_P(PluginDirtyInfoTest, dirtyInfoShouldBeEqualIfCrcValuesAreEqual) { + PluginDirtyInfo info1(0x12345678, 2, 10, 30, "cleaner1"); + PluginDirtyInfo info2(0x12345678, 4, 20, 60, "cleaner2"); + EXPECT_TRUE(info1 == info2); - info1 = PluginDirtyInfo(0x12345678, 2, 10, 30, "cleaner"); - info2 = PluginDirtyInfo(0x87654321, 2, 10, 30, "cleaner"); - EXPECT_FALSE(info1 == info2); - } + info1 = PluginDirtyInfo(0x12345678, 2, 10, 30, "cleaner"); + info2 = PluginDirtyInfo(0x87654321, 2, 10, 30, "cleaner"); + EXPECT_FALSE(info1 == info2); +} - TEST_P(PluginDirtyInfoTest, LessThanOperatorShouldCompareCrcValues) { - PluginDirtyInfo info1(0x12345678, 2, 10, 30, "cleaner1"); - PluginDirtyInfo info2(0x12345678, 4, 20, 60, "cleaner2"); - EXPECT_FALSE(info1 < info2); - EXPECT_FALSE(info2 < info1); +TEST_P(PluginDirtyInfoTest, LessThanOperatorShouldCompareCrcValues) { + PluginDirtyInfo info1(0x12345678, 2, 10, 30, "cleaner1"); + PluginDirtyInfo info2(0x12345678, 4, 20, 60, "cleaner2"); + EXPECT_FALSE(info1 < info2); + EXPECT_FALSE(info2 < info1); - info1 = PluginDirtyInfo(0x12345678, 2, 10, 30, "cleaner"); - info2 = PluginDirtyInfo(0x87654321, 2, 10, 30, "cleaner"); - EXPECT_TRUE(info1 < info2); - EXPECT_FALSE(info2 < info1); - } + info1 = PluginDirtyInfo(0x12345678, 2, 10, 30, "cleaner"); + info2 = PluginDirtyInfo(0x87654321, 2, 10, 30, "cleaner"); + EXPECT_TRUE(info1 < info2); + EXPECT_FALSE(info2 < info1); +} - TEST_P(PluginDirtyInfoTest, evalConditionShouldBeTrueIfTheCrcGivenMatchesTheRealPluginCrc) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(PluginDirtyInfoTest, evalConditionShouldBeTrueIfTheCrcGivenMatchesTheRealPluginCrc) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - PluginDirtyInfo dirtyInfo(blankEsmCrc, 2, 10, 30, "cleaner"); - EXPECT_TRUE(dirtyInfo.EvalCondition(game, blankEsm)); - } + PluginDirtyInfo dirtyInfo(blankEsmCrc, 2, 10, 30, "cleaner"); + EXPECT_TRUE(dirtyInfo.EvalCondition(game, blankEsm)); +} - TEST_P(PluginDirtyInfoTest, evalConditionShouldBeFalseIfTheCrcGivenDoesNotMatchTheRealPluginCrc) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(PluginDirtyInfoTest, evalConditionShouldBeFalseIfTheCrcGivenDoesNotMatchTheRealPluginCrc) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - PluginDirtyInfo dirtyInfo(0xDEADBEEF, 2, 10, 30, "cleaner"); - EXPECT_FALSE(dirtyInfo.EvalCondition(game, blankEsm)); - } + PluginDirtyInfo dirtyInfo(0xDEADBEEF, 2, 10, 30, "cleaner"); + EXPECT_FALSE(dirtyInfo.EvalCondition(game, blankEsm)); +} - TEST_P(PluginDirtyInfoTest, evalConditionShouldBeFalseIfAnEmptyPluginFilenameIsGiven) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); +TEST_P(PluginDirtyInfoTest, evalConditionShouldBeFalseIfAnEmptyPluginFilenameIsGiven) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); - PluginDirtyInfo dirtyInfo; - EXPECT_FALSE(dirtyInfo.EvalCondition(game, "")); - } + PluginDirtyInfo dirtyInfo; + EXPECT_FALSE(dirtyInfo.EvalCondition(game, "")); +} - TEST_P(PluginDirtyInfoTest, emittingAsYamlShouldOutputAllNonZeroCounts) { - PluginDirtyInfo info(0x12345678, 2, 10, 30, "cleaner"); - YAML::Emitter emitter; - emitter << info; +TEST_P(PluginDirtyInfoTest, emittingAsYamlShouldOutputAllNonZeroCounts) { + PluginDirtyInfo info(0x12345678, 2, 10, 30, "cleaner"); + YAML::Emitter emitter; + emitter << info; - EXPECT_STREQ("crc: 0x12345678\nutil: 'cleaner'\nitm: 2\nudr: 10\nnav: 30", emitter.c_str()); - } + EXPECT_STREQ("crc: 0x12345678\nutil: 'cleaner'\nitm: 2\nudr: 10\nnav: 30", emitter.c_str()); +} - TEST_P(PluginDirtyInfoTest, emittingAsYamlShouldOmitAllZeroCounts) { - PluginDirtyInfo info(0x12345678, 0, 0, 0, "cleaner"); - YAML::Emitter emitter; - emitter << info; +TEST_P(PluginDirtyInfoTest, emittingAsYamlShouldOmitAllZeroCounts) { + PluginDirtyInfo info(0x12345678, 0, 0, 0, "cleaner"); + YAML::Emitter emitter; + emitter << info; - EXPECT_STREQ("crc: 0x12345678\nutil: 'cleaner'", emitter.c_str()); - } + EXPECT_STREQ("crc: 0x12345678\nutil: 'cleaner'", emitter.c_str()); +} - TEST_P(PluginDirtyInfoTest, encodingAsYamlShouldOmitAllZeroCountFields) { - PluginDirtyInfo info(0x12345678, 0, 0, 0, "cleaner"); - YAML::Node node; - node = info; +TEST_P(PluginDirtyInfoTest, encodingAsYamlShouldOmitAllZeroCountFields) { + PluginDirtyInfo info(0x12345678, 0, 0, 0, "cleaner"); + YAML::Node node; + node = info; - EXPECT_EQ(0x12345678, node["crc"].as()); - EXPECT_EQ("cleaner", node["util"].as()); - EXPECT_FALSE(node["itm"]); - EXPECT_FALSE(node["udr"]); - EXPECT_FALSE(node["nav"]); - } + EXPECT_EQ(0x12345678, node["crc"].as()); + EXPECT_EQ("cleaner", node["util"].as()); + EXPECT_FALSE(node["itm"]); + EXPECT_FALSE(node["udr"]); + EXPECT_FALSE(node["nav"]); +} - TEST_P(PluginDirtyInfoTest, encodingAsYamlShouldOutputAllNonZeroCountFields) { - PluginDirtyInfo info(0x12345678, 2, 10, 30, "cleaner"); - YAML::Node node; - node = info; +TEST_P(PluginDirtyInfoTest, encodingAsYamlShouldOutputAllNonZeroCountFields) { + PluginDirtyInfo info(0x12345678, 2, 10, 30, "cleaner"); + YAML::Node node; + node = info; - EXPECT_EQ(0x12345678, node["crc"].as()); - EXPECT_EQ("cleaner", node["util"].as()); - EXPECT_EQ(2, node["itm"].as()); - EXPECT_EQ(10, node["udr"].as()); - EXPECT_EQ(30, node["nav"].as()); - } + EXPECT_EQ(0x12345678, node["crc"].as()); + EXPECT_EQ("cleaner", node["util"].as()); + EXPECT_EQ(2, node["itm"].as()); + EXPECT_EQ(10, node["udr"].as()); + EXPECT_EQ(30, node["nav"].as()); +} - TEST_P(PluginDirtyInfoTest, decodingFromYamlShouldLeaveMissingFieldsWithZeroValues) { - YAML::Node node = YAML::Load("{crc: 0x12345678, util: cleaner}"); - PluginDirtyInfo info = node.as(); +TEST_P(PluginDirtyInfoTest, decodingFromYamlShouldLeaveMissingFieldsWithZeroValues) { + YAML::Node node = YAML::Load("{crc: 0x12345678, util: cleaner}"); + PluginDirtyInfo info = node.as(); - EXPECT_EQ(0x12345678, info.CRC()); - EXPECT_EQ(0, info.ITMs()); - EXPECT_EQ(0, info.DeletedRefs()); - EXPECT_EQ(0, info.DeletedNavmeshes()); - EXPECT_EQ("cleaner", info.CleaningUtility()); - } + EXPECT_EQ(0x12345678, info.CRC()); + EXPECT_EQ(0, info.ITMs()); + EXPECT_EQ(0, info.DeletedRefs()); + EXPECT_EQ(0, info.DeletedNavmeshes()); + EXPECT_EQ("cleaner", info.CleaningUtility()); +} - TEST_P(PluginDirtyInfoTest, decodingFromYamlShouldStoreAllNonZeroCounts) { - YAML::Node node = YAML::Load("{crc: 0x12345678, util: cleaner, itm: 2, udr: 10, nav: 30}"); - PluginDirtyInfo info = node.as(); +TEST_P(PluginDirtyInfoTest, decodingFromYamlShouldStoreAllNonZeroCounts) { + YAML::Node node = YAML::Load("{crc: 0x12345678, util: cleaner, itm: 2, udr: 10, nav: 30}"); + PluginDirtyInfo info = node.as(); - EXPECT_EQ(0x12345678, info.CRC()); - EXPECT_EQ(2, info.ITMs()); - EXPECT_EQ(10, info.DeletedRefs()); - EXPECT_EQ(30, info.DeletedNavmeshes()); - EXPECT_EQ("cleaner", info.CleaningUtility()); - } + EXPECT_EQ(0x12345678, info.CRC()); + EXPECT_EQ(2, info.ITMs()); + EXPECT_EQ(10, info.DeletedRefs()); + EXPECT_EQ(30, info.DeletedNavmeshes()); + EXPECT_EQ("cleaner", info.CleaningUtility()); +} - TEST_P(PluginDirtyInfoTest, decodingFromYamlScalarShouldThrow) { - YAML::Node node = YAML::Load("scalar"); +TEST_P(PluginDirtyInfoTest, decodingFromYamlScalarShouldThrow) { + YAML::Node node = YAML::Load("scalar"); - EXPECT_ANY_THROW(node.as()); - } + EXPECT_ANY_THROW(node.as()); +} - TEST_P(PluginDirtyInfoTest, decodingFromYamlListShouldThrow) { - YAML::Node node = YAML::Load("[0, 1, 2]"); +TEST_P(PluginDirtyInfoTest, decodingFromYamlListShouldThrow) { + YAML::Node node = YAML::Load("[0, 1, 2]"); - EXPECT_ANY_THROW(node.as()); - } - } + EXPECT_ANY_THROW(node.as()); +} +} } #endif diff --git a/src/tests/backend/metadata/plugin_metadata_test.h b/src/tests/backend/metadata/plugin_metadata_test.h index eac2f038..dc316a67 100644 --- a/src/tests/backend/metadata/plugin_metadata_test.h +++ b/src/tests/backend/metadata/plugin_metadata_test.h @@ -22,1063 +22,1064 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_F_BACKEND_METADATA_PLUGIN_METADATA -#define LOOT_TEST_F_BACKEND_METADATA_PLUGIN_METADATA +#ifndef LOOT_TESTS_BACKEND_METADATA_PLUGIN_METADATA_TEST +#define LOOT_TESTS_BACKEND_METADATA_PLUGIN_METADATA_TEST #include "backend/metadata/plugin_metadata.h" + #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class PluginMetadataTest : public BaseGameTest {}; - - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - PluginMetadataTest, - ::testing::Values( - GameType::tes5)); - - TEST_P(PluginMetadataTest, defaultConstructorShouldLeaveNameEmptyAndEnableMetadataAndLeaveAllOtherFieldsAtTheirDefaults) { - PluginMetadata plugin; - - EXPECT_TRUE(plugin.Name().empty()); - EXPECT_TRUE(plugin.Enabled()); - EXPECT_FALSE(plugin.IsPriorityExplicit()); - EXPECT_FALSE(plugin.IsPriorityGlobal()); - EXPECT_EQ(0, plugin.Priority()); - } - - TEST_P(PluginMetadataTest, stringConstructorShouldSetNameToGivenStringAndEnableMetadataAndLeaveAllOtherFieldsAtTheirDefaults) { - PluginMetadata plugin(blankEsm); - - EXPECT_EQ(blankEsm, plugin.Name()); - EXPECT_TRUE(plugin.Enabled()); - EXPECT_FALSE(plugin.IsPriorityExplicit()); - EXPECT_FALSE(plugin.IsPriorityGlobal()); - EXPECT_EQ(0, plugin.Priority()); - } - - TEST_P(PluginMetadataTest, equalityOperatorShouldUseCaseInsensitiveNameComparisonForNonRegexNames) { - PluginMetadata plugin1(blankEsm); - PluginMetadata plugin2(boost::to_lower_copy(blankEsm)); - EXPECT_TRUE(plugin1 == plugin2); - - plugin1 = PluginMetadata(blankEsm); - plugin2 = PluginMetadata(blankDifferentEsm); - EXPECT_FALSE(plugin1 == plugin2); - } - - TEST_P(PluginMetadataTest, equalityOperatorShouldUseCaseInsensitiveNameComparisonForTwoRegexNames) { - PluginMetadata plugin1("Blan.\\.esm"); - PluginMetadata plugin2("blan.\\.esm"); - EXPECT_TRUE(plugin1 == plugin2); - EXPECT_TRUE(plugin2 == plugin1); - - plugin1 = PluginMetadata("Blan(k|p).esm"); - plugin2 = PluginMetadata("Blan.\\.esm"); - EXPECT_FALSE(plugin1 == plugin2); - EXPECT_FALSE(plugin2 == plugin1); - } - - TEST_P(PluginMetadataTest, equalityOperatorShouldUseRegexMatchingForARegexNameAndANonRegexName) { - PluginMetadata plugin1("Blank.esm"); - PluginMetadata plugin2("Blan.\\.esm"); - EXPECT_TRUE(plugin1 == plugin2); - EXPECT_TRUE(plugin2 == plugin1); - - plugin1 = PluginMetadata("Blan.esm"); - plugin2 = PluginMetadata("Blan.\\.esm"); - EXPECT_FALSE(plugin1 == plugin2); - EXPECT_FALSE(plugin2 == plugin1); - } - - TEST_P(PluginMetadataTest, mergeMetadataShouldNotChangeName) { - PluginMetadata plugin1(blankEsm); - PluginMetadata plugin2(blankDifferentEsm); - - plugin1.MergeMetadata(plugin2); - - EXPECT_EQ(blankEsm, plugin1.Name()); - } - - TEST_P(PluginMetadataTest, mergeMetadataShouldNotUseMergedEnabledStateIfMergedMetadataIsEmpty) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin2.Enabled(false); - ASSERT_TRUE(plugin2.HasNameOnly()); - plugin1.MergeMetadata(plugin2); - - EXPECT_TRUE(plugin1.Enabled()); - } - - TEST_P(PluginMetadataTest, mergeMetadataShouldUseMergedEnabledStateIfMergedMetadataIsNotEmpty) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin2.Enabled(false); - plugin2.SetPriorityExplicit(true); - ASSERT_FALSE(plugin2.HasNameOnly()); - plugin1.MergeMetadata(plugin2); - - EXPECT_FALSE(plugin1.Enabled()); - } - - TEST_P(PluginMetadataTest, mergeMetadataShouldUseMergedNonZeroPriorityValue) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin1.Priority(5); - plugin2.Priority(3); - plugin1.MergeMetadata(plugin2); - - EXPECT_EQ(3, plugin1.Priority()); - } - - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeAnExplicitPriorityValueOfZero) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin1.Priority(5); - plugin2.SetPriorityExplicit(true); - plugin1.MergeMetadata(plugin2); - - EXPECT_EQ(0, plugin1.Priority()); - } - - TEST_P(PluginMetadataTest, mergeMetadataShouldNotMergeNonExplicitGlobalPriorityState) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin2.SetPriorityGlobal(true); - plugin1.MergeMetadata(plugin2); - - EXPECT_FALSE(plugin1.IsPriorityGlobal()); - } - - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeExplicitGlobalPriorityState) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin2.SetPriorityExplicit(true); - plugin2.SetPriorityGlobal(true); - plugin1.MergeMetadata(plugin2); - - EXPECT_TRUE(plugin1.IsPriorityGlobal()); - } +namespace test { +class PluginMetadataTest : public BaseGameTest {}; + +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + PluginMetadataTest, + ::testing::Values( + GameType::tes5)); + +TEST_P(PluginMetadataTest, defaultConstructorShouldLeaveNameEmptyAndEnableMetadataAndLeaveAllOtherFieldsAtTheirDefaults) { + PluginMetadata plugin; + + EXPECT_TRUE(plugin.Name().empty()); + EXPECT_TRUE(plugin.Enabled()); + EXPECT_FALSE(plugin.IsPriorityExplicit()); + EXPECT_FALSE(plugin.IsPriorityGlobal()); + EXPECT_EQ(0, plugin.Priority()); +} + +TEST_P(PluginMetadataTest, stringConstructorShouldSetNameToGivenStringAndEnableMetadataAndLeaveAllOtherFieldsAtTheirDefaults) { + PluginMetadata plugin(blankEsm); + + EXPECT_EQ(blankEsm, plugin.Name()); + EXPECT_TRUE(plugin.Enabled()); + EXPECT_FALSE(plugin.IsPriorityExplicit()); + EXPECT_FALSE(plugin.IsPriorityGlobal()); + EXPECT_EQ(0, plugin.Priority()); +} + +TEST_P(PluginMetadataTest, equalityOperatorShouldUseCaseInsensitiveNameComparisonForNonRegexNames) { + PluginMetadata plugin1(blankEsm); + PluginMetadata plugin2(boost::to_lower_copy(blankEsm)); + EXPECT_TRUE(plugin1 == plugin2); + + plugin1 = PluginMetadata(blankEsm); + plugin2 = PluginMetadata(blankDifferentEsm); + EXPECT_FALSE(plugin1 == plugin2); +} + +TEST_P(PluginMetadataTest, equalityOperatorShouldUseCaseInsensitiveNameComparisonForTwoRegexNames) { + PluginMetadata plugin1("Blan.\\.esm"); + PluginMetadata plugin2("blan.\\.esm"); + EXPECT_TRUE(plugin1 == plugin2); + EXPECT_TRUE(plugin2 == plugin1); + + plugin1 = PluginMetadata("Blan(k|p).esm"); + plugin2 = PluginMetadata("Blan.\\.esm"); + EXPECT_FALSE(plugin1 == plugin2); + EXPECT_FALSE(plugin2 == plugin1); +} + +TEST_P(PluginMetadataTest, equalityOperatorShouldUseRegexMatchingForARegexNameAndANonRegexName) { + PluginMetadata plugin1("Blank.esm"); + PluginMetadata plugin2("Blan.\\.esm"); + EXPECT_TRUE(plugin1 == plugin2); + EXPECT_TRUE(plugin2 == plugin1); + + plugin1 = PluginMetadata("Blan.esm"); + plugin2 = PluginMetadata("Blan.\\.esm"); + EXPECT_FALSE(plugin1 == plugin2); + EXPECT_FALSE(plugin2 == plugin1); +} + +TEST_P(PluginMetadataTest, mergeMetadataShouldNotChangeName) { + PluginMetadata plugin1(blankEsm); + PluginMetadata plugin2(blankDifferentEsm); + + plugin1.MergeMetadata(plugin2); + + EXPECT_EQ(blankEsm, plugin1.Name()); +} + +TEST_P(PluginMetadataTest, mergeMetadataShouldNotUseMergedEnabledStateIfMergedMetadataIsEmpty) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin2.Enabled(false); + ASSERT_TRUE(plugin2.HasNameOnly()); + plugin1.MergeMetadata(plugin2); + + EXPECT_TRUE(plugin1.Enabled()); +} + +TEST_P(PluginMetadataTest, mergeMetadataShouldUseMergedEnabledStateIfMergedMetadataIsNotEmpty) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin2.Enabled(false); + plugin2.SetPriorityExplicit(true); + ASSERT_FALSE(plugin2.HasNameOnly()); + plugin1.MergeMetadata(plugin2); + + EXPECT_FALSE(plugin1.Enabled()); +} + +TEST_P(PluginMetadataTest, mergeMetadataShouldUseMergedNonZeroPriorityValue) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin1.Priority(5); + plugin2.Priority(3); + plugin1.MergeMetadata(plugin2); + + EXPECT_EQ(3, plugin1.Priority()); +} + +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeAnExplicitPriorityValueOfZero) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin1.Priority(5); + plugin2.SetPriorityExplicit(true); + plugin1.MergeMetadata(plugin2); + + EXPECT_EQ(0, plugin1.Priority()); +} + +TEST_P(PluginMetadataTest, mergeMetadataShouldNotMergeNonExplicitGlobalPriorityState) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin2.SetPriorityGlobal(true); + plugin1.MergeMetadata(plugin2); + + EXPECT_FALSE(plugin1.IsPriorityGlobal()); +} + +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeExplicitGlobalPriorityState) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin2.SetPriorityExplicit(true); + plugin2.SetPriorityGlobal(true); + plugin1.MergeMetadata(plugin2); + + EXPECT_TRUE(plugin1.IsPriorityGlobal()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeTrueExplicitPriorityState) { - PluginMetadata plugin1; - PluginMetadata plugin2; +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeTrueExplicitPriorityState) { + PluginMetadata plugin1; + PluginMetadata plugin2; - plugin2.SetPriorityExplicit(true); - plugin1.MergeMetadata(plugin2); + plugin2.SetPriorityExplicit(true); + plugin1.MergeMetadata(plugin2); - EXPECT_TRUE(plugin1.IsPriorityExplicit()); - } + EXPECT_TRUE(plugin1.IsPriorityExplicit()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldNotMergeFalseExplicitPriorityState) { - PluginMetadata plugin1; - PluginMetadata plugin2; +TEST_P(PluginMetadataTest, mergeMetadataShouldNotMergeFalseExplicitPriorityState) { + PluginMetadata plugin1; + PluginMetadata plugin2; - plugin1.SetPriorityExplicit(true); - plugin1.MergeMetadata(plugin2); + plugin1.SetPriorityExplicit(true); + plugin1.MergeMetadata(plugin2); - EXPECT_TRUE(plugin1.IsPriorityExplicit()); - } + EXPECT_TRUE(plugin1.IsPriorityExplicit()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeLoadAfterData) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeLoadAfterData) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); - plugin1.LoadAfter({file1}); - plugin2.LoadAfter({file1, file2}); - plugin1.MergeMetadata(plugin2); + plugin1.LoadAfter({file1}); + plugin2.LoadAfter({file1, file2}); + plugin1.MergeMetadata(plugin2); - EXPECT_EQ(std::set({file1, file2}), plugin1.LoadAfter()); - } + EXPECT_EQ(std::set({file1, file2}), plugin1.LoadAfter()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeRequirementData) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeRequirementData) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); - plugin1.Reqs({file1}); - plugin2.Reqs({file1, file2}); - plugin1.MergeMetadata(plugin2); + plugin1.Reqs({file1}); + plugin2.Reqs({file1, file2}); + plugin1.MergeMetadata(plugin2); - EXPECT_EQ(std::set({file1, file2}), plugin1.Reqs()); - } + EXPECT_EQ(std::set({file1, file2}), plugin1.Reqs()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeIncompatibilityData) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeIncompatibilityData) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); - plugin1.Incs({file1}); - plugin2.Incs({file1, file2}); - plugin1.MergeMetadata(plugin2); + plugin1.Incs({file1}); + plugin2.Incs({file1, file2}); + plugin1.MergeMetadata(plugin2); - EXPECT_EQ(std::set({file1, file2}), plugin1.Incs()); - } + EXPECT_EQ(std::set({file1, file2}), plugin1.Incs()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeMessages) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Message message(Message::Type::say, "content"); +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeMessages) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Message message(Message::Type::say, "content"); - plugin1.Messages({message}); - plugin2.Messages({message}); - plugin1.MergeMetadata(plugin2); + plugin1.Messages({message}); + plugin2.Messages({message}); + plugin1.MergeMetadata(plugin2); - EXPECT_EQ(std::list({message, message}), plugin1.Messages()); - } + EXPECT_EQ(std::list({message, message}), plugin1.Messages()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeTags) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Tag tag1("Relev"); - Tag tag2("Relev", false); - Tag tag3("Delev"); +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeTags) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Tag tag1("Relev"); + Tag tag2("Relev", false); + Tag tag3("Delev"); - plugin1.Tags({tag1}); - plugin2.Tags({tag1, tag2, tag3}); - plugin1.MergeMetadata(plugin2); + plugin1.Tags({tag1}); + plugin2.Tags({tag1, tag2, tag3}); + plugin1.MergeMetadata(plugin2); - EXPECT_EQ(std::set({tag1, tag2, tag3}), plugin1.Tags()); - } + EXPECT_EQ(std::set({tag1, tag2, tag3}), plugin1.Tags()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeDirtyInfoData) { - PluginMetadata plugin1; - PluginMetadata plugin2; - PluginDirtyInfo info1(0x5, 1, 2, 3, "utility"); - PluginDirtyInfo info2(0xA, 1, 2, 3, "utility"); +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeDirtyInfoData) { + PluginMetadata plugin1; + PluginMetadata plugin2; + PluginDirtyInfo info1(0x5, 1, 2, 3, "utility"); + PluginDirtyInfo info2(0xA, 1, 2, 3, "utility"); - plugin1.DirtyInfo({info1}); - plugin2.DirtyInfo({info1, info2}); - plugin1.MergeMetadata(plugin2); + plugin1.DirtyInfo({info1}); + plugin2.DirtyInfo({info1, info2}); + plugin1.MergeMetadata(plugin2); - EXPECT_EQ(std::set({info1, info2}), plugin1.DirtyInfo()); - } + EXPECT_EQ(std::set({info1, info2}), plugin1.DirtyInfo()); +} - TEST_P(PluginMetadataTest, mergeMetadataShouldMergeLocationData) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Location location1("http://www.example.com/1"); - Location location2("http://www.example.com/2"); - - plugin1.Locations({location1}); - plugin2.Locations({location1, location2}); - plugin1.MergeMetadata(plugin2); - - EXPECT_EQ(std::set({location1, location2}), plugin1.Locations()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldUseSourcePluginName) { - PluginMetadata plugin1(blankEsm); - PluginMetadata plugin2(blankDifferentEsm); - - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(blankEsm, diff.Name()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldUseSourcePluginEnabledState) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin2.Enabled(false); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_TRUE(diff.Enabled()); - - plugin1.Enabled(false); - diff = plugin1.DiffMetadata(plugin2); - - EXPECT_FALSE(diff.Enabled()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldUseSourcePluginPriorityDataIfItDiffersFromTheTargetPluginPriorityData) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin1.SetPriorityGlobal(true); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(0, diff.Priority()); - EXPECT_FALSE(diff.IsPriorityExplicit()); - EXPECT_TRUE(diff.IsPriorityGlobal()); - - plugin1.Priority(5); - plugin1.SetPriorityGlobal(false); - diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(5, diff.Priority()); - EXPECT_TRUE(diff.IsPriorityExplicit()); - EXPECT_FALSE(diff.IsPriorityGlobal()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldOutputDefaultPriorityDataIfTheSourceAndTargetPluginPriorityDataMatch) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin1.Priority(5); - plugin1.SetPriorityGlobal(true); - plugin2.Priority(5); - plugin2.SetPriorityGlobal(true); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(0, diff.Priority()); - EXPECT_FALSE(diff.IsPriorityExplicit()); - EXPECT_FALSE(diff.IsPriorityGlobal()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldOutputLoadAfterDataThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); - File file3(blankEsp); - - plugin1.LoadAfter({file1, file2}); - plugin2.LoadAfter({file1, file3}); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(std::set({file2, file3}), diff.LoadAfter()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldOutputRequirementsDataThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); - File file3(blankEsp); +TEST_P(PluginMetadataTest, mergeMetadataShouldMergeLocationData) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Location location1("http://www.example.com/1"); + Location location2("http://www.example.com/2"); + + plugin1.Locations({location1}); + plugin2.Locations({location1, location2}); + plugin1.MergeMetadata(plugin2); + + EXPECT_EQ(std::set({location1, location2}), plugin1.Locations()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldUseSourcePluginName) { + PluginMetadata plugin1(blankEsm); + PluginMetadata plugin2(blankDifferentEsm); + + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(blankEsm, diff.Name()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldUseSourcePluginEnabledState) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin2.Enabled(false); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_TRUE(diff.Enabled()); + + plugin1.Enabled(false); + diff = plugin1.DiffMetadata(plugin2); + + EXPECT_FALSE(diff.Enabled()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldUseSourcePluginPriorityDataIfItDiffersFromTheTargetPluginPriorityData) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin1.SetPriorityGlobal(true); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(0, diff.Priority()); + EXPECT_FALSE(diff.IsPriorityExplicit()); + EXPECT_TRUE(diff.IsPriorityGlobal()); + + plugin1.Priority(5); + plugin1.SetPriorityGlobal(false); + diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(5, diff.Priority()); + EXPECT_TRUE(diff.IsPriorityExplicit()); + EXPECT_FALSE(diff.IsPriorityGlobal()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldOutputDefaultPriorityDataIfTheSourceAndTargetPluginPriorityDataMatch) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin1.Priority(5); + plugin1.SetPriorityGlobal(true); + plugin2.Priority(5); + plugin2.SetPriorityGlobal(true); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(0, diff.Priority()); + EXPECT_FALSE(diff.IsPriorityExplicit()); + EXPECT_FALSE(diff.IsPriorityGlobal()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldOutputLoadAfterDataThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); + File file3(blankEsp); + + plugin1.LoadAfter({file1, file2}); + plugin2.LoadAfter({file1, file3}); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(std::set({file2, file3}), diff.LoadAfter()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldOutputRequirementsDataThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); + File file3(blankEsp); - plugin1.Reqs({file1, file2}); - plugin2.Reqs({file1, file3}); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); + plugin1.Reqs({file1, file2}); + plugin2.Reqs({file1, file3}); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); - EXPECT_EQ(std::set({file2, file3}), diff.Reqs()); - } + EXPECT_EQ(std::set({file2, file3}), diff.Reqs()); +} - TEST_P(PluginMetadataTest, diffMetadataShouldOutputIncompatibilityDataThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); - File file3(blankEsp); +TEST_P(PluginMetadataTest, diffMetadataShouldOutputIncompatibilityDataThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); + File file3(blankEsp); - plugin1.Incs({file1, file2}); - plugin2.Incs({file1, file3}); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(std::set({file2, file3}), diff.Incs()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldOutputMessagesThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Message message1(Message::Type::say, "content1"); - Message message2(Message::Type::say, "content2"); - Message message3(Message::Type::say, "content3"); - - plugin1.Messages({message1, message2}); - plugin2.Messages({message1, message3}); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(std::list({message2, message3}), diff.Messages()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldOutputTagsThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Tag tag1("Relev"); - Tag tag2("Relev", false); - Tag tag3("Delev"); - - plugin1.Tags({tag1, tag2}); - plugin2.Tags({tag1, tag3}); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(std::set({tag2, tag3}), diff.Tags()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldOutputDirtyInfoObjectsThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - PluginDirtyInfo info1(0x5, 1, 2, 3, "utility"); - PluginDirtyInfo info2(0xA, 1, 2, 3, "utility"); - PluginDirtyInfo info3(0x1, 1, 2, 3, "utility"); - - plugin1.DirtyInfo({info1, info2}); - plugin2.DirtyInfo({info1, info3}); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(std::set({info2, info3}), diff.DirtyInfo()); - } - - TEST_P(PluginMetadataTest, diffMetadataShouldOutputLocationsThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Location location1("http://www.example.com/1"); - Location location2("http://www.example.com/2"); - Location location3("http://www.example.com/3"); - - plugin1.Locations({location1, location2}); - plugin2.Locations({location1, location3}); - PluginMetadata diff = plugin1.DiffMetadata(plugin2); - - EXPECT_EQ(std::set({location2, location3}), diff.Locations()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldUseSourcePluginName) { - PluginMetadata plugin1(blankEsm); - PluginMetadata plugin2(blankDifferentEsm); - - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(blankEsm, newMetadata.Name()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldUseSourcePluginEnabledState) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin2.Enabled(false); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_TRUE(newMetadata.Enabled()); - - plugin1.Enabled(false); - newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_FALSE(newMetadata.Enabled()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldUseSourcePluginPriorityData) { - PluginMetadata plugin1; - PluginMetadata plugin2; - - plugin1.Priority(5); - plugin1.SetPriorityGlobal(true); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(5, newMetadata.Priority()); - EXPECT_TRUE(newMetadata.IsPriorityExplicit()); - EXPECT_TRUE(newMetadata.IsPriorityGlobal()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldOutputLoadAfterDataThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); - File file3(blankEsp); - - plugin1.LoadAfter({file1, file2}); - plugin2.LoadAfter({file1, file3}); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(std::set({file2}), newMetadata.LoadAfter()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldOutputRequirementsDataThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); - File file3(blankEsp); - - plugin1.Reqs({file1, file2}); - plugin2.Reqs({file1, file3}); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(std::set({file2}), newMetadata.Reqs()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldOutputIncompatibilityDataThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - File file1(blankEsm); - File file2(blankDifferentEsm); - File file3(blankEsp); - - plugin1.Incs({file1, file2}); - plugin2.Incs({file1, file3}); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(std::set({file2}), newMetadata.Incs()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldOutputMessagesThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Message message1(Message::Type::say, "content1"); - Message message2(Message::Type::say, "content2"); - Message message3(Message::Type::say, "content3"); - - plugin1.Messages({message1, message2}); - plugin2.Messages({message1, message3}); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(std::list({message2}), newMetadata.Messages()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldOutputTagsThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Tag tag1("Relev"); - Tag tag2("Relev", false); - Tag tag3("Delev"); - - plugin1.Tags({tag1, tag2}); - plugin2.Tags({tag1, tag3}); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(std::set({tag2}), newMetadata.Tags()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldOutputDirtyInfoObjectsThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - PluginDirtyInfo info1(0x5, 1, 2, 3, "utility"); - PluginDirtyInfo info2(0xA, 1, 2, 3, "utility"); - PluginDirtyInfo info3(0x1, 1, 2, 3, "utility"); - - plugin1.DirtyInfo({info1, info2}); - plugin2.DirtyInfo({info1, info3}); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(std::set({info2}), newMetadata.DirtyInfo()); - } - - TEST_P(PluginMetadataTest, newMetadataShouldOutputLocationsThatAreNotCommonToBothInputPlugins) { - PluginMetadata plugin1; - PluginMetadata plugin2; - Location location1("http://www.example.com/1"); - Location location2("http://www.example.com/2"); - Location location3("http://www.example.com/3"); - - plugin1.Locations({location1, location2}); - plugin2.Locations({location1, location3}); - PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); - - EXPECT_EQ(std::set({location2}), newMetadata.Locations()); - } - - TEST_P(PluginMetadataTest, settingPriorityWithAbsoluteValueGreaterOrEqualToGlobalPriorityDivisorShouldThrow) { - PluginMetadata plugin; - EXPECT_ANY_THROW(plugin.Priority(yamlGlobalPriorityDivisor)); - EXPECT_ANY_THROW(plugin.Priority(yamlGlobalPriorityDivisor + 1)); - EXPECT_ANY_THROW(plugin.Priority(-yamlGlobalPriorityDivisor)); - EXPECT_ANY_THROW(plugin.Priority(-yamlGlobalPriorityDivisor - 1)); - } - - TEST_P(PluginMetadataTest, settingPriorityAsGlobalShouldSucceed) { - PluginMetadata plugin; - ASSERT_FALSE(plugin.IsPriorityGlobal()); - plugin.SetPriorityGlobal(true); - EXPECT_TRUE(plugin.IsPriorityGlobal()); - } - - TEST_P(PluginMetadataTest, settingPriorityAsNotGlobalShouldSucceed) { - PluginMetadata plugin; - plugin.SetPriorityGlobal(true); - ASSERT_TRUE(plugin.IsPriorityGlobal()); - plugin.SetPriorityGlobal(false); - EXPECT_FALSE(plugin.IsPriorityGlobal()); - } - - TEST_P(PluginMetadataTest, gettingYamlPriorityValueForAGlobalPriorityShouldReturnValueWithGlobalFlagSet) { - PluginMetadata plugin; - plugin.Priority(10); - plugin.SetPriorityGlobal(true); - EXPECT_EQ(1000010, plugin.GetYamlPriorityValue()); - - plugin.Priority(-20); - EXPECT_EQ(-1000020, plugin.GetYamlPriorityValue()); - } - - TEST_P(PluginMetadataTest, gettingYamlPriorityValueForANonGlobalPriorityShouldReturnValueWithGlobalFlagNotSet) { - PluginMetadata plugin; - plugin.Priority(10); - EXPECT_EQ(10, plugin.GetYamlPriorityValue()); - - plugin.Priority(-20); - EXPECT_EQ(-20, plugin.GetYamlPriorityValue()); - } - - TEST_P(PluginMetadataTest, evalAllConditionsShouldEvaluateAllMetadataConditions) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - - PluginMetadata plugin(blankEsm); - - File file1(blankEsp); - File file2(blankDifferentEsm, "", "file(\"" + missingEsp + "\")"); - plugin.LoadAfter({file1, file2}); - plugin.Reqs({file1, file2}); - plugin.Incs({file1, file2}); - - Message message1(Message::Type::say, "content"); - Message message2(Message::Type::say, "content", "file(\"" + missingEsp + "\")"); - plugin.Messages({message1, message2}); - - Tag tag1("Relev"); - Tag tag2("Relev", true, "file(\"" + missingEsp + "\")"); - plugin.Tags({tag1, tag2}); - - PluginDirtyInfo info1(blankEsmCrc, 1, 2, 3, "utility"); - PluginDirtyInfo info2(0xDEADBEEF, 1, 2, 3, "utility"); - plugin.DirtyInfo({info1, info2}); - - EXPECT_NO_THROW(plugin.EvalAllConditions(game, Language::Code::english)); + plugin1.Incs({file1, file2}); + plugin2.Incs({file1, file3}); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(std::set({file2, file3}), diff.Incs()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldOutputMessagesThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Message message1(Message::Type::say, "content1"); + Message message2(Message::Type::say, "content2"); + Message message3(Message::Type::say, "content3"); + + plugin1.Messages({message1, message2}); + plugin2.Messages({message1, message3}); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(std::list({message2, message3}), diff.Messages()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldOutputTagsThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Tag tag1("Relev"); + Tag tag2("Relev", false); + Tag tag3("Delev"); + + plugin1.Tags({tag1, tag2}); + plugin2.Tags({tag1, tag3}); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(std::set({tag2, tag3}), diff.Tags()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldOutputDirtyInfoObjectsThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + PluginDirtyInfo info1(0x5, 1, 2, 3, "utility"); + PluginDirtyInfo info2(0xA, 1, 2, 3, "utility"); + PluginDirtyInfo info3(0x1, 1, 2, 3, "utility"); + + plugin1.DirtyInfo({info1, info2}); + plugin2.DirtyInfo({info1, info3}); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(std::set({info2, info3}), diff.DirtyInfo()); +} + +TEST_P(PluginMetadataTest, diffMetadataShouldOutputLocationsThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Location location1("http://www.example.com/1"); + Location location2("http://www.example.com/2"); + Location location3("http://www.example.com/3"); + + plugin1.Locations({location1, location2}); + plugin2.Locations({location1, location3}); + PluginMetadata diff = plugin1.DiffMetadata(plugin2); + + EXPECT_EQ(std::set({location2, location3}), diff.Locations()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldUseSourcePluginName) { + PluginMetadata plugin1(blankEsm); + PluginMetadata plugin2(blankDifferentEsm); + + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(blankEsm, newMetadata.Name()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldUseSourcePluginEnabledState) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin2.Enabled(false); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_TRUE(newMetadata.Enabled()); + + plugin1.Enabled(false); + newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_FALSE(newMetadata.Enabled()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldUseSourcePluginPriorityData) { + PluginMetadata plugin1; + PluginMetadata plugin2; + + plugin1.Priority(5); + plugin1.SetPriorityGlobal(true); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(5, newMetadata.Priority()); + EXPECT_TRUE(newMetadata.IsPriorityExplicit()); + EXPECT_TRUE(newMetadata.IsPriorityGlobal()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldOutputLoadAfterDataThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); + File file3(blankEsp); + + plugin1.LoadAfter({file1, file2}); + plugin2.LoadAfter({file1, file3}); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(std::set({file2}), newMetadata.LoadAfter()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldOutputRequirementsDataThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); + File file3(blankEsp); + + plugin1.Reqs({file1, file2}); + plugin2.Reqs({file1, file3}); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(std::set({file2}), newMetadata.Reqs()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldOutputIncompatibilityDataThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + File file1(blankEsm); + File file2(blankDifferentEsm); + File file3(blankEsp); + + plugin1.Incs({file1, file2}); + plugin2.Incs({file1, file3}); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(std::set({file2}), newMetadata.Incs()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldOutputMessagesThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Message message1(Message::Type::say, "content1"); + Message message2(Message::Type::say, "content2"); + Message message3(Message::Type::say, "content3"); + + plugin1.Messages({message1, message2}); + plugin2.Messages({message1, message3}); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(std::list({message2}), newMetadata.Messages()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldOutputTagsThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Tag tag1("Relev"); + Tag tag2("Relev", false); + Tag tag3("Delev"); + + plugin1.Tags({tag1, tag2}); + plugin2.Tags({tag1, tag3}); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(std::set({tag2}), newMetadata.Tags()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldOutputDirtyInfoObjectsThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + PluginDirtyInfo info1(0x5, 1, 2, 3, "utility"); + PluginDirtyInfo info2(0xA, 1, 2, 3, "utility"); + PluginDirtyInfo info3(0x1, 1, 2, 3, "utility"); + + plugin1.DirtyInfo({info1, info2}); + plugin2.DirtyInfo({info1, info3}); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(std::set({info2}), newMetadata.DirtyInfo()); +} + +TEST_P(PluginMetadataTest, newMetadataShouldOutputLocationsThatAreNotCommonToBothInputPlugins) { + PluginMetadata plugin1; + PluginMetadata plugin2; + Location location1("http://www.example.com/1"); + Location location2("http://www.example.com/2"); + Location location3("http://www.example.com/3"); + + plugin1.Locations({location1, location2}); + plugin2.Locations({location1, location3}); + PluginMetadata newMetadata = plugin1.NewMetadata(plugin2); + + EXPECT_EQ(std::set({location2}), newMetadata.Locations()); +} + +TEST_P(PluginMetadataTest, settingPriorityWithAbsoluteValueGreaterOrEqualToGlobalPriorityDivisorShouldThrow) { + PluginMetadata plugin; + EXPECT_ANY_THROW(plugin.Priority(yamlGlobalPriorityDivisor)); + EXPECT_ANY_THROW(plugin.Priority(yamlGlobalPriorityDivisor + 1)); + EXPECT_ANY_THROW(plugin.Priority(-yamlGlobalPriorityDivisor)); + EXPECT_ANY_THROW(plugin.Priority(-yamlGlobalPriorityDivisor - 1)); +} + +TEST_P(PluginMetadataTest, settingPriorityAsGlobalShouldSucceed) { + PluginMetadata plugin; + ASSERT_FALSE(plugin.IsPriorityGlobal()); + plugin.SetPriorityGlobal(true); + EXPECT_TRUE(plugin.IsPriorityGlobal()); +} + +TEST_P(PluginMetadataTest, settingPriorityAsNotGlobalShouldSucceed) { + PluginMetadata plugin; + plugin.SetPriorityGlobal(true); + ASSERT_TRUE(plugin.IsPriorityGlobal()); + plugin.SetPriorityGlobal(false); + EXPECT_FALSE(plugin.IsPriorityGlobal()); +} + +TEST_P(PluginMetadataTest, gettingYamlPriorityValueForAGlobalPriorityShouldReturnValueWithGlobalFlagSet) { + PluginMetadata plugin; + plugin.Priority(10); + plugin.SetPriorityGlobal(true); + EXPECT_EQ(1000010, plugin.GetYamlPriorityValue()); + + plugin.Priority(-20); + EXPECT_EQ(-1000020, plugin.GetYamlPriorityValue()); +} + +TEST_P(PluginMetadataTest, gettingYamlPriorityValueForANonGlobalPriorityShouldReturnValueWithGlobalFlagNotSet) { + PluginMetadata plugin; + plugin.Priority(10); + EXPECT_EQ(10, plugin.GetYamlPriorityValue()); + + plugin.Priority(-20); + EXPECT_EQ(-20, plugin.GetYamlPriorityValue()); +} + +TEST_P(PluginMetadataTest, evalAllConditionsShouldEvaluateAllMetadataConditions) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + + PluginMetadata plugin(blankEsm); + + File file1(blankEsp); + File file2(blankDifferentEsm, "", "file(\"" + missingEsp + "\")"); + plugin.LoadAfter({file1, file2}); + plugin.Reqs({file1, file2}); + plugin.Incs({file1, file2}); + + Message message1(Message::Type::say, "content"); + Message message2(Message::Type::say, "content", "file(\"" + missingEsp + "\")"); + plugin.Messages({message1, message2}); + + Tag tag1("Relev"); + Tag tag2("Relev", true, "file(\"" + missingEsp + "\")"); + plugin.Tags({tag1, tag2}); + + PluginDirtyInfo info1(blankEsmCrc, 1, 2, 3, "utility"); + PluginDirtyInfo info2(0xDEADBEEF, 1, 2, 3, "utility"); + plugin.DirtyInfo({info1, info2}); + + EXPECT_NO_THROW(plugin.EvalAllConditions(game, Language::Code::english)); - std::set expectedFiles({file1}); - EXPECT_EQ(expectedFiles, plugin.LoadAfter()); - EXPECT_EQ(expectedFiles, plugin.Reqs()); - EXPECT_EQ(expectedFiles, plugin.Incs()); - EXPECT_EQ(std::list({message1}), plugin.Messages()); - EXPECT_EQ(std::set({tag1}), plugin.Tags()); - EXPECT_EQ(std::set({info1}), plugin.DirtyInfo()); - } + std::set expectedFiles({file1}); + EXPECT_EQ(expectedFiles, plugin.LoadAfter()); + EXPECT_EQ(expectedFiles, plugin.Reqs()); + EXPECT_EQ(expectedFiles, plugin.Incs()); + EXPECT_EQ(std::list({message1}), plugin.Messages()); + EXPECT_EQ(std::set({tag1}), plugin.Tags()); + EXPECT_EQ(std::set({info1}), plugin.DirtyInfo()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeTrueForADefaultConstructedPluginMetadataObject) { - PluginMetadata plugin; +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeTrueForADefaultConstructedPluginMetadataObject) { + PluginMetadata plugin; - EXPECT_TRUE(plugin.HasNameOnly()); - } + EXPECT_TRUE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeTrueForAPluginMetadataObjectConstructedWithAName) { - PluginMetadata plugin(blankEsp); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeTrueForAPluginMetadataObjectConstructedWithAName) { + PluginMetadata plugin(blankEsp); - EXPECT_TRUE(plugin.HasNameOnly()); - } + EXPECT_TRUE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeTrueIfThePluginMetadataIsDisabled) { - PluginMetadata plugin(blankEsp); - plugin.Enabled(false); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeTrueIfThePluginMetadataIsDisabled) { + PluginMetadata plugin(blankEsp); + plugin.Enabled(false); - EXPECT_TRUE(plugin.HasNameOnly()); - } + EXPECT_TRUE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfThePriorityValueIsExplicit) { - PluginMetadata plugin(blankEsp); - plugin.SetPriorityExplicit(true); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfThePriorityValueIsExplicit) { + PluginMetadata plugin(blankEsp); + plugin.SetPriorityExplicit(true); - EXPECT_FALSE(plugin.HasNameOnly()); - } + EXPECT_FALSE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfLoadAfterMetadataExists) { - PluginMetadata plugin(blankEsp); - plugin.LoadAfter({File(blankEsm)}); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfLoadAfterMetadataExists) { + PluginMetadata plugin(blankEsp); + plugin.LoadAfter({File(blankEsm)}); - EXPECT_FALSE(plugin.HasNameOnly()); - } + EXPECT_FALSE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfRequirementMetadataExists) { - PluginMetadata plugin(blankEsp); - plugin.Reqs({File(blankEsm)}); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfRequirementMetadataExists) { + PluginMetadata plugin(blankEsp); + plugin.Reqs({File(blankEsm)}); - EXPECT_FALSE(plugin.HasNameOnly()); - } + EXPECT_FALSE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfIncompatibilityMetadataExists) { - PluginMetadata plugin(blankEsp); - plugin.Incs({File(blankEsm)}); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfIncompatibilityMetadataExists) { + PluginMetadata plugin(blankEsp); + plugin.Incs({File(blankEsm)}); - EXPECT_FALSE(plugin.HasNameOnly()); - } + EXPECT_FALSE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfMessagesExist) { - PluginMetadata plugin(blankEsp); - plugin.Messages({Message(Message::Type::say, "content")}); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfMessagesExist) { + PluginMetadata plugin(blankEsp); + plugin.Messages({Message(Message::Type::say, "content")}); - EXPECT_FALSE(plugin.HasNameOnly()); - } + EXPECT_FALSE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfTagsExist) { - PluginMetadata plugin(blankEsp); - plugin.Tags({Tag("Relev")}); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfTagsExist) { + PluginMetadata plugin(blankEsp); + plugin.Tags({Tag("Relev")}); - EXPECT_FALSE(plugin.HasNameOnly()); - } + EXPECT_FALSE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfDirtyInfoExists) { - PluginMetadata plugin(blankEsp); - plugin.DirtyInfo({PluginDirtyInfo(5, 0, 1, 2, "utility")}); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfDirtyInfoExists) { + PluginMetadata plugin(blankEsp); + plugin.DirtyInfo({PluginDirtyInfo(5, 0, 1, 2, "utility")}); - EXPECT_FALSE(plugin.HasNameOnly()); - } + EXPECT_FALSE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfLocationsExist) { - PluginMetadata plugin(blankEsp); - plugin.Locations({Location("http://www.example.com")}); +TEST_P(PluginMetadataTest, hasNameOnlyShouldBeFalseIfLocationsExist) { + PluginMetadata plugin(blankEsp); + plugin.Locations({Location("http://www.example.com")}); - EXPECT_FALSE(plugin.HasNameOnly()); - } + EXPECT_FALSE(plugin.HasNameOnly()); +} - TEST_P(PluginMetadataTest, isRegexPluginShouldBeFalseForAnEmptyPluginName) { - PluginMetadata plugin; +TEST_P(PluginMetadataTest, isRegexPluginShouldBeFalseForAnEmptyPluginName) { + PluginMetadata plugin; - EXPECT_FALSE(plugin.IsRegexPlugin()); - } + EXPECT_FALSE(plugin.IsRegexPlugin()); +} - TEST_P(PluginMetadataTest, isRegexPluginShouldBeFalseForAnExactPluginFilename) { - PluginMetadata plugin(blankEsm); +TEST_P(PluginMetadataTest, isRegexPluginShouldBeFalseForAnExactPluginFilename) { + PluginMetadata plugin(blankEsm); - EXPECT_FALSE(plugin.IsRegexPlugin()); - } + EXPECT_FALSE(plugin.IsRegexPlugin()); +} - TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsAColon) { - PluginMetadata plugin("Blank:.esm"); +TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsAColon) { + PluginMetadata plugin("Blank:.esm"); - EXPECT_TRUE(plugin.IsRegexPlugin()); - } + EXPECT_TRUE(plugin.IsRegexPlugin()); +} - TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsABackslash) { - PluginMetadata plugin("Blank\\.esm"); +TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsABackslash) { + PluginMetadata plugin("Blank\\.esm"); - EXPECT_TRUE(plugin.IsRegexPlugin()); - } + EXPECT_TRUE(plugin.IsRegexPlugin()); +} - TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsAnAsterisk) { - PluginMetadata plugin("Blank*.esm"); +TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsAnAsterisk) { + PluginMetadata plugin("Blank*.esm"); - EXPECT_TRUE(plugin.IsRegexPlugin()); - } + EXPECT_TRUE(plugin.IsRegexPlugin()); +} - TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsAQuestionMark) { - PluginMetadata plugin("Blank?.esm"); +TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsAQuestionMark) { + PluginMetadata plugin("Blank?.esm"); - EXPECT_TRUE(plugin.IsRegexPlugin()); - } + EXPECT_TRUE(plugin.IsRegexPlugin()); +} - TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsAVerticalBar) { - PluginMetadata plugin("Blank|.esm"); +TEST_P(PluginMetadataTest, isRegexPluginShouldBeTrueIfThePluginNameContainsAVerticalBar) { + PluginMetadata plugin("Blank|.esm"); - EXPECT_TRUE(plugin.IsRegexPlugin()); - } + EXPECT_TRUE(plugin.IsRegexPlugin()); +} - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithNoMetadataAsABlankString) { - PluginMetadata plugin(blankEsm); - YAML::Emitter emitter; - emitter << plugin; +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithNoMetadataAsABlankString) { + PluginMetadata plugin(blankEsm); + YAML::Emitter emitter; + emitter << plugin; - EXPECT_STREQ("", emitter.c_str()); - } + EXPECT_STREQ("", emitter.c_str()); +} - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithAnExplicitPriorityCorrectly) { - PluginMetadata plugin(blankEsm); - plugin.SetPriorityExplicit(true); +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithAnExplicitPriorityCorrectly) { + PluginMetadata plugin(blankEsm); + plugin.SetPriorityExplicit(true); - YAML::Emitter emitter; - emitter << plugin; + YAML::Emitter emitter; + emitter << plugin; - EXPECT_STREQ("name: 'Blank.esm'\n" - "priority: 0", emitter.c_str()); - } + EXPECT_STREQ("name: 'Blank.esm'\n" + "priority: 0", emitter.c_str()); +} - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithAnExplicitPriorityThatIsDisabledCorrectly) { - PluginMetadata plugin(blankEsm); - plugin.SetPriorityExplicit(true); - plugin.Enabled(false); +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithAnExplicitPriorityThatIsDisabledCorrectly) { + PluginMetadata plugin(blankEsm); + plugin.SetPriorityExplicit(true); + plugin.Enabled(false); - YAML::Emitter emitter; - emitter << plugin; + YAML::Emitter emitter; + emitter << plugin; - EXPECT_STREQ("name: 'Blank.esm'\n" - "priority: 0\n" - "enabled: false", emitter.c_str()); - } + EXPECT_STREQ("name: 'Blank.esm'\n" + "priority: 0\n" + "enabled: false", emitter.c_str()); +} - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithLoadAfterMetadataCorrectly) { - PluginMetadata plugin(blankEsp); - plugin.LoadAfter({File(blankEsm)}); +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithLoadAfterMetadataCorrectly) { + PluginMetadata plugin(blankEsp); + plugin.LoadAfter({File(blankEsm)}); - YAML::Emitter emitter; - emitter << plugin; + YAML::Emitter emitter; + emitter << plugin; - EXPECT_STREQ("name: 'Blank.esp'\n" - "after:\n" - " - 'Blank.esm'", emitter.c_str()); - } + EXPECT_STREQ("name: 'Blank.esp'\n" + "after:\n" + " - 'Blank.esm'", emitter.c_str()); +} - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithRequirementsCorrectly) { - PluginMetadata plugin(blankEsp); - plugin.Reqs({File(blankEsm)}); +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithRequirementsCorrectly) { + PluginMetadata plugin(blankEsp); + plugin.Reqs({File(blankEsm)}); - YAML::Emitter emitter; - emitter << plugin; + YAML::Emitter emitter; + emitter << plugin; - EXPECT_STREQ("name: 'Blank.esp'\n" - "req:\n" - " - 'Blank.esm'", emitter.c_str()); - } + EXPECT_STREQ("name: 'Blank.esp'\n" + "req:\n" + " - 'Blank.esm'", emitter.c_str()); +} - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithIncompatibilitiesCorrectly) { - PluginMetadata plugin(blankEsp); - plugin.Incs({File(blankEsm)}); - - YAML::Emitter emitter; - emitter << plugin; - - EXPECT_STREQ("name: 'Blank.esp'\n" - "inc:\n" - " - 'Blank.esm'", emitter.c_str()); - } - - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithMessagesCorrectly) { - PluginMetadata plugin(blankEsp); - plugin.Messages({Message(Message::Type::say, "content")}); - - YAML::Emitter emitter; - emitter << plugin; - - EXPECT_STREQ("name: 'Blank.esp'\n" - "msg:\n" - " - type: say\n" - " content: 'content'", emitter.c_str()); - } - - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithTagsCorrectly) { - PluginMetadata plugin(blankEsp); - plugin.Tags({Tag("Relev")}); - - YAML::Emitter emitter; - emitter << plugin; - - EXPECT_STREQ("name: 'Blank.esp'\n" - "tag:\n" - " - Relev", emitter.c_str()); - } - - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithDirtyInfoCorrectly) { - PluginMetadata plugin(blankEsp); - plugin.DirtyInfo({PluginDirtyInfo(5, 0, 1, 2, "utility")}); - - YAML::Emitter emitter; - emitter << plugin; - - EXPECT_STREQ("name: 'Blank.esp'\n" - "dirty:\n" - " - crc: 0x5\n" - " util: 'utility'\n" - " udr: 1\n" - " nav: 2", emitter.c_str()); - } - - TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithLocationsCorrectly) { - PluginMetadata plugin(blankEsp); - plugin.Locations({Location("http://www.example.com")}); - - YAML::Emitter emitter; - emitter << plugin; - - EXPECT_STREQ("name: 'Blank.esp'\n" - "url:\n" - " - 'http://www.example.com'", emitter.c_str()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldOmitAllUnsetFields) { - PluginMetadata plugin(blankEsp); - YAML::Node node; - node = plugin; - - EXPECT_EQ(plugin.Name(), node["name"].as()); - EXPECT_FALSE(node["enabled"]); - EXPECT_FALSE(node["priority"]); - EXPECT_FALSE(node["after"]); - EXPECT_FALSE(node["req"]); - EXPECT_FALSE(node["inc"]); - EXPECT_FALSE(node["msg"]); - EXPECT_FALSE(node["tag"]); - EXPECT_FALSE(node["dirty"]); - EXPECT_FALSE(node["url"]); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetPriorityFieldIfPriorityIsExplicit) { - PluginMetadata plugin(blankEsp); - plugin.SetPriorityExplicit(true); - YAML::Node node; - node = plugin; - - EXPECT_EQ(0, node["priority"].as()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetEnabledFieldIfItIsFalse) { - PluginMetadata plugin(blankEsp); - plugin.Enabled(false); - YAML::Node node; - node = plugin; - - EXPECT_FALSE(node["enabled"].as()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetAfterFieldIfLoadAfterMetadataExists) { - PluginMetadata plugin(blankEsp); - plugin.LoadAfter({File(blankEsm)}); - YAML::Node node; - node = plugin; - - EXPECT_EQ(plugin.LoadAfter(), node["after"].as>()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetReqFieldIfRequirementsExist) { - PluginMetadata plugin(blankEsp); - plugin.Reqs({File(blankEsm)}); - YAML::Node node; - node = plugin; - - EXPECT_EQ(plugin.Reqs(), node["req"].as>()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetIncFieldIfIncompatibilitiesExist) { - PluginMetadata plugin(blankEsp); - plugin.Incs({File(blankEsm)}); - YAML::Node node; - node = plugin; - - EXPECT_EQ(plugin.Incs(), node["inc"].as>()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetMsgFieldIfMessagesExist) { - PluginMetadata plugin(blankEsp); - plugin.Messages({Message(Message::Type::say, "content")}); - YAML::Node node; - node = plugin; - - EXPECT_EQ(plugin.Messages(), node["msg"].as>()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetTagFieldIfTagsExist) { - PluginMetadata plugin(blankEsp); - plugin.Tags({Tag("Relev")}); - YAML::Node node; - node = plugin; - - EXPECT_EQ(plugin.Tags(), node["tag"].as>()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetDirtyFieldIfDirtyInfoExists) { - PluginMetadata plugin(blankEsp); - plugin.DirtyInfo({PluginDirtyInfo(5, 0, 1, 2, "utility")}); - YAML::Node node; - node = plugin; - - EXPECT_EQ(plugin.DirtyInfo(), node["dirty"].as>()); - } - - TEST_P(PluginMetadataTest, encodingAsYamlShouldSetUrlFieldIfLocationsExist) { - PluginMetadata plugin(blankEsp); - plugin.Locations({Location("http://www.example.com")}); - YAML::Node node; - node = plugin; - - EXPECT_EQ(plugin.Locations(), node["url"].as>()); - } - - TEST_P(PluginMetadataTest, decodingFromYamlShouldSetDefaultPriorityValuesIfNoneAreSpecified) { - YAML::Node node = YAML::Load("name: " + blankEsp); - PluginMetadata plugin = node.as(); - - EXPECT_EQ(blankEsp, plugin.Name()); - EXPECT_EQ(0, plugin.Priority()); - EXPECT_FALSE(plugin.IsPriorityExplicit()); - EXPECT_FALSE(plugin.IsPriorityGlobal()); - } - - TEST_P(PluginMetadataTest, decodingFromYamlShouldStoreAllGivenData) { - YAML::Node node = YAML::Load("name: 'Blank.esp'\n" - "priority: 5\n" - "enabled: false\n" - "after:\n" - " - 'Blank.esm'\n" - "req:\n" - " - 'Blank.esm'\n" - "inc:\n" - " - 'Blank.esm'\n" - "msg:\n" - " - type: say\n" - " content: 'content'\n" - "tag:\n" - " - Relev\n" - "dirty:\n" - " - crc: 0x5\n" - " util: 'utility'\n" - " udr: 1\n" - " nav: 2\n" - "url:\n" - " - 'http://www.example.com'"); - PluginMetadata plugin = node.as(); - - EXPECT_EQ("Blank.esp", plugin.Name()); - EXPECT_EQ(5, plugin.Priority()); - EXPECT_TRUE(plugin.IsPriorityExplicit()); - EXPECT_FALSE(plugin.IsPriorityGlobal()); - EXPECT_FALSE(plugin.Enabled()); - EXPECT_EQ(std::set({ - File("Blank.esm") - }), plugin.LoadAfter()); - EXPECT_EQ(std::set({ - File("Blank.esm") - }), plugin.Reqs()); - EXPECT_EQ(std::set({ - File("Blank.esm") - }), plugin.Incs()); - EXPECT_EQ(std::list({ - Message(Message::Type::say, "content") - }), plugin.Messages()); - EXPECT_EQ(std::set({ - Tag("Relev") - }), plugin.Tags()); - EXPECT_EQ(std::set({ - PluginDirtyInfo(5, 0, 1, 2, "utility") - }), plugin.DirtyInfo()); - EXPECT_EQ(std::set({ - Location("http://www.example.com") - }), plugin.Locations()); - } - - TEST_P(PluginMetadataTest, decodingFromYamlWithDirtyInfoInARegexPluginMetadataObjectShouldThrow) { - YAML::Node node = YAML::Load("name: 'Blank\\.esp'\n" - "dirty:\n" - " - crc: 0x5\n" - " util: 'utility'\n" - " udr: 1\n" - " nav: 2"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - - TEST_P(PluginMetadataTest, decodingFromYamlWithAnInvalidRegexNameShouldThrow) { - YAML::Node node = YAML::Load("name: 'RagnvaldBook(Farengar(+Ragnvald)?)?\\.esp'\n" - "dirty:\n" - " - crc: 0x5\n" - " util: 'utility'\n" - " udr: 1\n" - " nav: 2"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - - TEST_P(PluginMetadataTest, decodingFromAYamlScalarShouldThrow) { - YAML::Node node = YAML::Load("scalar"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - - TEST_P(PluginMetadataTest, decodingFromAYamlListShouldThrow) { - YAML::Node node = YAML::Load("[0, 1, 2]"); - - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - } +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithIncompatibilitiesCorrectly) { + PluginMetadata plugin(blankEsp); + plugin.Incs({File(blankEsm)}); + + YAML::Emitter emitter; + emitter << plugin; + + EXPECT_STREQ("name: 'Blank.esp'\n" + "inc:\n" + " - 'Blank.esm'", emitter.c_str()); +} + +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithMessagesCorrectly) { + PluginMetadata plugin(blankEsp); + plugin.Messages({Message(Message::Type::say, "content")}); + + YAML::Emitter emitter; + emitter << plugin; + + EXPECT_STREQ("name: 'Blank.esp'\n" + "msg:\n" + " - type: say\n" + " content: 'content'", emitter.c_str()); +} + +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithTagsCorrectly) { + PluginMetadata plugin(blankEsp); + plugin.Tags({Tag("Relev")}); + + YAML::Emitter emitter; + emitter << plugin; + + EXPECT_STREQ("name: 'Blank.esp'\n" + "tag:\n" + " - Relev", emitter.c_str()); +} + +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithDirtyInfoCorrectly) { + PluginMetadata plugin(blankEsp); + plugin.DirtyInfo({PluginDirtyInfo(5, 0, 1, 2, "utility")}); + + YAML::Emitter emitter; + emitter << plugin; + + EXPECT_STREQ("name: 'Blank.esp'\n" + "dirty:\n" + " - crc: 0x5\n" + " util: 'utility'\n" + " udr: 1\n" + " nav: 2", emitter.c_str()); +} + +TEST_P(PluginMetadataTest, emittingAsYamlShouldOutputAPluginWithLocationsCorrectly) { + PluginMetadata plugin(blankEsp); + plugin.Locations({Location("http://www.example.com")}); + + YAML::Emitter emitter; + emitter << plugin; + + EXPECT_STREQ("name: 'Blank.esp'\n" + "url:\n" + " - 'http://www.example.com'", emitter.c_str()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldOmitAllUnsetFields) { + PluginMetadata plugin(blankEsp); + YAML::Node node; + node = plugin; + + EXPECT_EQ(plugin.Name(), node["name"].as()); + EXPECT_FALSE(node["enabled"]); + EXPECT_FALSE(node["priority"]); + EXPECT_FALSE(node["after"]); + EXPECT_FALSE(node["req"]); + EXPECT_FALSE(node["inc"]); + EXPECT_FALSE(node["msg"]); + EXPECT_FALSE(node["tag"]); + EXPECT_FALSE(node["dirty"]); + EXPECT_FALSE(node["url"]); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetPriorityFieldIfPriorityIsExplicit) { + PluginMetadata plugin(blankEsp); + plugin.SetPriorityExplicit(true); + YAML::Node node; + node = plugin; + + EXPECT_EQ(0, node["priority"].as()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetEnabledFieldIfItIsFalse) { + PluginMetadata plugin(blankEsp); + plugin.Enabled(false); + YAML::Node node; + node = plugin; + + EXPECT_FALSE(node["enabled"].as()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetAfterFieldIfLoadAfterMetadataExists) { + PluginMetadata plugin(blankEsp); + plugin.LoadAfter({File(blankEsm)}); + YAML::Node node; + node = plugin; + + EXPECT_EQ(plugin.LoadAfter(), node["after"].as>()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetReqFieldIfRequirementsExist) { + PluginMetadata plugin(blankEsp); + plugin.Reqs({File(blankEsm)}); + YAML::Node node; + node = plugin; + + EXPECT_EQ(plugin.Reqs(), node["req"].as>()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetIncFieldIfIncompatibilitiesExist) { + PluginMetadata plugin(blankEsp); + plugin.Incs({File(blankEsm)}); + YAML::Node node; + node = plugin; + + EXPECT_EQ(plugin.Incs(), node["inc"].as>()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetMsgFieldIfMessagesExist) { + PluginMetadata plugin(blankEsp); + plugin.Messages({Message(Message::Type::say, "content")}); + YAML::Node node; + node = plugin; + + EXPECT_EQ(plugin.Messages(), node["msg"].as>()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetTagFieldIfTagsExist) { + PluginMetadata plugin(blankEsp); + plugin.Tags({Tag("Relev")}); + YAML::Node node; + node = plugin; + + EXPECT_EQ(plugin.Tags(), node["tag"].as>()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetDirtyFieldIfDirtyInfoExists) { + PluginMetadata plugin(blankEsp); + plugin.DirtyInfo({PluginDirtyInfo(5, 0, 1, 2, "utility")}); + YAML::Node node; + node = plugin; + + EXPECT_EQ(plugin.DirtyInfo(), node["dirty"].as>()); +} + +TEST_P(PluginMetadataTest, encodingAsYamlShouldSetUrlFieldIfLocationsExist) { + PluginMetadata plugin(blankEsp); + plugin.Locations({Location("http://www.example.com")}); + YAML::Node node; + node = plugin; + + EXPECT_EQ(plugin.Locations(), node["url"].as>()); +} + +TEST_P(PluginMetadataTest, decodingFromYamlShouldSetDefaultPriorityValuesIfNoneAreSpecified) { + YAML::Node node = YAML::Load("name: " + blankEsp); + PluginMetadata plugin = node.as(); + + EXPECT_EQ(blankEsp, plugin.Name()); + EXPECT_EQ(0, plugin.Priority()); + EXPECT_FALSE(plugin.IsPriorityExplicit()); + EXPECT_FALSE(plugin.IsPriorityGlobal()); +} + +TEST_P(PluginMetadataTest, decodingFromYamlShouldStoreAllGivenData) { + YAML::Node node = YAML::Load("name: 'Blank.esp'\n" + "priority: 5\n" + "enabled: false\n" + "after:\n" + " - 'Blank.esm'\n" + "req:\n" + " - 'Blank.esm'\n" + "inc:\n" + " - 'Blank.esm'\n" + "msg:\n" + " - type: say\n" + " content: 'content'\n" + "tag:\n" + " - Relev\n" + "dirty:\n" + " - crc: 0x5\n" + " util: 'utility'\n" + " udr: 1\n" + " nav: 2\n" + "url:\n" + " - 'http://www.example.com'"); + PluginMetadata plugin = node.as(); + + EXPECT_EQ("Blank.esp", plugin.Name()); + EXPECT_EQ(5, plugin.Priority()); + EXPECT_TRUE(plugin.IsPriorityExplicit()); + EXPECT_FALSE(plugin.IsPriorityGlobal()); + EXPECT_FALSE(plugin.Enabled()); + EXPECT_EQ(std::set({ + File("Blank.esm") + }), plugin.LoadAfter()); + EXPECT_EQ(std::set({ + File("Blank.esm") + }), plugin.Reqs()); + EXPECT_EQ(std::set({ + File("Blank.esm") + }), plugin.Incs()); + EXPECT_EQ(std::list({ + Message(Message::Type::say, "content") + }), plugin.Messages()); + EXPECT_EQ(std::set({ + Tag("Relev") + }), plugin.Tags()); + EXPECT_EQ(std::set({ + PluginDirtyInfo(5, 0, 1, 2, "utility") + }), plugin.DirtyInfo()); + EXPECT_EQ(std::set({ + Location("http://www.example.com") + }), plugin.Locations()); +} + +TEST_P(PluginMetadataTest, decodingFromYamlWithDirtyInfoInARegexPluginMetadataObjectShouldThrow) { + YAML::Node node = YAML::Load("name: 'Blank\\.esp'\n" + "dirty:\n" + " - crc: 0x5\n" + " util: 'utility'\n" + " udr: 1\n" + " nav: 2"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} + +TEST_P(PluginMetadataTest, decodingFromYamlWithAnInvalidRegexNameShouldThrow) { + YAML::Node node = YAML::Load("name: 'RagnvaldBook(Farengar(+Ragnvald)?)?\\.esp'\n" + "dirty:\n" + " - crc: 0x5\n" + " util: 'utility'\n" + " udr: 1\n" + " nav: 2"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} + +TEST_P(PluginMetadataTest, decodingFromAYamlScalarShouldThrow) { + YAML::Node node = YAML::Load("scalar"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} + +TEST_P(PluginMetadataTest, decodingFromAYamlListShouldThrow) { + YAML::Node node = YAML::Load("[0, 1, 2]"); + + EXPECT_THROW(node.as(), YAML::RepresentationException); +} +} } #endif diff --git a/src/tests/backend/metadata/tag_test.h b/src/tests/backend/metadata/tag_test.h index a140fafd..5c1018dd 100644 --- a/src/tests/backend/metadata/tag_test.h +++ b/src/tests/backend/metadata/tag_test.h @@ -22,170 +22,170 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_TAG -#define LOOT_TEST_BACKEND_METADATA_TAG +#ifndef LOOT_TESTS_BACKEND_METADATA_TAG_TEST +#define LOOT_TESTS_BACKEND_METADATA_TAG_TEST #include "backend/metadata/tag.h" #include namespace loot { - namespace test { - TEST(Tag, defaultConstructorShouldSetEmptyNameAndConditionStringsForATagAddition) { - Tag tag; +namespace test { +TEST(Tag, defaultConstructorShouldSetEmptyNameAndConditionStringsForATagAddition) { + Tag tag; - EXPECT_TRUE(tag.Name().empty()); - EXPECT_TRUE(tag.IsAddition()); - EXPECT_TRUE(tag.Condition().empty()); - } + EXPECT_TRUE(tag.Name().empty()); + EXPECT_TRUE(tag.IsAddition()); + EXPECT_TRUE(tag.Condition().empty()); +} - TEST(Tag, dataConstructorShouldSetFieldsToGivenValues) { - Tag tag("name", false, "condition"); +TEST(Tag, dataConstructorShouldSetFieldsToGivenValues) { + Tag tag("name", false, "condition"); - EXPECT_EQ("name", tag.Name()); - EXPECT_FALSE(tag.IsAddition()); - EXPECT_EQ("condition", tag.Condition()); - } + EXPECT_EQ("name", tag.Name()); + EXPECT_FALSE(tag.IsAddition()); + EXPECT_EQ("condition", tag.Condition()); +} - TEST(Tag, tagsWithCaseInsensitiveEqualNamesAndEqualAdditionStatesShouldBeEqual) { - Tag tag1("Name", true, "condition1"); - Tag tag2("name", true, "condition2"); +TEST(Tag, tagsWithCaseInsensitiveEqualNamesAndEqualAdditionStatesShouldBeEqual) { + Tag tag1("Name", true, "condition1"); + Tag tag2("name", true, "condition2"); - EXPECT_TRUE(tag1 == tag2); - } + EXPECT_TRUE(tag1 == tag2); +} - TEST(Tags, tagsWithUnequalNamesShouldNotBeEqual) { - Tag tag1("name1"); - Tag tag2("name2"); +TEST(Tags, tagsWithUnequalNamesShouldNotBeEqual) { + Tag tag1("name1"); + Tag tag2("name2"); - EXPECT_FALSE(tag1 == tag2); - } + EXPECT_FALSE(tag1 == tag2); +} - TEST(Tag, tagsWithUnequalAdditionStatesShouldNotBeEqual) { - Tag tag1("Name", true); - Tag tag2("name", false); +TEST(Tag, tagsWithUnequalAdditionStatesShouldNotBeEqual) { + Tag tag1("Name", true); + Tag tag2("name", false); - EXPECT_FALSE(tag1 == tag2); - } + EXPECT_FALSE(tag1 == tag2); +} - TEST(Tag, lessThanOperatorShouldCaseInsensitivelyLexicographicallyCompareNameStrings) { - Tag tag1("Name"); - Tag tag2("name"); +TEST(Tag, lessThanOperatorShouldCaseInsensitivelyLexicographicallyCompareNameStrings) { + Tag tag1("Name"); + Tag tag2("name"); - EXPECT_FALSE(tag1 < tag2); - EXPECT_FALSE(tag2 < tag1); + EXPECT_FALSE(tag1 < tag2); + EXPECT_FALSE(tag2 < tag1); - tag1 = Tag("name1"); - tag2 = Tag("name2"); + tag1 = Tag("name1"); + tag2 = Tag("name2"); - EXPECT_TRUE(tag1 < tag2); - EXPECT_FALSE(tag2 < tag1); - } + EXPECT_TRUE(tag1 < tag2); + EXPECT_FALSE(tag2 < tag1); +} - TEST(Tag, lessThanOperatorShouldTreatTagAdditionsAsBeingLessThanRemovals) { - Tag tag1("name", true); - Tag tag2("name", false); +TEST(Tag, lessThanOperatorShouldTreatTagAdditionsAsBeingLessThanRemovals) { + Tag tag1("name", true); + Tag tag2("name", false); - EXPECT_TRUE(tag1 < tag2); - EXPECT_FALSE(tag2 < tag1); - } + EXPECT_TRUE(tag1 < tag2); + EXPECT_FALSE(tag2 < tag1); +} - TEST(Tag, emittingAsYamlShouldOutputOnlyTheNameStringIfTheTagIsAnAdditionWithNoCondition) { - Tag tag("name1"); - YAML::Emitter emitter; - emitter << tag; +TEST(Tag, emittingAsYamlShouldOutputOnlyTheNameStringIfTheTagIsAnAdditionWithNoCondition) { + Tag tag("name1"); + YAML::Emitter emitter; + emitter << tag; - EXPECT_EQ(tag.Name(), emitter.c_str()); - } + EXPECT_EQ(tag.Name(), emitter.c_str()); +} - TEST(Tag, emittingAsYamlShouldOutputOnlyTheNameStringPrefixedWithAHyphenIfTheTagIsARemovalWithNoCondition) { - Tag tag("name1", false); - YAML::Emitter emitter; - emitter << tag; +TEST(Tag, emittingAsYamlShouldOutputOnlyTheNameStringPrefixedWithAHyphenIfTheTagIsARemovalWithNoCondition) { + Tag tag("name1", false); + YAML::Emitter emitter; + emitter << tag; - EXPECT_EQ("-" + tag.Name(), emitter.c_str()); - } + EXPECT_EQ("-" + tag.Name(), emitter.c_str()); +} - TEST(Tag, emittingAsYamlShouldOutputAMapIfTheTagHasACondition) { - Tag tag("name1", false, "condition1"); - YAML::Emitter emitter; - emitter << tag; +TEST(Tag, emittingAsYamlShouldOutputAMapIfTheTagHasACondition) { + Tag tag("name1", false, "condition1"); + YAML::Emitter emitter; + emitter << tag; - EXPECT_STREQ("name: -name1\ncondition: 'condition1'", emitter.c_str()); - } + EXPECT_STREQ("name: -name1\ncondition: 'condition1'", emitter.c_str()); +} - TEST(Tag, encodingAsYamlShouldOmitTheConditionFieldIfTheConditionStringIsEmpty) { - Tag tag; - YAML::Node node; - node = tag; +TEST(Tag, encodingAsYamlShouldOmitTheConditionFieldIfTheConditionStringIsEmpty) { + Tag tag; + YAML::Node node; + node = tag; - EXPECT_FALSE(node["condition"]); - } + EXPECT_FALSE(node["condition"]); +} - TEST(Tag, encodingAsYamlShouldOutputTheNameFieldCorrectly) { - Tag tag("name1"); - YAML::Node node; - node = tag; +TEST(Tag, encodingAsYamlShouldOutputTheNameFieldCorrectly) { + Tag tag("name1"); + YAML::Node node; + node = tag; - EXPECT_EQ(tag.Name(), node["name"].as()); - } + EXPECT_EQ(tag.Name(), node["name"].as()); +} - TEST(Tag, encodingAsYamlShouldOutputTheNameFieldWithAHyphenPrefixIfTheTagIsARemoval) { - Tag tag("name1", false); - YAML::Node node; - node = tag; +TEST(Tag, encodingAsYamlShouldOutputTheNameFieldWithAHyphenPrefixIfTheTagIsARemoval) { + Tag tag("name1", false); + YAML::Node node; + node = tag; - EXPECT_EQ("-" + tag.Name(), node["name"].as()); - } + EXPECT_EQ("-" + tag.Name(), node["name"].as()); +} - TEST(Tag, encodingAsYamlShouldOutputTheConditionFieldIfTheConditionStringIsNotEmpty) { - Tag tag("name1", true, "condition1"); - YAML::Node node; - node = tag; +TEST(Tag, encodingAsYamlShouldOutputTheConditionFieldIfTheConditionStringIsNotEmpty) { + Tag tag("name1", true, "condition1"); + YAML::Node node; + node = tag; - EXPECT_EQ(tag.Name(), node["name"].as()); - EXPECT_EQ(tag.Condition(), node["condition"].as()); - } + EXPECT_EQ(tag.Name(), node["name"].as()); + EXPECT_EQ(tag.Condition(), node["condition"].as()); +} - TEST(Tag, decodingFromYamlScalarShouldSetNameCorrectly) { - YAML::Node node = YAML::Load("name1"); - Tag tag = node.as(); +TEST(Tag, decodingFromYamlScalarShouldSetNameCorrectly) { + YAML::Node node = YAML::Load("name1"); + Tag tag = node.as(); - EXPECT_EQ("name1", tag.Name()); - EXPECT_TRUE(tag.IsAddition()); - EXPECT_EQ("", tag.Condition()); - } + EXPECT_EQ("name1", tag.Name()); + EXPECT_TRUE(tag.IsAddition()); + EXPECT_EQ("", tag.Condition()); +} - TEST(Tag, decodingFromYamlScalarShouldSetAdditionStateCorrectly) { - YAML::Node node = YAML::Load("-name1"); - Tag tag = node.as(); +TEST(Tag, decodingFromYamlScalarShouldSetAdditionStateCorrectly) { + YAML::Node node = YAML::Load("-name1"); + Tag tag = node.as(); - EXPECT_EQ("name1", tag.Name()); - EXPECT_FALSE(tag.IsAddition()); - EXPECT_EQ("", tag.Condition()); - } + EXPECT_EQ("name1", tag.Name()); + EXPECT_FALSE(tag.IsAddition()); + EXPECT_EQ("", tag.Condition()); +} - TEST(Tag, decodingFromYamlMapShouldSetDataCorrectly) { - YAML::Node node = YAML::Load("{name: name1, condition: 'file(\"Foo.esp\")'}"); - Tag tag = node.as(); +TEST(Tag, decodingFromYamlMapShouldSetDataCorrectly) { + YAML::Node node = YAML::Load("{name: name1, condition: 'file(\"Foo.esp\")'}"); + Tag tag = node.as(); - EXPECT_EQ("name1", tag.Name()); - EXPECT_TRUE(tag.IsAddition()); - EXPECT_EQ("file(\"Foo.esp\")", tag.Condition()); - } + EXPECT_EQ("name1", tag.Name()); + EXPECT_TRUE(tag.IsAddition()); + EXPECT_EQ("file(\"Foo.esp\")", tag.Condition()); +} - TEST(Tag, decodingFromYamlShouldThrowIfAnInvalidConditionIsGiven) { - YAML::Node node = YAML::Load("{name: name1, condition: invalid}"); +TEST(Tag, decodingFromYamlShouldThrowIfAnInvalidConditionIsGiven) { + YAML::Node node = YAML::Load("{name: name1, condition: invalid}"); - EXPECT_THROW(node.as(), YAML::RepresentationException); - } + EXPECT_THROW(node.as(), YAML::RepresentationException); +} - TEST(Tag, decodingFromYamlListShouldThrow) { - YAML::Node node = YAML::Load("[0, 1, 2]"); +TEST(Tag, decodingFromYamlListShouldThrow) { + YAML::Node node = YAML::Load("[0, 1, 2]"); - EXPECT_THROW(node.as(), YAML::RepresentationException); - } - } + EXPECT_THROW(node.as(), YAML::RepresentationException); +} +} } #endif diff --git a/src/tests/backend/metadata_list_test.h b/src/tests/backend/metadata_list_test.h index db61cf94..a661c5f5 100644 --- a/src/tests/backend/metadata_list_test.h +++ b/src/tests/backend/metadata_list_test.h @@ -22,298 +22,295 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_METADATA_LIST -#define LOOT_TEST_BACKEND_METADATA_LIST +#ifndef LOOT_TESTS_BACKEND_METADATA_LIST_TEST +#define LOOT_TESTS_BACKEND_METADATA_LIST_TEST #include "backend/metadata_list.h" -#include "backend/game/game.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class MetadataListTest : public BaseGameTest { - protected: - MetadataListTest() : - metadataPath("./testing-metadata/masterlist.yaml"), - savedMetadataPath("./testing-metadata/saved.masterlist.yaml"), - missingMetadataPath("./missing-metadata.yaml"), - invalidMetadataPaths({"./testing-metadata/invalid/non_unique.yaml"}) { - PluginMetadataToString = [](const PluginMetadata& plugin) { - return plugin.Name(); - }; - } +namespace test { +class MetadataListTest : public BaseGameTest { +protected: + MetadataListTest() : + metadataPath("./testing-metadata/masterlist.yaml"), + savedMetadataPath("./testing-metadata/saved.masterlist.yaml"), + missingMetadataPath("./missing-metadata.yaml"), + invalidMetadataPaths({"./testing-metadata/invalid/non_unique.yaml"}) {} - inline virtual void SetUp() { - BaseGameTest::SetUp(); + inline virtual void SetUp() { + BaseGameTest::SetUp(); - ASSERT_TRUE(boost::filesystem::exists(metadataPath)); - ASSERT_FALSE(boost::filesystem::exists(savedMetadataPath)); + ASSERT_TRUE(boost::filesystem::exists(metadataPath)); + ASSERT_FALSE(boost::filesystem::exists(savedMetadataPath)); - for (const auto& path : invalidMetadataPaths) { - ASSERT_TRUE(boost::filesystem::exists(path)); - } - } - - inline virtual void TearDown() { - BaseGameTest::TearDown(); - - ASSERT_TRUE(boost::filesystem::exists(metadataPath)); - ASSERT_NO_THROW(boost::filesystem::remove(savedMetadataPath)); - - for (const auto& path : invalidMetadataPaths) { - ASSERT_TRUE(boost::filesystem::exists(path)); - } - } - - const boost::filesystem::path metadataPath; - const boost::filesystem::path savedMetadataPath; - const boost::filesystem::path missingMetadataPath; - const std::vector invalidMetadataPaths; - - std::function PluginMetadataToString; - }; - - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - MetadataListTest, - ::testing::Values( - GameType::tes4)); - - TEST_P(MetadataListTest, loadShouldLoadGlobalMessages) { - MetadataList metadataList; - - EXPECT_NO_THROW(metadataList.Load(metadataPath)); - EXPECT_EQ(std::list({ - Message(Message::Type::say, "A global message."), - }), metadataList.Messages()); - } - - TEST_P(MetadataListTest, loadShouldLoadPluginMetadata) { - MetadataList metadataList; - - EXPECT_NO_THROW(metadataList.Load(metadataPath)); - // Non-regex plugins can be outputted in any order, and regex entries can - // match each other, so convert the list to a set of strings for - // comparison. - std::list result(metadataList.Plugins()); - std::set names; - std::transform(begin(result), - end(result), - std::insert_iterator>(names, begin(names)), - PluginMetadataToString); - - EXPECT_EQ(std::set({ - blankEsm, - blankEsp, - "Blank.+\\.esp", - "Blank.+(Different)?.*\\.esp", - }), names); - } - - TEST_P(MetadataListTest, loadShouldLoadBashTags) { - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - - EXPECT_EQ(std::set({ - "C.Climate", - "Relev" - }), metadataList.BashTags()); - } - - TEST_P(MetadataListTest, loadShouldThrowIfAnInvalidMetadataFileIsGiven) { - MetadataList ml; - for (const auto& path : invalidMetadataPaths) { - EXPECT_ANY_THROW(ml.Load(path)); - } - } - - TEST_P(MetadataListTest, loadShouldClearExistingDataIfAnInvalidMetadataFileIsGiven) { - MetadataList metadataList; - - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - ASSERT_FALSE(metadataList.Messages().empty()); - ASSERT_FALSE(metadataList.Plugins().empty()); - ASSERT_FALSE(metadataList.BashTags().empty()); - - EXPECT_ANY_THROW(metadataList.Load(blankEsm)); - EXPECT_TRUE(metadataList.Messages().empty()); - EXPECT_TRUE(metadataList.Plugins().empty()); - EXPECT_TRUE(metadataList.BashTags().empty()); - } - - TEST_P(MetadataListTest, loadShouldClearExistingDataIfAMissingMetadataFileIsGiven) { - MetadataList metadataList; - - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - ASSERT_FALSE(metadataList.Messages().empty()); - ASSERT_FALSE(metadataList.Plugins().empty()); - ASSERT_FALSE(metadataList.BashTags().empty()); - - EXPECT_ANY_THROW(metadataList.Load(missingMetadataPath)); - EXPECT_TRUE(metadataList.Messages().empty()); - EXPECT_TRUE(metadataList.Plugins().empty()); - EXPECT_TRUE(metadataList.BashTags().empty()); - } - - TEST_P(MetadataListTest, saveShouldWriteTheLoadedMetadataToTheGivenFilePath) { - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - - EXPECT_NO_THROW(metadataList.Save(savedMetadataPath)); - - EXPECT_TRUE(boost::filesystem::exists(savedMetadataPath)); - - // Check the new file contains the same metadata. - EXPECT_NO_THROW(metadataList.Load(savedMetadataPath)); - - EXPECT_EQ(std::set({ - "C.Climate", - "Relev" - }), metadataList.BashTags()); - - EXPECT_EQ(std::list({ - Message(Message::Type::say, "A global message."), - }), metadataList.Messages()); - - // Non-regex plugins can be outputted in any order, and regex entries can - // match each other, so convert the list to a set of strings for - // comparison. - std::list result(metadataList.Plugins()); - std::set names; - std::transform(begin(result), - end(result), - std::insert_iterator>(names, begin(names)), - PluginMetadataToString); - EXPECT_EQ(std::set({ - blankEsm, - blankEsp, - "Blank.+\\.esp", - "Blank.+(Different)?.*\\.esp", - }), names); - } - - TEST_P(MetadataListTest, clearShouldClearLoadedData) { - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - ASSERT_FALSE(metadataList.Messages().empty()); - ASSERT_FALSE(metadataList.Plugins().empty()); - ASSERT_FALSE(metadataList.BashTags().empty()); - - metadataList.clear(); - EXPECT_TRUE(metadataList.Messages().empty()); - EXPECT_TRUE(metadataList.Plugins().empty()); - EXPECT_TRUE(metadataList.BashTags().empty()); - } - - TEST_P(MetadataListTest, findPluginShouldReturnAnEmptyPluginObjectIfTheGivenPluginIsNotInTheMetadataList) { - MetadataList metadataList; - PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankDifferentEsm)); - - EXPECT_EQ(blankDifferentEsm, plugin.Name()); - EXPECT_TRUE(plugin.HasNameOnly()); - } - - TEST_P(MetadataListTest, findPluginShouldReturnTheMetadataObjectInTheMetadataListIfOneExistsForTheGivenPlugin) { - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - - PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankDifferentEsp)); - - EXPECT_EQ(blankDifferentEsp, plugin.Name()); - EXPECT_EQ(std::set({ - File(blankEsm), - }), plugin.LoadAfter()); - EXPECT_EQ(std::set({ - File(blankEsp), - }), plugin.Incs()); - } - - TEST_P(MetadataListTest, addPluginShouldStoreGivenSpecificPluginMetadata) { - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - ASSERT_TRUE(metadataList.FindPlugin(PluginMetadata(blankDifferentEsm)).HasNameOnly()); - - PluginMetadata plugin(blankDifferentEsm); - plugin.Priority(1000); - metadataList.AddPlugin(plugin); - - plugin = metadataList.FindPlugin(plugin); - - EXPECT_EQ(blankDifferentEsm, plugin.Name()); - EXPECT_EQ(1000, plugin.Priority()); - } - - TEST_P(MetadataListTest, addPluginShouldStoreGivenRegexPluginMetadata) { - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - - PluginMetadata plugin(".+Dependent\\.esp"); - plugin.Priority(-10); - metadataList.AddPlugin(plugin); - - plugin = metadataList.FindPlugin(PluginMetadata(blankPluginDependentEsp)); - - EXPECT_EQ(-10, plugin.Priority()); - } - - TEST_P(MetadataListTest, addPluginShouldThrowIfAMatchingPluginAlreadyExists) { - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - - PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankEsm)); - ASSERT_EQ(blankEsm, plugin.Name()); - ASSERT_FALSE(plugin.HasNameOnly()); - - ASSERT_ANY_THROW(metadataList.AddPlugin(PluginMetadata(blankEsm))); - } - - TEST_P(MetadataListTest, erasePluginShouldRemoveStoredMetadatForTheGivenPlugin) { - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - - PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankEsp)); - ASSERT_EQ(blankEsp, plugin.Name()); - ASSERT_FALSE(plugin.HasNameOnly()); - - metadataList.ErasePlugin(plugin); - - plugin = metadataList.FindPlugin(plugin); - EXPECT_EQ(blankEsp, plugin.Name()); - EXPECT_TRUE(plugin.HasNameOnly()); - } - - TEST_P(MetadataListTest, evalAllConditionsShouldEvaluateTheConditionsForThePluginsStoredInTeMetadataList) { - Game game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - - MetadataList metadataList; - ASSERT_NO_THROW(metadataList.Load(metadataPath)); - - PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankEsm)); - ASSERT_EQ(std::list({ - Message(Message::Type::warn, "This is a warning."), - Message(Message::Type::say, "This message should be removed when evaluating conditions."), - }), plugin.Messages()); - - plugin = metadataList.FindPlugin(PluginMetadata(blankEsp)); - ASSERT_EQ(blankEsp, plugin.Name()); - ASSERT_FALSE(plugin.HasNameOnly()); - - EXPECT_NO_THROW(metadataList.EvalAllConditions(game, Language::Code::english)); - - plugin = metadataList.FindPlugin(PluginMetadata(blankEsm)); - EXPECT_EQ(std::list({ - Message(Message::Type::warn, "This is a warning."), - }), plugin.Messages()); - - plugin = metadataList.FindPlugin(PluginMetadata(blankEsp)); - EXPECT_EQ(blankEsp, plugin.Name()); - EXPECT_TRUE(plugin.HasNameOnly()); - } + for (const auto& path : invalidMetadataPaths) { + ASSERT_TRUE(boost::filesystem::exists(path)); } + } + + inline virtual void TearDown() { + BaseGameTest::TearDown(); + + ASSERT_TRUE(boost::filesystem::exists(metadataPath)); + ASSERT_NO_THROW(boost::filesystem::remove(savedMetadataPath)); + + for (const auto& path : invalidMetadataPaths) { + ASSERT_TRUE(boost::filesystem::exists(path)); + } + } + + static std::string PluginMetadataToString(const PluginMetadata& metadata) { + return metadata.Name(); + } + + const boost::filesystem::path metadataPath; + const boost::filesystem::path savedMetadataPath; + const boost::filesystem::path missingMetadataPath; + const std::vector invalidMetadataPaths; +}; + +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + MetadataListTest, + ::testing::Values( + GameType::tes4)); + +TEST_P(MetadataListTest, loadShouldLoadGlobalMessages) { + MetadataList metadataList; + + EXPECT_NO_THROW(metadataList.Load(metadataPath)); + EXPECT_EQ(std::list({ + Message(Message::Type::say, "A global message."), + }), metadataList.Messages()); +} + +TEST_P(MetadataListTest, loadShouldLoadPluginMetadata) { + MetadataList metadataList; + + EXPECT_NO_THROW(metadataList.Load(metadataPath)); + // Non-regex plugins can be outputted in any order, and regex entries can + // match each other, so convert the list to a set of strings for + // comparison. + std::list result(metadataList.Plugins()); + std::set names; + std::transform(begin(result), + end(result), + std::insert_iterator>(names, begin(names)), + &MetadataListTest::PluginMetadataToString); + + EXPECT_EQ(std::set({ + blankEsm, + blankEsp, + "Blank.+\\.esp", + "Blank.+(Different)?.*\\.esp", + }), names); +} + +TEST_P(MetadataListTest, loadShouldLoadBashTags) { + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + + EXPECT_EQ(std::set({ + "C.Climate", + "Relev" + }), metadataList.BashTags()); +} + +TEST_P(MetadataListTest, loadShouldThrowIfAnInvalidMetadataFileIsGiven) { + MetadataList ml; + for (const auto& path : invalidMetadataPaths) { + EXPECT_ANY_THROW(ml.Load(path)); + } +} + +TEST_P(MetadataListTest, loadShouldClearExistingDataIfAnInvalidMetadataFileIsGiven) { + MetadataList metadataList; + + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + ASSERT_FALSE(metadataList.Messages().empty()); + ASSERT_FALSE(metadataList.Plugins().empty()); + ASSERT_FALSE(metadataList.BashTags().empty()); + + EXPECT_ANY_THROW(metadataList.Load(blankEsm)); + EXPECT_TRUE(metadataList.Messages().empty()); + EXPECT_TRUE(metadataList.Plugins().empty()); + EXPECT_TRUE(metadataList.BashTags().empty()); +} + +TEST_P(MetadataListTest, loadShouldClearExistingDataIfAMissingMetadataFileIsGiven) { + MetadataList metadataList; + + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + ASSERT_FALSE(metadataList.Messages().empty()); + ASSERT_FALSE(metadataList.Plugins().empty()); + ASSERT_FALSE(metadataList.BashTags().empty()); + + EXPECT_ANY_THROW(metadataList.Load(missingMetadataPath)); + EXPECT_TRUE(metadataList.Messages().empty()); + EXPECT_TRUE(metadataList.Plugins().empty()); + EXPECT_TRUE(metadataList.BashTags().empty()); +} + +TEST_P(MetadataListTest, saveShouldWriteTheLoadedMetadataToTheGivenFilePath) { + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + + EXPECT_NO_THROW(metadataList.Save(savedMetadataPath)); + + EXPECT_TRUE(boost::filesystem::exists(savedMetadataPath)); + + // Check the new file contains the same metadata. + EXPECT_NO_THROW(metadataList.Load(savedMetadataPath)); + + EXPECT_EQ(std::set({ + "C.Climate", + "Relev" + }), metadataList.BashTags()); + + EXPECT_EQ(std::list({ + Message(Message::Type::say, "A global message."), + }), metadataList.Messages()); + + // Non-regex plugins can be outputted in any order, and regex entries can + // match each other, so convert the list to a set of strings for + // comparison. + std::list result(metadataList.Plugins()); + std::set names; + std::transform(begin(result), + end(result), + std::insert_iterator>(names, begin(names)), + &MetadataListTest::PluginMetadataToString); + EXPECT_EQ(std::set({ + blankEsm, + blankEsp, + "Blank.+\\.esp", + "Blank.+(Different)?.*\\.esp", + }), names); +} + +TEST_P(MetadataListTest, clearShouldClearLoadedData) { + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + ASSERT_FALSE(metadataList.Messages().empty()); + ASSERT_FALSE(metadataList.Plugins().empty()); + ASSERT_FALSE(metadataList.BashTags().empty()); + + metadataList.Clear(); + EXPECT_TRUE(metadataList.Messages().empty()); + EXPECT_TRUE(metadataList.Plugins().empty()); + EXPECT_TRUE(metadataList.BashTags().empty()); +} + +TEST_P(MetadataListTest, findPluginShouldReturnAnEmptyPluginObjectIfTheGivenPluginIsNotInTheMetadataList) { + MetadataList metadataList; + PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankDifferentEsm)); + + EXPECT_EQ(blankDifferentEsm, plugin.Name()); + EXPECT_TRUE(plugin.HasNameOnly()); +} + +TEST_P(MetadataListTest, findPluginShouldReturnTheMetadataObjectInTheMetadataListIfOneExistsForTheGivenPlugin) { + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + + PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankDifferentEsp)); + + EXPECT_EQ(blankDifferentEsp, plugin.Name()); + EXPECT_EQ(std::set({ + File(blankEsm), + }), plugin.LoadAfter()); + EXPECT_EQ(std::set({ + File(blankEsp), + }), plugin.Incs()); +} + +TEST_P(MetadataListTest, addPluginShouldStoreGivenSpecificPluginMetadata) { + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + ASSERT_TRUE(metadataList.FindPlugin(PluginMetadata(blankDifferentEsm)).HasNameOnly()); + + PluginMetadata plugin(blankDifferentEsm); + plugin.Priority(1000); + metadataList.AddPlugin(plugin); + + plugin = metadataList.FindPlugin(plugin); + + EXPECT_EQ(blankDifferentEsm, plugin.Name()); + EXPECT_EQ(1000, plugin.Priority()); +} + +TEST_P(MetadataListTest, addPluginShouldStoreGivenRegexPluginMetadata) { + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + + PluginMetadata plugin(".+Dependent\\.esp"); + plugin.Priority(-10); + metadataList.AddPlugin(plugin); + + plugin = metadataList.FindPlugin(PluginMetadata(blankPluginDependentEsp)); + + EXPECT_EQ(-10, plugin.Priority()); +} + +TEST_P(MetadataListTest, addPluginShouldThrowIfAMatchingPluginAlreadyExists) { + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + + PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankEsm)); + ASSERT_EQ(blankEsm, plugin.Name()); + ASSERT_FALSE(plugin.HasNameOnly()); + + ASSERT_ANY_THROW(metadataList.AddPlugin(PluginMetadata(blankEsm))); +} + +TEST_P(MetadataListTest, erasePluginShouldRemoveStoredMetadatForTheGivenPlugin) { + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + + PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankEsp)); + ASSERT_EQ(blankEsp, plugin.Name()); + ASSERT_FALSE(plugin.HasNameOnly()); + + metadataList.ErasePlugin(plugin); + + plugin = metadataList.FindPlugin(plugin); + EXPECT_EQ(blankEsp, plugin.Name()); + EXPECT_TRUE(plugin.HasNameOnly()); +} + +TEST_P(MetadataListTest, evalAllConditionsShouldEvaluateTheConditionsForThePluginsStoredInTeMetadataList) { + Game game(GetParam()); + game.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game.Init(false, localPath)); + + MetadataList metadataList; + ASSERT_NO_THROW(metadataList.Load(metadataPath)); + + PluginMetadata plugin = metadataList.FindPlugin(PluginMetadata(blankEsm)); + ASSERT_EQ(std::list({ + Message(Message::Type::warn, "This is a warning."), + Message(Message::Type::say, "This message should be removed when evaluating conditions."), + }), plugin.Messages()); + + plugin = metadataList.FindPlugin(PluginMetadata(blankEsp)); + ASSERT_EQ(blankEsp, plugin.Name()); + ASSERT_FALSE(plugin.HasNameOnly()); + + EXPECT_NO_THROW(metadataList.EvalAllConditions(game, Language::Code::english)); + + plugin = metadataList.FindPlugin(PluginMetadata(blankEsm)); + EXPECT_EQ(std::list({ + Message(Message::Type::warn, "This is a warning."), + }), plugin.Messages()); + + plugin = metadataList.FindPlugin(PluginMetadata(blankEsp)); + EXPECT_EQ(blankEsp, plugin.Name()); + EXPECT_TRUE(plugin.HasNameOnly()); +} +} } #endif diff --git a/src/tests/backend/plugin/plugin_sorter_test.h b/src/tests/backend/plugin/plugin_sorter_test.h index bff2ea76..b6a2ffbd 100644 --- a/src/tests/backend/plugin/plugin_sorter_test.h +++ b/src/tests/backend/plugin/plugin_sorter_test.h @@ -22,221 +22,222 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_PLUGIN_SORTER -#define LOOT_TEST_BACKEND_PLUGIN_SORTER +#ifndef LOOT_TESTS_BACKEND_PLUGIN_PLUGIN_SORTER_TEST +#define LOOT_TESTS_BACKEND_PLUGIN_PLUGIN_SORTER_TEST #include "backend/plugin/plugin_sorter.h" + #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class PluginSorterTest : public BaseGameTest { - protected: - inline virtual void SetUp() { - BaseGameTest::SetUp(); +namespace test { +class PluginSorterTest : public BaseGameTest { +protected: + inline virtual void SetUp() { + BaseGameTest::SetUp(); - game = Game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - ASSERT_NO_THROW(game.Init(false, localPath)); - } + game_ = Game(GetParam()); + game_.SetGamePath(dataPath.parent_path()); + ASSERT_NO_THROW(game_.Init(false, localPath)); + } - Game game; - }; + Game game_; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - PluginSorterTest, - ::testing::Values( - GameType::tes4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + PluginSorterTest, + ::testing::Values( + GameType::tes4)); - TEST_P(PluginSorterTest, sortingWithNoLoadedPluginsShouldReturnAnEmptyList) { - PluginSorter sorter; - std::list sorted = sorter.Sort(game, Language::Code::english); +TEST_P(PluginSorterTest, sortingWithNoLoadedPluginsShouldReturnAnEmptyList) { + PluginSorter sorter; + std::list sorted = sorter.Sort(game_, Language::Code::english); - EXPECT_TRUE(sorted.empty()); - } + EXPECT_TRUE(sorted.empty()); +} - TEST_P(PluginSorterTest, sortingShouldNotMakeUnnecessaryChangesToAnExistingLoadOrder) { - ASSERT_NO_THROW(game.LoadPlugins(false)); +TEST_P(PluginSorterTest, sortingShouldNotMakeUnnecessaryChangesToAnExistingLoadOrder) { + ASSERT_NO_THROW(game_.LoadPlugins(false)); - PluginSorter ps; - std::list expectedSortedOrder = getLoadOrder(); + PluginSorter ps; + std::list expectedSortedOrder = getLoadOrder(); - std::list sorted = ps.Sort(game, Language::Code::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); + std::list sorted = ps.Sort(game_, Language::Code::english); + EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); - // Check stability. - sorted = ps.Sort(game, Language::Code::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); - } + // Check stability. + sorted = ps.Sort(game_, Language::Code::english); + EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); +} - TEST_P(PluginSorterTest, sortingShouldClearExistingGameMessages) { - ASSERT_NO_THROW(game.LoadPlugins(false)); - game.AppendMessage(Message(Message::Type::say, "1")); - ASSERT_FALSE(game.GetMessages().empty()); +TEST_P(PluginSorterTest, sortingShouldClearExistingGameMessages) { + ASSERT_NO_THROW(game_.LoadPlugins(false)); + game_.AppendMessage(Message(Message::Type::say, "1")); + ASSERT_FALSE(game_.GetMessages().empty()); - PluginSorter ps; - std::list sorted = ps.Sort(game, Language::Code::english); - EXPECT_TRUE(game.GetMessages().empty()); - } + PluginSorter ps; + std::list sorted = ps.Sort(game_, Language::Code::english); + EXPECT_TRUE(game_.GetMessages().empty()); +} - TEST_P(PluginSorterTest, failedSortShouldNotClearExistingGameMessages) { - ASSERT_NO_THROW(game.LoadPlugins(false)); - PluginMetadata plugin(blankEsm); - plugin.LoadAfter({File(blankMasterDependentEsm)}); - game.GetUserlist().AddPlugin(plugin); - game.AppendMessage(Message(Message::Type::say, "1")); - ASSERT_FALSE(game.GetMessages().empty()); +TEST_P(PluginSorterTest, failedSortShouldNotClearExistingGameMessages) { + ASSERT_NO_THROW(game_.LoadPlugins(false)); + PluginMetadata plugin(blankEsm); + plugin.LoadAfter({File(blankMasterDependentEsm)}); + game_.GetUserlist().AddPlugin(plugin); + game_.AppendMessage(Message(Message::Type::say, "1")); + ASSERT_FALSE(game_.GetMessages().empty()); - PluginSorter ps; - EXPECT_ANY_THROW(ps.Sort(game, Language::Code::english)); - EXPECT_FALSE(game.GetMessages().empty()); - } + PluginSorter ps; + EXPECT_ANY_THROW(ps.Sort(game_, Language::Code::english)); + EXPECT_FALSE(game_.GetMessages().empty()); +} - TEST_P(PluginSorterTest, sortingShouldEvaluateRelativePriorities) { - ASSERT_NO_THROW(game.LoadPlugins(false)); - PluginMetadata plugin(blankDifferentMasterDependentEsp); - plugin.Priority(-100000); - plugin.SetPriorityGlobal(true); - game.GetUserlist().AddPlugin(plugin); +TEST_P(PluginSorterTest, sortingShouldEvaluateRelativePriorities) { + ASSERT_NO_THROW(game_.LoadPlugins(false)); + PluginMetadata plugin(blankDifferentMasterDependentEsp); + plugin.Priority(-100000); + plugin.SetPriorityGlobal(true); + game_.GetUserlist().AddPlugin(plugin); - PluginSorter ps; - std::list expectedSortedOrder({ - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankDifferentMasterDependentEsp, - blankEsp, - blankDifferentEsp, - blankMasterDependentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }); + PluginSorter ps; + std::list expectedSortedOrder({ + masterFile, + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankDifferentMasterDependentEsp, + blankEsp, + blankDifferentEsp, + blankMasterDependentEsp, + blankPluginDependentEsp, + blankDifferentPluginDependentEsp, + }); - std::list sorted = ps.Sort(game, Language::Code::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); - } + std::list sorted = ps.Sort(game_, Language::Code::english); + EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); +} - TEST_P(PluginSorterTest, sortingWithPrioritiesShouldInheritRecursivelyRegardlessOfEvaluationOrder) { - ASSERT_NO_THROW(game.LoadPlugins(false)); +TEST_P(PluginSorterTest, sortingWithPrioritiesShouldInheritRecursivelyRegardlessOfEvaluationOrder) { + ASSERT_NO_THROW(game_.LoadPlugins(false)); - // Set Blank.esp's priority. - PluginMetadata plugin(blankEsp); - plugin.Priority(2); - game.GetUserlist().AddPlugin(plugin); + // Set Blank.esp's priority. + PluginMetadata plugin(blankEsp); + plugin.Priority(2); + game_.GetUserlist().AddPlugin(plugin); - // Load Blank - Master Dependent.esp after Blank.esp so that it - // inherits Blank.esp's priority. - plugin = PluginMetadata(blankMasterDependentEsp); - plugin.LoadAfter({ - File(blankEsp), - }); - game.GetUserlist().AddPlugin(plugin); + // Load Blank - Master Dependent.esp after Blank.esp so that it + // inherits Blank.esp's priority. + plugin = PluginMetadata(blankMasterDependentEsp); + plugin.LoadAfter({ + File(blankEsp), + }); + game_.GetUserlist().AddPlugin(plugin); - // Load Blank - Different.esp after Blank - Master Dependent.esp, so - // that it inherits its inherited priority. - plugin = PluginMetadata(blankDifferentEsp); - plugin.LoadAfter({ - File(blankMasterDependentEsp), - }); - game.GetUserlist().AddPlugin(plugin); + // Load Blank - Different.esp after Blank - Master Dependent.esp, so + // that it inherits its inherited priority. + plugin = PluginMetadata(blankDifferentEsp); + plugin.LoadAfter({ + File(blankMasterDependentEsp), + }); + game_.GetUserlist().AddPlugin(plugin); - // Set Blank - Different Master Dependent.esp to have a higher priority - // than 0 but lower than Blank.esp. Need to also make it a global priority - // because it doesn't otherwise conflict with the other plugins. - plugin = PluginMetadata(blankDifferentMasterDependentEsp); - plugin.Priority(1); - plugin.SetPriorityGlobal(true); - game.GetUserlist().AddPlugin(plugin); + // Set Blank - Different Master Dependent.esp to have a higher priority + // than 0 but lower than Blank.esp. Need to also make it a global priority + // because it doesn't otherwise conflict with the other plugins. + plugin = PluginMetadata(blankDifferentMasterDependentEsp); + plugin.Priority(1); + plugin.SetPriorityGlobal(true); + game_.GetUserlist().AddPlugin(plugin); - PluginSorter ps; - std::list expectedSortedOrder({ - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankDifferentMasterDependentEsp, - blankEsp, - blankMasterDependentEsp, - blankDifferentEsp, - blankPluginDependentEsp, - blankDifferentPluginDependentEsp, - }); + PluginSorter ps; + std::list expectedSortedOrder({ + masterFile, + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankDifferentMasterDependentEsp, + blankEsp, + blankMasterDependentEsp, + blankDifferentEsp, + blankPluginDependentEsp, + blankDifferentPluginDependentEsp, + }); - std::list sorted = ps.Sort(game, Language::Code::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); - } + std::list sorted = ps.Sort(game_, Language::Code::english); + EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); +} - TEST_P(PluginSorterTest, sortingShouldUseLoadAfterMetadataWhenDecidingRelativePluginPositions) { - ASSERT_NO_THROW(game.LoadPlugins(false)); - PluginMetadata plugin(blankEsp); - plugin.LoadAfter({ - File(blankDifferentEsp), - File(blankDifferentPluginDependentEsp), - }); - game.GetUserlist().AddPlugin(plugin); +TEST_P(PluginSorterTest, sortingShouldUseLoadAfterMetadataWhenDecidingRelativePluginPositions) { + ASSERT_NO_THROW(game_.LoadPlugins(false)); + PluginMetadata plugin(blankEsp); + plugin.LoadAfter({ + File(blankDifferentEsp), + File(blankDifferentPluginDependentEsp), + }); + game_.GetUserlist().AddPlugin(plugin); - PluginSorter ps; - std::list expectedSortedOrder({ - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankPluginDependentEsp, - }); + PluginSorter ps; + std::list expectedSortedOrder({ + masterFile, + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankDifferentEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankDifferentPluginDependentEsp, + blankEsp, + blankPluginDependentEsp, + }); - std::list sorted = ps.Sort(game, Language::Code::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); - } + std::list sorted = ps.Sort(game_, Language::Code::english); + EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); +} - TEST_P(PluginSorterTest, sortingShouldUseRequirementMetadataWhenDecidingRelativePluginPositions) { - ASSERT_NO_THROW(game.LoadPlugins(false)); - PluginMetadata plugin(blankEsp); - plugin.Reqs({ - File(blankDifferentEsp), - File(blankDifferentPluginDependentEsp), - }); - game.GetUserlist().AddPlugin(plugin); +TEST_P(PluginSorterTest, sortingShouldUseRequirementMetadataWhenDecidingRelativePluginPositions) { + ASSERT_NO_THROW(game_.LoadPlugins(false)); + PluginMetadata plugin(blankEsp); + plugin.Reqs({ + File(blankDifferentEsp), + File(blankDifferentPluginDependentEsp), + }); + game_.GetUserlist().AddPlugin(plugin); - PluginSorter ps; - std::list expectedSortedOrder({ - masterFile, - blankEsm, - blankDifferentEsm, - blankMasterDependentEsm, - blankDifferentMasterDependentEsm, - blankDifferentEsp, - blankMasterDependentEsp, - blankDifferentMasterDependentEsp, - blankDifferentPluginDependentEsp, - blankEsp, - blankPluginDependentEsp, - }); + PluginSorter ps; + std::list expectedSortedOrder({ + masterFile, + blankEsm, + blankDifferentEsm, + blankMasterDependentEsm, + blankDifferentMasterDependentEsm, + blankDifferentEsp, + blankMasterDependentEsp, + blankDifferentMasterDependentEsp, + blankDifferentPluginDependentEsp, + blankEsp, + blankPluginDependentEsp, + }); - std::list sorted = ps.Sort(game, Language::Code::english); - EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); - } + std::list sorted = ps.Sort(game_, Language::Code::english); + EXPECT_TRUE(std::equal(begin(sorted), end(sorted), begin(expectedSortedOrder))); +} - TEST_P(PluginSorterTest, sortingShouldThrowIfACyclicInteractionIsEncountered) { - ASSERT_NO_THROW(game.LoadPlugins(false)); - PluginMetadata plugin(blankEsm); - plugin.LoadAfter({File(blankMasterDependentEsm)}); - game.GetUserlist().AddPlugin(plugin); +TEST_P(PluginSorterTest, sortingShouldThrowIfACyclicInteractionIsEncountered) { + ASSERT_NO_THROW(game_.LoadPlugins(false)); + PluginMetadata plugin(blankEsm); + plugin.LoadAfter({File(blankMasterDependentEsm)}); + game_.GetUserlist().AddPlugin(plugin); - PluginSorter ps; - EXPECT_ANY_THROW(ps.Sort(game, Language::Code::english)); - } - } + PluginSorter ps; + EXPECT_ANY_THROW(ps.Sort(game_, Language::Code::english)); +} +} } #endif diff --git a/src/tests/backend/plugin/plugin_test.h b/src/tests/backend/plugin/plugin_test.h index 79f27674..b3cebeb0 100644 --- a/src/tests/backend/plugin/plugin_test.h +++ b/src/tests/backend/plugin/plugin_test.h @@ -22,312 +22,314 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_BACKEND_PLUGIN -#define LOOT_TEST_BACKEND_PLUGIN +#ifndef LOOT_TESTS_BACKEND_PLUGIN_PLUGIN_TEST +#define LOOT_TESTS_BACKEND_PLUGIN_PLUGIN_TEST #include "backend/plugin/plugin.h" + +#include "backend/game/game.h" #include "tests/backend/base_game_test.h" namespace loot { - namespace test { - class PluginTest : public BaseGameTest { - protected: - PluginTest() : - emptyFile("EmptyFile.esm"), - nonPluginFile("NotAPlugin.esm"), - blankArchive("Blank" + Game(GetParam()).GetArchiveFileExtension()), - blankSuffixArchive("Blank - Different - suffix" + Game(GetParam()).GetArchiveFileExtension()) {} +namespace test { +class PluginTest : public BaseGameTest { +protected: + PluginTest() : + emptyFile("EmptyFile.esm"), + nonPluginFile("NotAPlugin.esm"), + blankArchive("Blank" + Game(GetParam()).GetArchiveFileExtension()), + blankSuffixArchive("Blank - Different - suffix" + Game(GetParam()).GetArchiveFileExtension()) {} - inline void SetUp() { - BaseGameTest::SetUp(); + void SetUp() { + BaseGameTest::SetUp(); - game = Game(GetParam()); - game.SetGamePath(dataPath.parent_path()); - game.Init(false, localPath); + game_ = Game(GetParam()); + game_.SetGamePath(dataPath.parent_path()); + game_.Init(false, localPath); - // Write out an empty file. - boost::filesystem::ofstream out(dataPath / emptyFile); - out.close(); - ASSERT_TRUE(boost::filesystem::exists(dataPath / emptyFile)); + // Write out an empty file. + boost::filesystem::ofstream out(dataPath / emptyFile); + out.close(); + ASSERT_TRUE(boost::filesystem::exists(dataPath / emptyFile)); - // Write out an non-empty, non-plugin file. - out.open(dataPath / nonPluginFile); - out << "This isn't a valid plugin file."; - out.close(); - ASSERT_TRUE(boost::filesystem::exists(dataPath / nonPluginFile)); + // Write out an non-empty, non-plugin file. + out.open(dataPath / nonPluginFile); + out << "This isn't a valid plugin file."; + out.close(); + ASSERT_TRUE(boost::filesystem::exists(dataPath / nonPluginFile)); - // Create dummy archive files. - out.open(dataPath / blankArchive); - out.close(); - out.open(dataPath / blankSuffixArchive); - out.close(); - } + // Create dummy archive files. + out.open(dataPath / blankArchive); + out.close(); + out.open(dataPath / blankSuffixArchive); + out.close(); + } - inline void TearDown() { - BaseGameTest::TearDown(); + void TearDown() { + BaseGameTest::TearDown(); - boost::filesystem::remove(dataPath / emptyFile); - boost::filesystem::remove(dataPath / nonPluginFile); - boost::filesystem::remove(dataPath / blankArchive); - boost::filesystem::remove(dataPath / blankSuffixArchive); - } + boost::filesystem::remove(dataPath / emptyFile); + boost::filesystem::remove(dataPath / nonPluginFile); + boost::filesystem::remove(dataPath / blankArchive); + boost::filesystem::remove(dataPath / blankSuffixArchive); + } - Game game; + Game game_; - const std::string emptyFile; - const std::string nonPluginFile; - const std::string blankArchive; - const std::string blankSuffixArchive; - }; + const std::string emptyFile; + const std::string nonPluginFile; + const std::string blankArchive; + const std::string blankSuffixArchive; +}; - // Pass an empty first argument, as it's a prefix for the test instantation, - // but we only have the one so no prefix is necessary. - INSTANTIATE_TEST_CASE_P(, - PluginTest, - ::testing::Values( - GameType::tes4, - GameType::tes5, - GameType::fo3, - GameType::fonv, - GameType::fo4)); +// Pass an empty first argument, as it's a prefix for the test instantation, +// but we only have the one so no prefix is necessary. +INSTANTIATE_TEST_CASE_P(, + PluginTest, + ::testing::Values( + GameType::tes4, + GameType::tes5, + GameType::fo3, + GameType::fonv, + GameType::fo4)); - TEST_P(PluginTest, loadingHeaderOnlyShouldReadHeaderData) { - Plugin plugin(game, blankEsm, true); +TEST_P(PluginTest, loadingHeaderOnlyShouldReadHeaderData) { + Plugin plugin(game_, blankEsm, true); - EXPECT_EQ(blankEsm, plugin.Name()); - EXPECT_TRUE(plugin.getMasters().empty()); - EXPECT_TRUE(plugin.isMasterFile()); - EXPECT_FALSE(plugin.IsEmpty()); - EXPECT_EQ("v5.0", plugin.getDescription()); - } + EXPECT_EQ(blankEsm, plugin.Name()); + EXPECT_TRUE(plugin.getMasters().empty()); + EXPECT_TRUE(plugin.isMasterFile()); + EXPECT_FALSE(plugin.IsEmpty()); + EXPECT_EQ("v5.0", plugin.getDescription()); +} - TEST_P(PluginTest, loadingHeaderOnlyShouldNotReadFieldsOrCalculateCrc) { - Plugin plugin(game, blankEsm, true); +TEST_P(PluginTest, loadingHeaderOnlyShouldNotReadFieldsOrCalculateCrc) { + Plugin plugin(game_, blankEsm, true); - EXPECT_TRUE(plugin.getFormIds().empty()); - EXPECT_EQ(0, plugin.Crc()); - } + EXPECT_TRUE(plugin.getFormIds().empty()); + EXPECT_EQ(0, plugin.Crc()); +} - TEST_P(PluginTest, loadingWholePluginShouldReadHeaderData) { - Plugin plugin(game, blankEsm, true); +TEST_P(PluginTest, loadingWholePluginShouldReadHeaderData) { + Plugin plugin(game_, blankEsm, true); - EXPECT_EQ(blankEsm, plugin.Name()); - EXPECT_TRUE(plugin.getMasters().empty()); - EXPECT_TRUE(plugin.isMasterFile()); - EXPECT_FALSE(plugin.IsEmpty()); - EXPECT_EQ("v5.0", plugin.getDescription()); - } + EXPECT_EQ(blankEsm, plugin.Name()); + EXPECT_TRUE(plugin.getMasters().empty()); + EXPECT_TRUE(plugin.isMasterFile()); + EXPECT_FALSE(plugin.IsEmpty()); + EXPECT_EQ("v5.0", plugin.getDescription()); +} - TEST_P(PluginTest, loadingWholePluginShouldReadFields) { - Plugin plugin(game, blankMasterDependentEsm, false); +TEST_P(PluginTest, loadingWholePluginShouldReadFields) { + Plugin plugin(game_, blankMasterDependentEsm, false); - EXPECT_EQ(4, plugin.NumOverrideFormIDs()); - } + EXPECT_EQ(4, plugin.NumOverrideFormIDs()); +} - TEST_P(PluginTest, loadingWholePluginShouldCalculateCrc) { - Plugin plugin(game, blankEsm, false); +TEST_P(PluginTest, loadingWholePluginShouldCalculateCrc) { + Plugin plugin(game_, blankEsm, false); - EXPECT_EQ(blankEsmCrc, plugin.Crc()); - } + EXPECT_EQ(blankEsmCrc, plugin.Crc()); +} - TEST_P(PluginTest, loadingANonMasterPluginShouldReadTheMasterFlagAsFalse) { - Plugin plugin(game, blankMasterDependentEsp, true); +TEST_P(PluginTest, loadingANonMasterPluginShouldReadTheMasterFlagAsFalse) { + Plugin plugin(game_, blankMasterDependentEsp, true); - EXPECT_FALSE(plugin.isMasterFile()); - } + EXPECT_FALSE(plugin.isMasterFile()); +} - TEST_P(PluginTest, loadingAPluginWithMastersShouldReadThemCorrectly) { - Plugin plugin(game, blankMasterDependentEsp, true); +TEST_P(PluginTest, loadingAPluginWithMastersShouldReadThemCorrectly) { + Plugin plugin(game_, blankMasterDependentEsp, true); - EXPECT_EQ(std::vector({ - blankEsm - }), plugin.getMasters()); - } + EXPECT_EQ(std::vector({ + blankEsm + }), plugin.getMasters()); +} - TEST_P(PluginTest, loadsArchiveForAnArchiveThatExactlyMatchesAnEsmFileBasenameShouldReturnTrueForAllGamesExceptOblivion) { - bool loadsArchive = Plugin(game, blankEsm, true).LoadsArchive(); +TEST_P(PluginTest, loadsArchiveForAnArchiveThatExactlyMatchesAnEsmFileBasenameShouldReturnTrueForAllGamesExceptOblivion) { + bool loadsArchive = Plugin(game_, blankEsm, true).LoadsArchive(); - if (GetParam() == GameType::tes4) - EXPECT_FALSE(loadsArchive); - else - EXPECT_TRUE(loadsArchive); - } + if (GetParam() == GameType::tes4) + EXPECT_FALSE(loadsArchive); + else + EXPECT_TRUE(loadsArchive); +} - TEST_P(PluginTest, loadsArchiveForAnArchiveThatExactlyMatchesAnEspFileBasenameShouldReturnTrue) { - EXPECT_TRUE(Plugin(game, blankEsp, true).LoadsArchive()); - } +TEST_P(PluginTest, loadsArchiveForAnArchiveThatExactlyMatchesAnEspFileBasenameShouldReturnTrue) { + EXPECT_TRUE(Plugin(game_, blankEsp, true).LoadsArchive()); +} - TEST_P(PluginTest, loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEsmFileBasenameShouldReturnTrueForAllGamesExceptOblivionAndSkyrim) { - bool loadsArchive = Plugin(game, blankDifferentEsm, true).LoadsArchive(); +TEST_P(PluginTest, loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEsmFileBasenameShouldReturnTrueForAllGamesExceptOblivionAndSkyrim) { + bool loadsArchive = Plugin(game_, blankDifferentEsm, true).LoadsArchive(); - if (GetParam() == GameType::tes4 || GetParam() == GameType::tes5) - EXPECT_FALSE(loadsArchive); - else - EXPECT_TRUE(loadsArchive); - } + if (GetParam() == GameType::tes4 || GetParam() == GameType::tes5) + EXPECT_FALSE(loadsArchive); + else + EXPECT_TRUE(loadsArchive); +} - TEST_P(PluginTest, loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEspFileBasenameShouldReturnTrueForAllGamesExceptSkyrim) { - bool loadsArchive = Plugin(game, blankDifferentEsp, true).LoadsArchive(); +TEST_P(PluginTest, loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEspFileBasenameShouldReturnTrueForAllGamesExceptSkyrim) { + bool loadsArchive = Plugin(game_, blankDifferentEsp, true).LoadsArchive(); - if (GetParam() == GameType::tes5) - EXPECT_FALSE(loadsArchive); - else - EXPECT_TRUE(loadsArchive); - } + if (GetParam() == GameType::tes5) + EXPECT_FALSE(loadsArchive); + else + EXPECT_TRUE(loadsArchive); +} - TEST_P(PluginTest, loadsArchiveShouldReturnFalseForAPluginThatDoesNotLoadAnArchive) { - EXPECT_FALSE(Plugin(game, blankMasterDependentEsp, true).LoadsArchive()); - } +TEST_P(PluginTest, loadsArchiveShouldReturnFalseForAPluginThatDoesNotLoadAnArchive) { + EXPECT_FALSE(Plugin(game_, blankMasterDependentEsp, true).LoadsArchive()); +} - TEST_P(PluginTest, loadsArchiveShouldReturnFalseForAPluginWithARegexFilename) { - EXPECT_FALSE(Plugin(game, "Blank\\.esp", true).LoadsArchive()); - } +TEST_P(PluginTest, loadsArchiveShouldReturnFalseForAPluginWithARegexFilename) { + EXPECT_FALSE(Plugin(game_, "Blank\\.esp", true).LoadsArchive()); +} - TEST_P(PluginTest, isValidShouldReturnTrueForAValidPlugin) { - EXPECT_TRUE(Plugin::IsValid(blankEsm, game)); - } +TEST_P(PluginTest, isValidShouldReturnTrueForAValidPlugin) { + EXPECT_TRUE(Plugin::IsValid(blankEsm, game_)); +} - TEST_P(PluginTest, isValidShouldReturnFalseForANonPluginFile) { - EXPECT_FALSE(Plugin::IsValid(nonPluginFile, game)); - } +TEST_P(PluginTest, isValidShouldReturnFalseForANonPluginFile) { + EXPECT_FALSE(Plugin::IsValid(nonPluginFile, game_)); +} - TEST_P(PluginTest, isValidShouldReturnFalseForAnEmptyFile) { - EXPECT_FALSE(Plugin::IsValid(emptyFile, game)); - } +TEST_P(PluginTest, isValidShouldReturnFalseForAnEmptyFile) { + EXPECT_FALSE(Plugin::IsValid(emptyFile, game_)); +} - TEST_P(PluginTest, isActiveShouldReturnTrueForAPluginThatIsActive) { - EXPECT_TRUE(Plugin(game, blankEsm, true).IsActive()); - } +TEST_P(PluginTest, isActiveShouldReturnTrueForAPluginThatIsActive) { + EXPECT_TRUE(Plugin(game_, blankEsm, true).IsActive()); +} - TEST_P(PluginTest, isActiveShouldReturnFalseForAPluginThatIsNotActive) { - EXPECT_FALSE(Plugin(game, blankEsp, true).IsActive()); - } +TEST_P(PluginTest, isActiveShouldReturnFalseForAPluginThatIsNotActive) { + EXPECT_FALSE(Plugin(game_, blankEsp, true).IsActive()); +} - TEST_P(PluginTest, lessThanOperatorShouldUseCaseInsensitiveLexicographicalNameComparison) { - Plugin plugin1(game, "Blank.esp", true); - Plugin plugin2(game, "blank.esp", true); +TEST_P(PluginTest, lessThanOperatorShouldUseCaseInsensitiveLexicographicalNameComparison) { + Plugin plugin1(game_, "Blank.esp", true); + Plugin plugin2(game_, "blank.esp", true); - EXPECT_FALSE(plugin1 < plugin2); - EXPECT_FALSE(plugin2 < plugin1); + EXPECT_FALSE(plugin1 < plugin2); + EXPECT_FALSE(plugin2 < plugin1); - plugin1 = Plugin(game, "blank.esm", true); - plugin2 = Plugin(game, "blank.esp", true); + plugin1 = Plugin(game_, "blank.esm", true); + plugin2 = Plugin(game_, "blank.esp", true); - EXPECT_TRUE(plugin1 < plugin2); - EXPECT_FALSE(plugin2 < plugin1); - } + EXPECT_TRUE(plugin1 < plugin2); + EXPECT_FALSE(plugin2 < plugin1); +} - TEST_P(PluginTest, doFormIDsOverlapShouldReturnFalseForTwoPluginsWithOnlyHeadersLoaded) { - Plugin plugin1(game, blankEsm, true); - Plugin plugin2(game, blankMasterDependentEsm, true); +TEST_P(PluginTest, doFormIDsOverlapShouldReturnFalseForTwoPluginsWithOnlyHeadersLoaded) { + Plugin plugin1(game_, blankEsm, true); + Plugin plugin2(game_, blankMasterDependentEsm, true); - EXPECT_FALSE(plugin1.DoFormIDsOverlap(plugin2)); - EXPECT_FALSE(plugin2.DoFormIDsOverlap(plugin1)); - } + EXPECT_FALSE(plugin1.DoFormIDsOverlap(plugin2)); + EXPECT_FALSE(plugin2.DoFormIDsOverlap(plugin1)); +} - TEST_P(PluginTest, doFormIDsOverlapShouldReturnFalseIfThePluginsHaveUnrelatedRecords) { - Plugin plugin1(game, blankEsm, false); - Plugin plugin2(game, blankEsp, false); +TEST_P(PluginTest, doFormIDsOverlapShouldReturnFalseIfThePluginsHaveUnrelatedRecords) { + Plugin plugin1(game_, blankEsm, false); + Plugin plugin2(game_, blankEsp, false); - EXPECT_FALSE(plugin1.DoFormIDsOverlap(plugin2)); - EXPECT_FALSE(plugin2.DoFormIDsOverlap(plugin1)); - } + EXPECT_FALSE(plugin1.DoFormIDsOverlap(plugin2)); + EXPECT_FALSE(plugin2.DoFormIDsOverlap(plugin1)); +} - TEST_P(PluginTest, doFormIDsOverlapShouldReturnTrueIfOnePluginOverridesTheOthersRecords) { - Plugin plugin1(game, blankEsm, false); - Plugin plugin2(game, blankMasterDependentEsm, false); +TEST_P(PluginTest, doFormIDsOverlapShouldReturnTrueIfOnePluginOverridesTheOthersRecords) { + Plugin plugin1(game_, blankEsm, false); + Plugin plugin2(game_, blankMasterDependentEsm, false); - EXPECT_TRUE(plugin1.DoFormIDsOverlap(plugin2)); - EXPECT_TRUE(plugin2.DoFormIDsOverlap(plugin1)); - } + EXPECT_TRUE(plugin1.DoFormIDsOverlap(plugin2)); + EXPECT_TRUE(plugin2.DoFormIDsOverlap(plugin1)); +} - TEST_P(PluginTest, overlapFormIDsShouldReturnAnEmptySetForTwoPluginsWithOnlyHeadersLoaded) { - Plugin plugin1(game, blankEsm, true); - Plugin plugin2(game, blankMasterDependentEsm, true); +TEST_P(PluginTest, overlapFormIDsShouldReturnAnEmptySetForTwoPluginsWithOnlyHeadersLoaded) { + Plugin plugin1(game_, blankEsm, true); + Plugin plugin2(game_, blankMasterDependentEsm, true); - EXPECT_TRUE(plugin1.OverlapFormIDs(plugin2).empty()); - EXPECT_TRUE(plugin2.OverlapFormIDs(plugin1).empty()); - } + EXPECT_TRUE(plugin1.OverlapFormIDs(plugin2).empty()); + EXPECT_TRUE(plugin2.OverlapFormIDs(plugin1).empty()); +} - TEST_P(PluginTest, overlapFormIDsShouldReturnAnEmptySetIfThePluginsHaveUnrelatedRecords) { - Plugin plugin1(game, blankEsm, false); - Plugin plugin2(game, blankEsp, false); +TEST_P(PluginTest, overlapFormIDsShouldReturnAnEmptySetIfThePluginsHaveUnrelatedRecords) { + Plugin plugin1(game_, blankEsm, false); + Plugin plugin2(game_, blankEsp, false); - EXPECT_TRUE(plugin1.OverlapFormIDs(plugin2).empty()); - EXPECT_TRUE(plugin2.OverlapFormIDs(plugin1).empty()); - } + EXPECT_TRUE(plugin1.OverlapFormIDs(plugin2).empty()); + EXPECT_TRUE(plugin2.OverlapFormIDs(plugin1).empty()); +} - TEST_P(PluginTest, overlapFormIDsShouldReturnTheFormIDsOfRecordsAddedByOnePluginAndOverriddenByTheOther) { - Plugin plugin1(game, blankEsm, false); - Plugin plugin2(game, blankMasterDependentEsm, false); +TEST_P(PluginTest, overlapFormIDsShouldReturnTheFormIDsOfRecordsAddedByOnePluginAndOverriddenByTheOther) { + Plugin plugin1(game_, blankEsm, false); + Plugin plugin2(game_, blankMasterDependentEsm, false); - std::set expectedFormIds({ - libespm::FormId(blankEsm, std::vector(), 0xCF0), - libespm::FormId(blankEsm, std::vector(), 0xCF1), - libespm::FormId(blankEsm, std::vector(), 0xCF2), - libespm::FormId(blankEsm, std::vector(), 0xCF3), - }); - EXPECT_EQ(expectedFormIds, plugin1.OverlapFormIDs(plugin2)); - EXPECT_EQ(expectedFormIds, plugin2.OverlapFormIDs(plugin1)); - } + std::set expectedFormIds({ + libespm::FormId(blankEsm, std::vector(), 0xCF0), + libespm::FormId(blankEsm, std::vector(), 0xCF1), + libespm::FormId(blankEsm, std::vector(), 0xCF2), + libespm::FormId(blankEsm, std::vector(), 0xCF3), + }); + EXPECT_EQ(expectedFormIds, plugin1.OverlapFormIDs(plugin2)); + EXPECT_EQ(expectedFormIds, plugin2.OverlapFormIDs(plugin1)); +} - TEST_P(PluginTest, checkInstallValidityShouldCheckThatRequirementsArePresent) { - Plugin plugin(game, blankEsm, false); - plugin.Reqs({ - File(missingEsp), - File(blankEsp), - }); +TEST_P(PluginTest, checkInstallValidityShouldCheckThatRequirementsArePresent) { + Plugin plugin(game_, blankEsm, false); + plugin.Reqs({ + File(missingEsp), + File(blankEsp), + }); - EXPECT_FALSE(plugin.CheckInstallValidity(game)); - EXPECT_EQ(std::list({ - Message(Message::Type::error, "This plugin requires \"" + missingEsp + "\" to be installed, but it is missing."), - }), plugin.Messages()); - } + EXPECT_FALSE(plugin.CheckInstallValidity(game_)); + EXPECT_EQ(std::list({ + Message(Message::Type::error, "This plugin requires \"" + missingEsp + "\" to be installed, but it is missing."), + }), plugin.Messages()); +} - TEST_P(PluginTest, checkInstallValidityShouldCheckThatIncompatibilitiesAreAbsent) { - Plugin plugin(game, blankEsm, false); - plugin.Incs({ - File(missingEsp), - File(masterFile), - }); +TEST_P(PluginTest, checkInstallValidityShouldCheckThatIncompatibilitiesAreAbsent) { + Plugin plugin(game_, blankEsm, false); + plugin.Incs({ + File(missingEsp), + File(masterFile), + }); - EXPECT_FALSE(plugin.CheckInstallValidity(game)); - EXPECT_EQ(std::list({ - Message(Message::Type::error, "This plugin is incompatible with \"" + masterFile + "\", but both are present."), - }), plugin.Messages()); - } + EXPECT_FALSE(plugin.CheckInstallValidity(game_)); + EXPECT_EQ(std::list({ + Message(Message::Type::error, "This plugin is incompatible with \"" + masterFile + "\", but both are present."), + }), plugin.Messages()); +} - TEST_P(PluginTest, checkInstallValidityShouldGenerateMessagesFromDirtyInfo) { - Plugin plugin(game, blankEsm, false); - plugin.DirtyInfo({ - PluginDirtyInfo(blankEsmCrc, 0, 1, 2, "utility1"), - PluginDirtyInfo(0xDEADBEEF, 0, 5, 10, "utility2"), - }); +TEST_P(PluginTest, checkInstallValidityShouldGenerateMessagesFromDirtyInfo) { + Plugin plugin(game_, blankEsm, false); + plugin.DirtyInfo({ + PluginDirtyInfo(blankEsmCrc, 0, 1, 2, "utility1"), + PluginDirtyInfo(0xDEADBEEF, 0, 5, 10, "utility2"), + }); - EXPECT_TRUE(plugin.CheckInstallValidity(game)); - EXPECT_EQ(std::list({ - PluginDirtyInfo(blankEsmCrc, 0, 1, 2, "utility1").AsMessage(), - PluginDirtyInfo(0xDEADBEEF, 0, 5, 10, "utility2").AsMessage(), - }), plugin.Messages()); - } + EXPECT_TRUE(plugin.CheckInstallValidity(game_)); + EXPECT_EQ(std::list({ + PluginDirtyInfo(blankEsmCrc, 0, 1, 2, "utility1").AsMessage(), + PluginDirtyInfo(0xDEADBEEF, 0, 5, 10, "utility2").AsMessage(), + }), plugin.Messages()); +} - TEST_P(PluginTest, checkInstallValidityShouldCheckIfAPluginsMastersAreAllPresentAndActiveIfNoFilterTagIsPresent) { - Plugin plugin(game, blankDifferentMasterDependentEsp, false); +TEST_P(PluginTest, checkInstallValidityShouldCheckIfAPluginsMastersAreAllPresentAndActiveIfNoFilterTagIsPresent) { + Plugin plugin(game_, blankDifferentMasterDependentEsp, false); - EXPECT_FALSE(plugin.CheckInstallValidity(game)); - EXPECT_EQ(std::list({ - Message(Message::Type::error, "This plugin requires \"" + blankDifferentEsm + "\" to be active, but it is inactive."), - }), plugin.Messages()); - } + EXPECT_FALSE(plugin.CheckInstallValidity(game_)); + EXPECT_EQ(std::list({ + Message(Message::Type::error, "This plugin requires \"" + blankDifferentEsm + "\" to be active, but it is inactive."), + }), plugin.Messages()); +} - TEST_P(PluginTest, checkInstallValidityShouldNotCheckIfAPluginsMastersAreAllActiveIfAFilterTagIsPresent) { - Plugin plugin(game, blankDifferentMasterDependentEsp, false); - plugin.Tags({Tag("Filter")}); +TEST_P(PluginTest, checkInstallValidityShouldNotCheckIfAPluginsMastersAreAllActiveIfAFilterTagIsPresent) { + Plugin plugin(game_, blankDifferentMasterDependentEsp, false); + plugin.Tags({Tag("Filter")}); - EXPECT_FALSE(plugin.CheckInstallValidity(game)); - EXPECT_TRUE(plugin.Messages().empty()); - } - } + EXPECT_FALSE(plugin.CheckInstallValidity(game_)); + EXPECT_TRUE(plugin.Messages().empty()); +} +} } #endif diff --git a/src/tests/common_game_test_fixture.h b/src/tests/common_game_test_fixture.h index b210e085..8cc227db 100644 --- a/src/tests/common_game_test_fixture.h +++ b/src/tests/common_game_test_fixture.h @@ -3,7 +3,7 @@ A load order optimisation tool for Oblivion, Skyrim, Fallout 3 and Fallout: New Vegas. -Copyright (C) 2013-2016 WrinklyNinja +Copyright (C) 2014-2016 WrinklyNinja This file is part of LOOT. @@ -25,240 +25,235 @@ along with LOOT. If not, see #ifndef LOOT_TESTS_COMMON_GAME_TEST_FIXTURE #define LOOT_TESTS_COMMON_GAME_TEST_FIXTURE -#include -#include -#include -#include - #include #include +#include +#include +#include +#include + namespace loot { - namespace test { - class CommonGameTestFixture { - protected: - CommonGameTestFixture(unsigned int gameType) : - gameType_(gameType), - missingPath("./missing"), - dataPath(getPluginsPath()), - localPath(getLocalPath()), - masterFile(getMasterFile()), - missingEsp("Blank.missing.esp"), - blankEsm("Blank.esm"), - blankDifferentEsm("Blank - Different.esm"), - blankMasterDependentEsm("Blank - Master Dependent.esm"), - blankDifferentMasterDependentEsm("Blank - Different Master Dependent.esm"), - blankEsp("Blank.esp"), - blankDifferentEsp("Blank - Different.esp"), - blankMasterDependentEsp("Blank - Master Dependent.esp"), - blankDifferentMasterDependentEsp("Blank - Different Master Dependent.esp"), - blankPluginDependentEsp("Blank - Plugin Dependent.esp"), - blankDifferentPluginDependentEsp("Blank - Different Plugin Dependent.esp"), - blankEsmCrc(getBlankEsmCrc()) {} +namespace test { +class CommonGameTestFixture { +protected: + CommonGameTestFixture(unsigned int gameType) : + gameType(gameType), + missingPath("./missing"), + dataPath(getPluginsPath()), + localPath(getLocalPath()), + masterFile(getMasterFile()), + missingEsp("Blank.missing.esp"), + blankEsm("Blank.esm"), + blankDifferentEsm("Blank - Different.esm"), + blankMasterDependentEsm("Blank - Master Dependent.esm"), + blankDifferentMasterDependentEsm("Blank - Different Master Dependent.esm"), + blankEsp("Blank.esp"), + blankDifferentEsp("Blank - Different.esp"), + blankMasterDependentEsp("Blank - Master Dependent.esp"), + blankDifferentMasterDependentEsp("Blank - Different Master Dependent.esp"), + blankPluginDependentEsp("Blank - Plugin Dependent.esp"), + blankDifferentPluginDependentEsp("Blank - Different Plugin Dependent.esp"), + blankEsmCrc(getBlankEsmCrc()) {} - void setUp() { - ASSERT_NO_THROW(boost::filesystem::create_directories(localPath)); - ASSERT_TRUE(boost::filesystem::exists(localPath)); + void setUp() { + ASSERT_NO_THROW(boost::filesystem::create_directories(localPath)); + ASSERT_TRUE(boost::filesystem::exists(localPath)); - ASSERT_FALSE(boost::filesystem::exists(missingPath)); - ASSERT_FALSE(boost::filesystem::exists(dataPath / missingEsp)); + ASSERT_FALSE(boost::filesystem::exists(missingPath)); + ASSERT_FALSE(boost::filesystem::exists(dataPath / missingEsp)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankEsm)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentEsm)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankMasterDependentEsm)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentMasterDependentEsm)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankEsp)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentEsp)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankMasterDependentEsp)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentMasterDependentEsp)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankPluginDependentEsp)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentPluginDependentEsp)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankEsm)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentEsm)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankMasterDependentEsm)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentMasterDependentEsm)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankEsp)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentEsp)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankMasterDependentEsp)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentMasterDependentEsp)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankPluginDependentEsp)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / blankDifferentPluginDependentEsp)); - // Make sure the game master file exists. - ASSERT_FALSE(boost::filesystem::exists(dataPath / masterFile)); - ASSERT_NO_THROW(boost::filesystem::copy_file(dataPath / blankEsm, dataPath / masterFile)); - ASSERT_TRUE(boost::filesystem::exists(dataPath / masterFile)); + // Make sure the game master file exists. + ASSERT_FALSE(boost::filesystem::exists(dataPath / masterFile)); + ASSERT_NO_THROW(boost::filesystem::copy_file(dataPath / blankEsm, dataPath / masterFile)); + ASSERT_TRUE(boost::filesystem::exists(dataPath / masterFile)); - // Set initial load order and active plugins. - setLoadOrder(getInitialLoadOrder()); + // Set initial load order and active plugins. + setLoadOrder(getInitialLoadOrder()); - // Ghost a plugin. - ASSERT_FALSE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost"))); - ASSERT_NO_THROW(boost::filesystem::rename(dataPath / blankMasterDependentEsm, dataPath / (blankMasterDependentEsm + ".ghost"))); - ASSERT_TRUE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost"))); - } + // Ghost a plugin. + ASSERT_FALSE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost"))); + ASSERT_NO_THROW(boost::filesystem::rename(dataPath / blankMasterDependentEsm, dataPath / (blankMasterDependentEsm + ".ghost"))); + ASSERT_TRUE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost"))); + } - void tearDown() { - ASSERT_NO_THROW(boost::filesystem::remove_all(localPath)); + void tearDown() { + ASSERT_NO_THROW(boost::filesystem::remove_all(localPath)); - ASSERT_NO_THROW(boost::filesystem::remove(dataPath / masterFile)); + ASSERT_NO_THROW(boost::filesystem::remove(dataPath / masterFile)); - // Unghost the ghosted plugin. - ASSERT_TRUE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost"))); - ASSERT_NO_THROW(boost::filesystem::rename(dataPath / (blankMasterDependentEsm + ".ghost"), dataPath / blankMasterDependentEsm)); - ASSERT_FALSE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost"))); - } + // Unghost the ghosted plugin. + ASSERT_TRUE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost"))); + ASSERT_NO_THROW(boost::filesystem::rename(dataPath / (blankMasterDependentEsm + ".ghost"), dataPath / blankMasterDependentEsm)); + ASSERT_FALSE(boost::filesystem::exists(dataPath / (blankMasterDependentEsm + ".ghost"))); + } - std::list getLoadOrder() { - std::list actual; - if (isLoadOrderTimestampBased(gameType_)) { - std::map loadOrder; - for (boost::filesystem::directory_iterator it(dataPath); it != boost::filesystem::directory_iterator(); ++it) { - if (boost::filesystem::is_regular_file(it->status())) { - std::string filename = it->path().filename().string(); - if (boost::ends_with(filename, ".ghost")) - filename = it->path().stem().string(); - if (boost::ends_with(filename, ".esp") || boost::ends_with(filename, ".esm")) - loadOrder.emplace(boost::filesystem::last_write_time(it->path()), filename); - } - } - for (const auto& plugin : loadOrder) - actual.push_back(plugin.second); - } - else if (gameType_ == tes5) { - boost::filesystem::ifstream in(localPath / "loadorder.txt"); - while (in) { - std::string line; - std::getline(in, line); + std::list getLoadOrder() { + std::list actual; + if (isLoadOrderTimestampBased(gameType)) { + std::map loadOrder; + for (boost::filesystem::directory_iterator it(dataPath); it != boost::filesystem::directory_iterator(); ++it) { + if (boost::filesystem::is_regular_file(it->status())) { + std::string filename = it->path().filename().string(); + if (boost::ends_with(filename, ".ghost")) + filename = it->path().stem().string(); + if (boost::ends_with(filename, ".esp") || boost::ends_with(filename, ".esm")) + loadOrder.emplace(boost::filesystem::last_write_time(it->path()), filename); + } + } + for (const auto& plugin : loadOrder) + actual.push_back(plugin.second); + } else if (gameType == tes5) { + boost::filesystem::ifstream in(localPath / "loadorder.txt"); + while (in) { + std::string line; + std::getline(in, line); - if (!line.empty()) - actual.push_back(line); - } - } - else { - boost::filesystem::ifstream in(localPath / "plugins.txt"); - while (in) { - std::string line; - std::getline(in, line); + if (!line.empty()) + actual.push_back(line); + } + } else { + boost::filesystem::ifstream in(localPath / "plugins.txt"); + while (in) { + std::string line; + std::getline(in, line); - if (!line.empty()) { - if (line[0] == '*') - line = line.substr(1); + if (!line.empty()) { + if (line[0] == '*') + line = line.substr(1); - actual.push_back(line); - } - } - } - - return actual; - } - - inline std::vector> getInitialLoadOrder() const { - return std::vector>({ - {masterFile, true}, - {blankEsm, true}, - {blankDifferentEsm, false}, - {blankMasterDependentEsm, false}, - {blankDifferentMasterDependentEsm, false}, - {blankEsp, false}, - {blankDifferentEsp, false}, - {blankMasterDependentEsp, false}, - {blankDifferentMasterDependentEsp, true}, - {blankPluginDependentEsp, false}, - {blankDifferentPluginDependentEsp, false}, - }); - } - - private: - // This needs to be here to ensure the correct initialisation order. - const unsigned int gameType_; - - protected: - const boost::filesystem::path missingPath; - const boost::filesystem::path dataPath; - const boost::filesystem::path localPath; - - const std::string masterFile; - const std::string missingEsp; - const std::string blankEsm; - const std::string blankDifferentEsm; - const std::string blankMasterDependentEsm; - const std::string blankDifferentMasterDependentEsm; - const std::string blankEsp; - const std::string blankDifferentEsp; - const std::string blankMasterDependentEsp; - const std::string blankDifferentMasterDependentEsp; - const std::string blankPluginDependentEsp; - const std::string blankDifferentPluginDependentEsp; - - const uint32_t blankEsmCrc; - - private: - static const unsigned int tes4 = 1; - static const unsigned int tes5 = 2; - static const unsigned int fo3 = 3; - static const unsigned int fonv = 4; - static const unsigned int fo4 = 5; - - inline boost::filesystem::path getLocalPath() const { - if (gameType_ == tes4) - return "./local/Oblivion"; - else - return "./local/Skyrim"; - } - - inline boost::filesystem::path getPluginsPath() const { - if (gameType_ == tes4) - return "./Oblivion/Data"; - else - return "./Skyrim/Data"; - } - - inline std::string getMasterFile() const { - if (gameType_ == tes4) - return "Oblivion.esm"; - else if (gameType_ == tes5) - return "Skyrim.esm"; - else if (gameType_ == fo3) - return "Fallout3.esm"; - else if (gameType_ == fonv) - return "FalloutNV.esm"; - else - return "Fallout4.esm"; - } - - inline uint32_t getBlankEsmCrc() const { - if (gameType_ == tes4) - return 0x374E2A6F; - else - return 0x187BE342; - } - - void setLoadOrder(const std::vector>& loadOrder) const { - boost::filesystem::ofstream out(localPath / "plugins.txt"); - for (const auto &plugin : loadOrder) { - if (gameType_ == fo4 && plugin.second) - out << '*'; - else if (gameType_ != fo4 && !plugin.second) - continue; - - out << plugin.first << std::endl; - } - - if (isLoadOrderTimestampBased(gameType_)) { - time_t modificationTime = time(NULL); // Current time. - for (const auto &plugin : loadOrder) { - if (boost::filesystem::exists(dataPath / boost::filesystem::path(plugin.first + ".ghost"))) { - boost::filesystem::last_write_time(dataPath / boost::filesystem::path(plugin.first + ".ghost"), modificationTime); - } - else { - boost::filesystem::last_write_time(dataPath / plugin.first, modificationTime); - } - modificationTime += 60; - } - } - else if (gameType_ == tes5) { - boost::filesystem::ofstream out(localPath / "loadorder.txt"); - for (const auto &plugin : loadOrder) - out << plugin.first << std::endl; - } - } - - inline static bool isLoadOrderTimestampBased(unsigned int gameId) { - return gameId == tes4 || gameId == fo3 || gameId == fonv; - } - }; + actual.push_back(line); + } + } } -} + return actual; + } + + inline std::vector> getInitialLoadOrder() const { + return std::vector>({ + {masterFile, true}, + {blankEsm, true}, + {blankDifferentEsm, false}, + {blankMasterDependentEsm, false}, + {blankDifferentMasterDependentEsm, false}, + {blankEsp, false}, + {blankDifferentEsp, false}, + {blankMasterDependentEsp, false}, + {blankDifferentMasterDependentEsp, true}, + {blankPluginDependentEsp, false}, + {blankDifferentPluginDependentEsp, false}, + }); + } + +private: + // This needs to be here to ensure the correct initialisation order. + const unsigned int gameType; + +protected: + const boost::filesystem::path missingPath; + const boost::filesystem::path dataPath; + const boost::filesystem::path localPath; + + const std::string masterFile; + const std::string missingEsp; + const std::string blankEsm; + const std::string blankDifferentEsm; + const std::string blankMasterDependentEsm; + const std::string blankDifferentMasterDependentEsm; + const std::string blankEsp; + const std::string blankDifferentEsp; + const std::string blankMasterDependentEsp; + const std::string blankDifferentMasterDependentEsp; + const std::string blankPluginDependentEsp; + const std::string blankDifferentPluginDependentEsp; + + const uint32_t blankEsmCrc; + +private: + static const unsigned int tes4 = 1; + static const unsigned int tes5 = 2; + static const unsigned int fo3 = 3; + static const unsigned int fonv = 4; + static const unsigned int fo4 = 5; + + inline boost::filesystem::path getLocalPath() const { + if (gameType == tes4) + return "./local/Oblivion"; + else + return "./local/Skyrim"; + } + + inline boost::filesystem::path getPluginsPath() const { + if (gameType == tes4) + return "./Oblivion/Data"; + else + return "./Skyrim/Data"; + } + + inline std::string getMasterFile() const { + if (gameType == tes4) + return "Oblivion.esm"; + else if (gameType == tes5) + return "Skyrim.esm"; + else if (gameType == fo3) + return "Fallout3.esm"; + else if (gameType == fonv) + return "FalloutNV.esm"; + else + return "Fallout4.esm"; + } + + inline uint32_t getBlankEsmCrc() const { + if (gameType == tes4) + return 0x374E2A6F; + else + return 0x187BE342; + } + + void setLoadOrder(const std::vector>& loadOrder) const { + boost::filesystem::ofstream out(localPath / "plugins.txt"); + for (const auto &plugin : loadOrder) { + if (gameType == fo4 && plugin.second) + out << '*'; + else if (gameType != fo4 && !plugin.second) + continue; + + out << plugin.first << std::endl; + } + + if (isLoadOrderTimestampBased(gameType)) { + time_t modificationTime = time(NULL); // Current time. + for (const auto &plugin : loadOrder) { + if (boost::filesystem::exists(dataPath / boost::filesystem::path(plugin.first + ".ghost"))) { + boost::filesystem::last_write_time(dataPath / boost::filesystem::path(plugin.first + ".ghost"), modificationTime); + } else { + boost::filesystem::last_write_time(dataPath / plugin.first, modificationTime); + } + modificationTime += 60; + } + } else if (gameType == tes5) { + boost::filesystem::ofstream out(localPath / "loadorder.txt"); + for (const auto &plugin : loadOrder) + out << plugin.first << std::endl; + } + } + + inline static bool isLoadOrderTimestampBased(unsigned int gameId) { + return gameId == tes4 || gameId == fo3 || gameId == fonv; + } +}; +} +} #endif diff --git a/src/tests/printers.h b/src/tests/printers.h index 5b03b603..6cb4b2a7 100644 --- a/src/tests/printers.h +++ b/src/tests/printers.h @@ -22,8 +22,8 @@ along with LOOT. If not, see . */ -#ifndef LOOT_TEST_PRINTERS -#define LOOT_TEST_PRINTERS +#ifndef LOOT_TESTS_PRINTERS +#define LOOT_TESTS_PRINTERS #include @@ -39,68 +39,68 @@ along with LOOT. If not, see #include "backend/plugin/plugin.h" namespace loot { - namespace test { - void PrintTo(const File& value, ::std::ostream* os) { - *os << "File(\"" << value.Name() << "\", " - << "\"" << value.DisplayName() << "\", " - << "\"" << value.Condition() << "\"" - << ")"; - } +namespace test { +void PrintTo(const File& value, ::std::ostream* os) { + *os << "File(\"" << value.Name() << "\", " + << "\"" << value.DisplayName() << "\", " + << "\"" << value.Condition() << "\"" + << ")"; +} - void PrintTo(const Location& value, ::std::ostream* os) { - *os << "Location(\"" << value.URL() << "\", " - << "\"" << value.Name() << "\", " - << ")"; - } +void PrintTo(const Location& value, ::std::ostream* os) { + *os << "Location(\"" << value.URL() << "\", " + << "\"" << value.Name() << "\", " + << ")"; +} - void PrintTo(const Message& value, ::std::ostream* os) { - std::string type; - if (value.GetType() == Message::Type::warn) - type = "warn"; - else if (value.GetType() == Message::Type::error) - type = "error"; - else - type = "say"; +void PrintTo(const Message& value, ::std::ostream* os) { + std::string type; + if (value.GetType() == Message::Type::warn) + type = "warn"; + else if (value.GetType() == Message::Type::error) + type = "error"; + else + type = "say"; - *os << "Message(\"" << type << "\", " - << ::testing::PrintToString(value.GetContent()) << ", " - << "\"" << value.Condition() << "\"" - << ")"; - } + *os << "Message(\"" << type << "\", " + << ::testing::PrintToString(value.GetContent()) << ", " + << "\"" << value.Condition() << "\"" + << ")"; +} - void PrintTo(const MessageContent& value, ::std::ostream* os) { - *os << "MessageContent(\"" << value.Str() << "\", " - << "\"" << Language(value.GetLanguage()).GetName() << "\"" - << ")"; - } +void PrintTo(const MessageContent& value, ::std::ostream* os) { + *os << "MessageContent(\"" << value.GetText() << "\", " + << "\"" << Language(value.GetLanguage()).GetName() << "\"" + << ")"; +} - void PrintTo(const PluginDirtyInfo& value, ::std::ostream* os) { - *os << "PluginDirtyInfo(0x" - << std::hex << std::uppercase - << value.CRC() - << std::nouppercase << std::dec << ", " - << value.ITMs() << ", " - << value.DeletedRefs() << ", " - << value.DeletedNavmeshes() << ", " - << "\"" << value.CleaningUtility() << "\"" - << ")"; - } +void PrintTo(const PluginDirtyInfo& value, ::std::ostream* os) { + *os << "PluginDirtyInfo(0x" + << std::hex << std::uppercase + << value.CRC() + << std::nouppercase << std::dec << ", " + << value.ITMs() << ", " + << value.DeletedRefs() << ", " + << value.DeletedNavmeshes() << ", " + << "\"" << value.CleaningUtility() << "\"" + << ")"; +} - void PrintTo(const PluginMetadata& value, ::std::ostream* os) { - *os << "PluginMetadata(\"" << value.Name() << "\")"; - } +void PrintTo(const PluginMetadata& value, ::std::ostream* os) { + *os << "PluginMetadata(\"" << value.Name() << "\")"; +} - void PrintTo(const Tag& value, ::std::ostream* os) { - *os << "Tag(\"" << value.Name() << "\", " - << value.IsAddition() << ", " - << "\"" << value.Condition() << "\"" - << ")"; - } +void PrintTo(const Tag& value, ::std::ostream* os) { + *os << "Tag(\"" << value.Name() << "\", " + << value.IsAddition() << ", " + << "\"" << value.Condition() << "\"" + << ")"; +} - void PrintTo(const Plugin& value, ::std::ostream* os) { - *os << "Plugin(\"" << value.Name() << "\")"; - } - } +void PrintTo(const Plugin& value, ::std::ostream* os) { + *os << "Plugin(\"" << value.Name() << "\")"; +} +} } #endif diff --git a/src/validator/main.cpp b/src/validator/main.cpp index 08d02363..b6b55c3d 100644 --- a/src/validator/main.cpp +++ b/src/validator/main.cpp @@ -22,50 +22,52 @@ . */ +#include + #include "backend/app/loot_version.h" #include "backend/metadata_list.h" -#include - int main(int argc, char **argv) { - //Set the locale to get encoding conversions working correctly. - std::locale::global(boost::locale::generator().generate("")); - boost::filesystem::path::imbue(std::locale()); + using std::cout; + using std::endl; - //Disable logging or else stdout will get overrun. - boost::log::core::get()->set_logging_enabled(false); + //Set the locale to get encoding conversions working correctly. + std::locale::global(boost::locale::generator().generate("")); + boost::filesystem::path::imbue(std::locale()); - // Print help text if -h, --help or invalid args are given (including no args). - if (argc != 2 || (strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) { - std::cout << std::endl - << "Usage: metadata-validator " << std::endl << std::endl - << "Arguments:" << std::endl << std::endl - << " " << "" << " " << "The metadata file to validate." << std::endl - << " " << "-v, --version" << " " << "Prints version information for this utility." << std::endl - << " " << "-h, --help" << " " << "Prints this help text." << std::endl << std::endl; - return 1; - } + //Disable logging or else stdout will get overrun. + boost::log::core::get()->set_logging_enabled(false); - // Print version info if -v or --version are given. - if ((strcmp(argv[1], "-v") == 0) || (strcmp(argv[1], "--version") == 0)) { - std::cout << std::endl << "LOOT Metadata Validator" << std::endl - << "v" << loot::LootVersion::major << "." << loot::LootVersion::minor - << "." << loot::LootVersion::patch << std::endl - << "build revision " << loot::LootVersion::revision << std::endl << std::endl; - return 0; - } - - try { - std::cout << std::endl << "Validating metadata file: " << argv[1] << std::endl << std::endl; - // Test YAML parsing. - loot::MetadataList metadata; - metadata.Load(argv[1]); - } - catch (std::exception& e) { - std::cout << "ERROR: " << e.what() << std::endl << std::endl; - return 1; - } - std::cout << "SUCCESS!" << std::endl << std::endl; + // Print help text if -h, --help or invalid args are given (including no args). + if (argc != 2 || (strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0)) { + cout << endl + << "Usage: metadata-validator " << endl << endl + << "Arguments:" << endl << endl + << " " << "" << " " << "The metadata file to validate." << endl + << " " << "-v, --version" << " " << "Prints version information for this utility." << endl + << " " << "-h, --help" << " " << "Prints this help text." << endl << endl; + return 1; + } + // Print version info if -v or --version are given. + if ((strcmp(argv[1], "-v") == 0) || (strcmp(argv[1], "--version") == 0)) { + cout << endl << "LOOT Metadata Validator" << endl + << "v" << loot::LootVersion::major << "." << loot::LootVersion::minor + << "." << loot::LootVersion::patch << endl + << "build revision " << loot::LootVersion::revision << endl << endl; return 0; + } + + try { + cout << endl << "Validating metadata file: " << argv[1] << endl << endl; + // Test YAML parsing. + loot::MetadataList metadata; + metadata.Load(argv[1]); + } catch (std::exception& e) { + cout << "ERROR: " << e.what() << endl << endl; + return 1; + } + cout << "SUCCESS!" << endl << endl; + + return 0; }