diff --git a/CHANGES b/CHANGES index 1b18202a..c61131f9 100644 --- a/CHANGES +++ b/CHANGES @@ -1,11 +1,15 @@ Changes for 0.10.0 +* **Breaking change:** The _Configuration Files_ directory has moved from `%USERPROFILE%\ShellAnything` to `%USERPROFILE%\ShellAnything\configurations`. Configuration Files in the old directory will move to the new directory automatically on application first launch. Other files in `%USERPROFILE%\ShellAnything` will not be moved. +* **Breaking change:** The _logs_ directory has moved from `%USERPROFILE%\ShellAnything\Logs` to `%LOCALAPPDATA%\ShellAnything\logs`. The previous logs directory will be removed on application first launch. * ShellAnything has a new high-resolution logo icon! * Shellanything now features verbose logging mode and command line arguments debugging tools. -* ShellAnything now packages icons from [icons8/flat-color-icons](https://github.com/icons8/flat-color-icons) +* ShellAnything now packages icons from [icons8/flat-color-icons](https://github.com/icons8/flat-color-icons). +Fixes: * Fixed issue #6 : (twice) Right-click on a directory with Windows Explorer in the left panel shows the menus twice. * Fixed issue #31 : (twice) Error in logs for CContextMenu::GetCommandString() +* Fixed issue #108: Separate location for log files ? (and exclusions?) * Fixed issue #109: Implement default and verbose logging. * Fixed issue #110: Create a simple command line arguments debugging application. * Fixed issue #148: Can't uninstall. diff --git a/CMakeLists.txt b/CMakeLists.txt index 8748e858..b91c2282 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -373,12 +373,18 @@ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/shellanything-config-version.cma configure_file(${CMAKE_SOURCE_DIR}/cmake/shellanything-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/cmake/shellanything-config.cmake @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/shellanything-config.cmake DESTINATION ${SHELLANYTHING_INSTALL_CMAKE_DIR}) -install(FILES ${CMAKE_SOURCE_DIR}/resources/register.bat - ${CMAKE_SOURCE_DIR}/resources/unregister.bat +install(FILES ${CMAKE_SOURCE_DIR}/resources/installer/bin/register.bat + ${CMAKE_SOURCE_DIR}/resources/installer/bin/unregister.bat DESTINATION ${SHELLANYTHING_INSTALL_BIN_DIR}) # Resources +install(DIRECTORY "${CMAKE_SOURCE_DIR}/resources/configurations" DESTINATION ${SHELLANYTHING_INSTALL_RESOURCE_DIR}) install(DIRECTORY "${CMAKE_SOURCE_DIR}/resources/Windows Icon Tables" DESTINATION ${SHELLANYTHING_INSTALL_RESOURCE_DIR}) +install(FILES ${CMAKE_SOURCE_DIR}/resources/icons/shellanything.ico + ${CMAKE_SOURCE_DIR}/resources/icons/0.1.0.ico + ${CMAKE_SOURCE_DIR}/resources/icons/0.2.0.ico + ${CMAKE_SOURCE_DIR}/resources/icons/0.2.0-16x16-legacy.ico + DESTINATION ${SHELLANYTHING_INSTALL_RESOURCE_DIR}/icons) install(DIRECTORY ${CMAKE_SOURCE_DIR}/include/shellanything DESTINATION ${SHELLANYTHING_INSTALL_INCLUDE_DIR}) install(FILES ${SHELLANYTHING_EXPORT_HEADER} @@ -390,7 +396,6 @@ install(DIRECTORY ${CMAKE_SOURCE_DIR}/licenses DESTINATION ${SHELLANYTHING_INSTA install(FILES ${CMAKE_SOURCE_DIR}/docs/screenshot_file.png ${CMAKE_SOURCE_DIR}/docs/screenshot_folder.png ${CMAKE_SOURCE_DIR}/docs/ShellAnything-splashscreen.jpg - ${CMAKE_SOURCE_DIR}/resources/icons/shellanything.ico ${CMAKE_CURRENT_BINARY_DIR}/sa_plugin_demo.zip DESTINATION ${SHELLANYTHING_INSTALL_DOC_DIR}) @@ -469,7 +474,7 @@ set(CPACK_PACKAGE_EXECUTABLES # This is intended behavior for ShellAnything. set(CPACK_WIX_UPGRADE_GUID "EBEF1698-CA72-480A-9FE3-E8A08E7E03ED") # as per v0.7.0 and older -set(CPACK_WIX_LICENSE_RTF "${CMAKE_SOURCE_DIR}/resources/wix_license_template.rtf") +set(CPACK_WIX_LICENSE_RTF "${CMAKE_SOURCE_DIR}/resources/installer/wix_license_template.rtf") set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/resources/icons/shellanything.ico") set(CPACK_WIX_HELP_LINK "https://github.com/end2endzone/ShellAnything") set(CPACK_WIX_PROGRAM_MENU_FOLDER "${PROJECT_NAME}") diff --git a/UserManual.md b/UserManual.md index 68b1ff47..b3d3568e 100644 --- a/UserManual.md +++ b/UserManual.md @@ -46,7 +46,7 @@ This manual includes a description of the system functionalities and capabilitie * [Multi-selection-based properties](#multi-selection-based-properties) * [Fixed properties](#fixed-properties) * [Default properties](#default-properties) -* [Environment variables](#environment-variables) +* [Environment Variables options](#environment-variables-options) * [Tools](#tools) * [file_explorer_renew](#file_explorer_renew) * [arguments.debugger](#argumentsdebugger) @@ -79,6 +79,7 @@ This manual includes a description of the system functionalities and capabilitie * [Troubleshooting](#troubleshooting) * [Logging support](#logging-support) * [Checkout the _Tools_ section](#tools) + * [Change the rendering order of your system's shell extension menus](#change-the-rendering-order-of-your-systems-shell-extension-menus) * [Missing ampersand character (`&`) in menus](#missing-ampersand-character--in-menus) * [Reporting bugs](#reporting-bugs) @@ -168,7 +169,7 @@ A *configuration file* contains the definition of all [<menu>](#Menu) elem When a user right-click on a file in *Windows Explorer*, the application will load all available *configuration files* and display their content into the displayed context menu. -The list of *Configuration Files* is unique for each users of the system. The files are stored in `C:\Users\%USERNAME%\ShellAnything` directory where `%USERNAME%` is your current Windows session *username*. Note that *Windows Explorer* also support copy & pasting `C:\Users\%USERNAME%\ShellAnything` into an *address bar* to quickly jump to the directory. +The list of *Configuration Files* is unique for each users of the system. The files are stored in `C:\Users\%USERNAME%\ShellAnything\configurations` directory where `%USERNAME%` is your current Windows session *username*. Note that you can paste `C:\Users\%USERNAME%\ShellAnything\configurations` into an *address bar* of *Windows Explorer* to quickly jump to the directory. The application support multiple *configuration files* at the same time. One can add new files in the *configuration directory* and the system will automatically detect and load them. @@ -176,6 +177,8 @@ When a *configuration file* is deleted, the application automatically detect the To temporary disable a *configuration file*, one can simply change the file extension from `xml` to `txt`. Change the file extension back to `xml` to re-enable the file. +**Note:** The *Configuration Files* directory can be modified with the `SA_OPTION_CONFIGURATIONS_DIR` environment variable option. See [Environment Variables options](#environment-variables-options) section for details. + ## Basic Xml Document ## @@ -1720,9 +1723,21 @@ The system will generates the following property values (note the `\r\n` charact If you need more flexibility when dealing with multiple files, the system defines the property `selection.multi.separator` that allows customizing the separator when combining multiple files. -By default, this property is set to the value `\r\n` (new line) when the application initialize. +By default, this property is set to the value `\r\n` (new line) when the application initialize. + +The property can be modified at any time using a [<property>](#property-action) action or with the [<default>](#default) element to change it globally. -The property can be modified at any time using a [<property>](#property-action) action for changing the property when a menu is executed or with the [<default>](#default) element to change the value globally (when the `Configuration File` is loaded). +If you mostly need the opposite of the default behavior (double-quotes instead of CRLF), override the value of the property globally when the `Configuration File` is loaded : +```xml + + + + + + + + +``` To reset the property back to the default value, use the following <property> action: ```xml @@ -1731,7 +1746,7 @@ To reset the property back to the default value, use the following <property& **Example #1:** -If an executable must get the list of selected files in a single command line (one after the other), one can set the property `selection.multi.separator` to `" "` (double quote, space, double quote) and use the string `"${selection.path}"` (including the double quotes) to get the required expanded value: +If an executable must get the list of selected files in a single command line (one after the other), one can temporary set the property `selection.multi.separator` to `" "` (double quote, space, double quote) and use the string `"${selection.path}"` (including the double quotes) to get the required expanded value: ```xml @@ -1740,13 +1755,15 @@ If an executable must get the list of selected files in a single command line (o ``` + Which result in the following expanded value: ``` "C:\Program Files (x86)\Winamp\libFLAC.dll" "C:\Program Files (x86)\Winamp\winamp.exe" "C:\Program Files (x86)\Winamp\zlib.dll" ``` + **Example #2:** -Assume that you want to run a specific command on each of the selected files (for example reset the files attributes), one can set the property `selection.multi.separator` to `${line.separator}attrib -r -a -s -h "` (including the ending double quote character) and use the string `attrib -r -a -s -h "${selection.filename}"` (including the double quotes) to get the following expanded value: +Assume that you want to run a specific command on each of the selected files (for example reset the files attributes), one can set the property `selection.multi.separator` to `${line.separator}attrib -r -a -s -h "` (including the ending double quote character) and use the string `attrib -r -a -s -h "${selection.filename}"` (including the double quotes) to get the following expanded value: ``` attrib -r -a -s -h "C:\Program Files (x86)\Winamp\libFLAC.dll" attrib -r -a -s -h "C:\Program Files (x86)\Winamp\winamp.exe" @@ -1807,7 +1824,7 @@ For example, the following would define `services.wce.command.start` and `servic -# Environment variables # +# Environment Variables options # ShellAnything default startup behavior can be modified by setting specific pre-defined environment variables. Some features or configuration options can also be enabled or disabled through environment variables. For example, one can define an environment variables to enable verbose logging. @@ -1819,10 +1836,11 @@ All ShellAnything environment variables names are prefixed with `SA_`. The following table defines the list of pre-defined environment variables for ShellAnything: -| Name | Description | -|--------------------------------|--------------------------------------------------------------------------------------------------------------------| -| SA_OPTION_LOGGING_VERBOSE | Enables [verbose logging](#verbose-logging) when set to a value that evaluates to [true](#istrue-attribute). | - +| Name | Description | +|--------------------------------|----------------------------------------------------------------------------------------------------------------------| +| SA_OPTION_LOGGING_VERBOSE | Enables [verbose logging](#verbose-logging) when set to a value that evaluates to [true](#istrue-attribute). | +| SA_OPTION_CONFIGURATIONS_DIR | Set to a custom value to change/override the directory where [Configuration Files](#configuration-files) are stored. | +| SA_OPTION_LOGS_DIR | Set to a custom value to change/override the directory where [Log Files](#logging-support) are stored. | @@ -2636,8 +2654,9 @@ ShellAnything provides logging support for troubleshooting and debugging command The logging directory is unique for each users of the system. -The log files are stored in `C:\Users\%USERNAME%\ShellAnything\Logs` directory where `%USERNAME%` matches your current login username. -For example, the user `JohnSmith` can find his own ShellAnything log files in directory `C:\Users\JohnSmith\ShellAnything\Logs`. +The log files are stored in `%LOCALAPPDATA%\ShellAnything\logs` directory. For example, the user `JohnSmith` can find his own ShellAnything log files in directory `C:\Users\JohnSmith\AppData\Local\ShellAnything\logs`. + +**Note:** The logging directory can be modified with the `SA_OPTION_LOGS_DIR` environment variable option. See [Environment Variables options](#environment-variables-options) section for details. @@ -2715,6 +2734,33 @@ If no option is specified, verbose mode is disabled. +## Change the rendering order of your system's shell extension menus ## + +ShellAnything does not control in which order the system renders all the registered Shell Extensions. Because of this, your ShellAnything menus could be rendered at the top, middle or the end of Window's Context menu. + +This behavior can however be altered. + +**Note:** +Changing the default order of ShellAnything's menu is not officially supported by ShellAnything. The feature is documented as working on Windows 10 (22H2 or later) and Windows 11. This is basically a hack. The method is added to ShellAnything's documentation because some users have a need for it. + +Windows renders the registered Shell Extension alphabetically. For example, to change the rendering order of Shell Extensions for directories, you need to rename the desired shell extension's registry key in `HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\shellex\ContextMenuHandlers`. + +For example: you could rename the registry key `HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\shellex\ContextMenuHandlers\ShellAnything` to change ShellAnything's menu order in a Windows context menu. You can rename the key to `!ShellAnything` to have ShellAnything's menus rendered to the top of the context menu. Or you can rename the registry key to `ΩShellAnything` to move ShellAnything's menus to the bottom of the context menu. + +This feature is not officially supported. If you unregister or uninstall ShellAnything, the renamed registry key will not be deleted from your system. If you re-register ShellAnything then two registry keys referencing ShellAnything will be registered on the system (`!ShellAnything` and `ShellAnything`). This could lead to adding the menus twice to the context menu. + +Another downside is that you need to do this for every locations in the registry where ShellAnything is registered as a shell extension: + +* `*` (star character) +* `Directory` +* `Directory\Background` +* `Folder` +* `Drive` + +There is also shell extensions registered under `AllFilesystemObjects` (_HKEY_LOCAL_MACHINE\SOFTWARE\Classes\AllFilesystemObjects\shellex\ContextMenuHandlers_). ShellAnything do not register itself to this file system element. This could create confusion on the rendering order of context menus since `Directory` may (or may not) have priority over `AllFilesystemObjects`. + + + ## Missing ampersand character (`&`) in menus ## One might be puzzled as to why his menus do not display ampersand character (`&`) properly. diff --git a/docs/ShellAnything.xcf b/docs/ShellAnything-splashscreen.xcf similarity index 100% rename from docs/ShellAnything.xcf rename to docs/ShellAnything-splashscreen.xcf diff --git a/resources/configurations/shellanything.xml b/resources/configurations/shellanything.xml index 4ee24f59..6f552413 100644 --- a/resources/configurations/shellanything.xml +++ b/resources/configurations/shellanything.xml @@ -36,51 +36,51 @@ - + - + - + - + - + - + - + - + - + - + - + - + @@ -136,22 +136,22 @@ - + - + - + - + @@ -159,22 +159,22 @@ - + - + - + - + @@ -183,7 +183,7 @@ - + diff --git a/resources/register.bat b/resources/installer/bin/register.bat similarity index 100% rename from resources/register.bat rename to resources/installer/bin/register.bat diff --git a/resources/unregister.bat b/resources/installer/bin/unregister.bat similarity index 100% rename from resources/unregister.bat rename to resources/installer/bin/unregister.bat diff --git a/resources/wix_license_template.rtf b/resources/installer/wix_license_template.rtf similarity index 100% rename from resources/wix_license_template.rtf rename to resources/installer/wix_license_template.rtf diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3489e14d..14b28862 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -16,18 +16,7 @@ configure_file( ${CMAKE_SOURCE_DIR}/src/version.rc.in ${SHELLANYTHING_VERSION_RC set(CONFIGURATION_DEFAULT_FILES "" ${CMAKE_SOURCE_DIR}/resources/configurations/default.xml - "${CMAKE_SOURCE_DIR}/resources/configurations/git.xml" - "${CMAKE_SOURCE_DIR}/resources/configurations/Hugo.xml" - "${CMAKE_SOURCE_DIR}/resources/configurations/Mark Text.xml" - "${CMAKE_SOURCE_DIR}/resources/configurations/" - "${CMAKE_SOURCE_DIR}/resources/configurations/" - "${CMAKE_SOURCE_DIR}/resources/configurations/Microsoft Office 2003.xml" - "${CMAKE_SOURCE_DIR}/resources/configurations/Microsoft Office 2007.xml" - "${CMAKE_SOURCE_DIR}/resources/configurations/Microsoft Office 2010.xml" - "${CMAKE_SOURCE_DIR}/resources/configurations/Microsoft Office 2013.xml" - "${CMAKE_SOURCE_DIR}/resources/configurations/Microsoft Office 2016.xml" ${CMAKE_SOURCE_DIR}/resources/configurations/shellanything.xml - ${CMAKE_SOURCE_DIR}/resources/configurations/WinDirStat.xml ) # Subprojects @@ -59,10 +48,6 @@ add_custom_target(src_dummy_target "${CMAKE_CURRENT_SOURCE_DIR}/version.rc.in" ) -install(DIRECTORY ${CMAKE_SOURCE_DIR}/resources/configurations - DESTINATION ${SHELLANYTHING_INSTALL_BIN_DIR} -) - install(FILES ${LIBMAGIC_MGC_DIR}/magic.mgc ${LIBMAGIC_MGC_DIR}/file.exe ${ZLIB_INCLUDE_DIRS}/../bin/zlib.dll diff --git a/src/core/App.cpp b/src/core/App.cpp index 25621e5e..dd37b8b7 100644 --- a/src/core/App.cpp +++ b/src/core/App.cpp @@ -26,13 +26,12 @@ #include "LoggerHelper.h" #include "ConfigManager.h" #include "PropertyManager.h" +#include "Environment.h" -#include "rapidassist/process.h" -#include "rapidassist/user.h" -#include "rapidassist/environment.h" -#include "rapidassist/filesystem.h" -#include "rapidassist/filesystem_utf8.h" +#include "rapidassist/process_utf8.h" #include "rapidassist/user_utf8.h" +#include "rapidassist/environment_utf8.h" +#include "rapidassist/filesystem_utf8.h" #include "rapidassist/unicode.h" #include "shellanything/version.h" @@ -159,12 +158,20 @@ namespace shellanything bool App::IsTestingEnvironment() { - std::string process_path = ra::process::GetCurrentProcessPath(); + std::string process_path = ra::process::GetCurrentProcessPathUtf8(); if (process_path.find("sa.tests") != std::string::npos) return true; return false; } + std::string App::GetLegacyLogsDirectory() + { + //get home directory of the user + std::string home_dir = ra::user::GetHomeDirectoryUtf8(); + std::string legacy_dir = home_dir + "\\" + app_name + "\\Logs"; + return legacy_dir; + } + std::string App::GetLogDirectory() { //Issue #10 - Change the log directory if run from the unit tests executable @@ -174,7 +181,7 @@ namespace shellanything //Create 'test_logs' directory under the current executable. //When running tests from a developer environment, the 'test_logs' directory is expected to have write access. - std::string log_dir = ra::process::GetCurrentProcessDir(); + std::string log_dir = ra::process::GetCurrentProcessDirUtf8(); if (!log_dir.empty()) { log_dir.append("\\test_logs"); @@ -185,7 +192,7 @@ namespace shellanything //Issue #60 - Unit tests cannot execute from installation directory. //If unit tests are executed from the installation directory, //the 'test_logs' directory under the current executable is denied write access. - log_dir = ra::environment::GetEnvironmentVariable("TEMP"); + log_dir = ra::environment::GetEnvironmentVariableUtf8("TEMP"); if (!log_dir.empty()) { log_dir.append("\\test_logs"); @@ -194,35 +201,88 @@ namespace shellanything } } - //This DLL is executed by the shell (File Explorer). + //This DLL is most probably executed by the shell (File Explorer). //By default, GLOG will output log files in %TEMP% directory. - //However, I prefer to use %USERPROFILE%\ShellAnything\Logs - std::string log_dir = ra::user::GetHomeDirectory(); - if (!log_dir.empty()) + // Issue #108. Log files directory can be overriden with an option. + Environment& env = Environment::GetInstance(); + if (env.IsOptionSet(Environment::SYSTEM_LOGS_DIR_OVERRIDE_ENVIRONMENT_VARIABLE_NAME)) + { + std::string log_dir = env.GetOptionValue(Environment::SYSTEM_LOGS_DIR_OVERRIDE_ENVIRONMENT_VARIABLE_NAME); + if (IsValidLogDirectory(log_dir)) + return log_dir; + } + + // Issue #108. Log files should be stored in %LOCALAPPDATA%\ShellAnything\logs + std::string localappdata_dir = ra::environment::GetEnvironmentVariableUtf8("LOCALAPPDATA"); + if (!localappdata_dir.empty() && ra::filesystem::DirectoryExistsUtf8(localappdata_dir.c_str())) + { + std::string log_dir = localappdata_dir + "\\" + app_name + "\\logs"; + if (IsValidLogDirectory(log_dir)) + return log_dir; + } + + // Fallback to %USERPROFILE%\ShellAnything\logs + std::string home_dir = ra::user::GetHomeDirectoryUtf8(); + if (!home_dir.empty() && ra::filesystem::DirectoryExistsUtf8(home_dir.c_str())) { //We got the %USERPROFILE% directory. - //Now add our custom path to it - log_dir.append("\\ShellAnything\\Logs"); + std::string log_dir = home_dir + "\\" + app_name + "\\logs"; if (IsValidLogDirectory(log_dir)) return log_dir; } //Failed getting HOME directory. //Fallback to using %TEMP%. - log_dir = ra::environment::GetEnvironmentVariable("TEMP"); - return log_dir; + std::string temp_dir = ra::environment::GetEnvironmentVariableUtf8("TEMP"); + return temp_dir; + } + + std::string App::GetLegacyConfigurationsDirectory() + { + //get home directory of the user + std::string home_dir = ra::user::GetHomeDirectoryUtf8(); + std::string legacy_dir = home_dir + "\\" + app_name; + return legacy_dir; } std::string App::GetConfigurationsDirectory() { + // Issue #108. Configuration Files directory can be overriden with an option. + Environment& env = Environment::GetInstance(); + if (env.IsOptionSet(Environment::SYSTEM_CONFIGURATIONS_DIR_OVERRIDE_ENVIRONMENT_VARIABLE_NAME)) + { + std::string config_dir = env.GetOptionValue(Environment::SYSTEM_CONFIGURATIONS_DIR_OVERRIDE_ENVIRONMENT_VARIABLE_NAME); + if (IsValidConfigDirectory(config_dir)) + return config_dir; + } + //get home directory of the user std::string home_dir = ra::user::GetHomeDirectoryUtf8(); - std::string config_dir = home_dir + "\\" + app_name; + std::string app_dir = home_dir + "\\" + app_name; + std::string config_dir = app_dir + +"\\configurations"; + + if (IsValidConfigDirectory(config_dir)) + return config_dir; + return config_dir; } + std::string App::GetBinDirectory() + { + const std::string module_path = GetCurrentModulePathUtf8(); + const std::string bin_dir = ra::filesystem::GetParentPath(module_path); + return bin_dir; + } + + std::string App::GetInstallDirectory() + { + const std::string bin_dir = GetBinDirectory(); + const std::string install_dir = ra::filesystem::GetParentPath(bin_dir); + return install_dir; + } + bool App::Start() { SetupGlobalProperties(); @@ -237,16 +297,39 @@ namespace shellanything //Issue #60 - Unit tests cannot execute from installation directory. //Check if the directory already exists - if (!ra::filesystem::DirectoryExists(path.c_str())) + if (!ra::filesystem::DirectoryExistsUtf8(path.c_str())) { //Try to create the directory. - bool created = ra::filesystem::CreateDirectory(path.c_str()); + bool created = ra::filesystem::CreateDirectoryUtf8(path.c_str()); if (!created) return false; } //Validate that directory path is writable. - bool write_access = HasDirectoryWriteAccess(path); + bool write_access = HasDirectoryWriteAccessUtf8(path); + if (!write_access) + return false; //Write to directory is denied. + + //Write to directory is granted. + return true; + } + + bool App::IsValidConfigDirectory(const std::string& path) + { + // Config directory must be accessible for reading. + // Write access is optional + + //Check if the directory already exists + if (!ra::filesystem::DirectoryExistsUtf8(path.c_str())) + { + //Try to create the directory. + bool created = ra::filesystem::CreateDirectoryUtf8(path.c_str()); + if (!created) + return false; + } + + //Validate that directory path is readable. + bool write_access = HasDirectoryReadAccessUtf8(path); if (!write_access) return false; //Write to directory is denied. @@ -256,16 +339,10 @@ namespace shellanything void App::InstallDefaultConfigurations(const std::string& dest_dir) { - std::string app_path = GetCurrentModulePathUtf8(); - std::string app_dir = ra::filesystem::GetParentPath(app_path); + const std::string install_dir = shellanything::App::GetInstallDirectory(); static const char* default_files[] = { "default.xml", - "Microsoft Office 2003.xml", - "Microsoft Office 2007.xml", - "Microsoft Office 2010.xml", - "Microsoft Office 2013.xml", - "Microsoft Office 2016.xml", "shellanything.xml", }; static const size_t num_files = sizeof(default_files) / sizeof(default_files[0]); @@ -276,7 +353,7 @@ namespace shellanything for (size_t i = 0; i < num_files; i++) { const char* filename = default_files[i]; - std::string source_path = app_dir + "\\configurations\\" + filename; + std::string source_path = install_dir + "\\resources\\configurations\\" + filename; std::string target_path = dest_dir + "\\" + filename; SA_LOG(INFO) << "Installing configuration file: " << target_path; @@ -288,6 +365,91 @@ namespace shellanything } } + void App::ClearLegacyConfigurationDirectory() + { + const std::string legacy_config_dir = GetLegacyConfigurationsDirectory(); + const std::string config_dir = GetConfigurationsDirectory(); + if (legacy_config_dir == config_dir) + return; // nothing to do + + // Search for xml files directly under legacy_dir + ra::strings::StringVector files; + static const int depth = 0; // Do not search recursively + bool success = ra::filesystem::FindFilesUtf8(files, legacy_config_dir.c_str(), depth); + if (!success) + return; // aborted + + // for each file found + for (size_t i = 0; i < files.size(); i++) + { + const std::string& file_path = files[i]; + + // Is that a configuration file ? + if (ConfigFile::IsValidConfigFile(file_path)) + { + // It does not belongs there. Move it to the new configuration directory. + + std::string file_name = ra::filesystem::GetFilename(file_path.c_str()); + std::string old_path = file_path; + std::string new_path = config_dir + "\\" + file_name; + + SA_LOG(INFO) << "Moving legacy configuration file '" << old_path << "' to '" << new_path << "'."; + bool moved = RenameFileUtf8(old_path, new_path); + if (!moved) + { + SA_LOG(ERROR) << "Failed moving configuration file '" << old_path << "' to target file '" << new_path << "'."; + } + } + } + } + + void App::ClearLegacyLogsDirectory() + { + const std::string legacy_logs_dir = GetLegacyLogsDirectory(); + + // Search for log files directly under legacy_logs_dir + ra::strings::StringVector files; + static const int depth = 0; // Do not search recursively + bool success = ra::filesystem::FindFilesUtf8(files, legacy_logs_dir.c_str(), depth); + if (!success) + return; // aborted + + // for each file found + for (size_t i = 0; i < files.size(); i++) + { + const std::string& file_path = files[i]; + + // Is that a configuration file ? + if (LoggerHelper::IsValidLogFile(file_path)) + { + // It does not belongs there. + // Delete the file + SA_LOG(INFO) << "Deleting old legacy log file '" << file_path << "'."; + bool deleted = ra::filesystem::DeleteFileUtf8(file_path.c_str()); + if (!deleted) + { + SA_LOG(ERROR) << "Failed deleting old legacy log file '" << file_path << "'."; + } + } + } + + // Check if the directory is empty. + // We need to make this check before calling ra::filesystel::DeleteDirectory() because the + // DeleteDirectory function will automatically delete remaining files in order to delete the directory. + // We need to make sure the directory is empty first. + bool empty = ra::filesystem::IsDirectoryEmptyUtf8(legacy_logs_dir); + if (empty) + { + // Now it is safe to delete the directory + SA_LOG(INFO) << "Deleting old legacy log directory '" << legacy_logs_dir << "'."; + ra::filesystem::DeleteDirectoryUtf8(legacy_logs_dir.c_str()); + } + else + { + SA_LOG(ERROR) << "Skipped deleting old legacy log directory '" << legacy_logs_dir << "'. The directory is not empty. The directory likely contains files that are not log files."; + } + } + void App::InitConfigManager() { shellanything::ConfigManager& cmgr = shellanything::ConfigManager::GetInstance(); @@ -298,6 +460,8 @@ namespace shellanything if (first_run) { SA_LOG(INFO) << "First application launch."; + ClearLegacyConfigurationDirectory(); // Issue #108 moved Configuration Files directory to a new location. + ClearLegacyLogsDirectory(); // Issue #108 delete previous logs directory. InstallDefaultConfigurations(config_dir); } @@ -314,10 +478,10 @@ namespace shellanything //get home directory of the user std::string home_dir = ra::user::GetHomeDirectoryUtf8(); std::string config_dir = GetConfigurationsDirectory(); - std::string log_dir = ra::unicode::AnsiToUtf8(GetLogDirectory()); + std::string log_dir = GetLogDirectory(); SA_LOG(INFO) << "HOME directory : " << home_dir.c_str(); - SA_LOG(INFO) << "Config directory : " << config_dir.c_str(); + SA_LOG(INFO) << "CONFIG directory : " << config_dir.c_str(); SA_LOG(INFO) << "LOG directory : " << log_dir.c_str(); //define global properties diff --git a/src/core/App.h b/src/core/App.h index 18731678..ad5f7775 100644 --- a/src/core/App.h +++ b/src/core/App.h @@ -182,12 +182,33 @@ namespace shellanything /// Returns true if application is loaded in a test environment. Returns false otherwise. bool IsTestingEnvironment(); + /// + /// Get the application's legacy log directory. The returned directory has write access. + /// + /// Returns the path of the legacy directory that was used by the logging framework. + std::string GetLegacyLogsDirectory(); + /// /// Get the application's log directory. The returned directory has write access. /// /// Returns the path of the directory that should be used by the logging framework. std::string GetLogDirectory(); + /// + /// Test if the given directory is valid for logging. + /// + /// Returns true if the directory is valid for logging. Returns false otherwise. + bool IsValidLogDirectory(const std::string& path); + + /// + /// Get the application's configurations legacy directory. + /// + /// + /// Directory was change in issue #108. + /// + /// Returns the path of the legacy directory of the user's Configuration Files. + std::string GetLegacyConfigurationsDirectory(); + /// /// Get the application's configurations directory. /// @@ -195,10 +216,23 @@ namespace shellanything std::string GetConfigurationsDirectory(); /// - /// Test if the given directory is valid for logging. + /// Test if the given directory is valid for reading configurations. /// - /// Returns true if the directory is valid for logging. Returns false otherwise. - bool IsValidLogDirectory(const std::string& path); + /// Returns true if the directory is valid for reading configurations. Returns false otherwise. + bool IsValidConfigDirectory(const std::string& path); + + /// + /// Get the application's bin directory. The bin directory contains all ShellAnything's executables and binaries. + /// + /// Returns the path of the bin directory. + static std::string GetBinDirectory(); + + /// + /// Get the application's installation directory. The installation directory is the root directory selected when the application was installed. + /// When running in debug mode or within a development environment, the installation directory is set to the parent directory of `${CMAKE_BIN_DIR}\build\bin\Debug`. + /// + /// Returns the path of the installation directory. + static std::string GetInstallDirectory(); /// /// Initialize and start the application ready for usage. @@ -213,6 +247,18 @@ namespace shellanything /// The destination directory. void InstallDefaultConfigurations(const std::string& dest_dir); + /// + /// Moved any Configuration Files from the legacy directory to the official configurations directory. + /// The term legacy refers to version 0.9.0 and older. + /// + void ClearLegacyConfigurationDirectory(); + + /// + /// Delete any log files from the legacy logs directory. + /// The term legacy refers to version 0.9.0 and older. + /// + void ClearLegacyLogsDirectory(); + /// /// Initialize the Configuration Manager to the user's stall the original configuration files to the specified destination directory. /// diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 2f7e6a34..13fd8338 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -114,17 +114,17 @@ source_group("Default Configuration Files" FILES ${CONFIGURATION_DEFAUL # Copy default configuration files database to target dir add_custom_command( TARGET sa.core POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/resources/configurations $/configurations + ${CMAKE_SOURCE_DIR}/resources/configurations $/../resources/configurations COMMENT "Copying default configurations files database.") # Copy registration and unregistration scripts to target dir add_custom_command( TARGET sa.core POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_SOURCE_DIR}/resources/register.bat $/register.bat + ${CMAKE_SOURCE_DIR}/resources/installer/bin/register.bat $/register.bat COMMENT "Copying register.bat script.") add_custom_command( TARGET sa.core POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different - ${CMAKE_SOURCE_DIR}/resources/unregister.bat $/unregister.bat + ${CMAKE_SOURCE_DIR}/resources/installer/bin/unregister.bat $/unregister.bat COMMENT "Copying unregister.bat script.") # Copy files and other dependencies to target dir diff --git a/src/core/Environment.cpp b/src/core/Environment.cpp index 60de92f4..f96104bc 100644 --- a/src/core/Environment.cpp +++ b/src/core/Environment.cpp @@ -33,6 +33,8 @@ namespace shellanything static const std::string EMPTY_VALUE; const std::string Environment::SYSTEM_LOGGING_VERBOSE_ENVIRONMENT_VARIABLE_NAME = "SA_OPTION_LOGGING_VERBOSE"; + const std::string Environment::SYSTEM_CONFIGURATIONS_DIR_OVERRIDE_ENVIRONMENT_VARIABLE_NAME = "SA_OPTION_CONFIGURATIONS_DIR"; + const std::string Environment::SYSTEM_LOGS_DIR_OVERRIDE_ENVIRONMENT_VARIABLE_NAME = "SA_OPTION_LOGS_DIR"; Environment::Environment() { @@ -67,6 +69,12 @@ namespace shellanything return is_true; } + std::string Environment::GetOptionValue(const std::string& name) const + { + std::string value = ra::environment::GetEnvironmentVariableUtf8(name.c_str()); + return value; + } + bool Environment::IsOptionFalse(const std::string& name) const { std::string value = ra::environment::GetEnvironmentVariableUtf8(name.c_str()); diff --git a/src/core/Environment.h b/src/core/Environment.h index ffda1508..154470be 100644 --- a/src/core/Environment.h +++ b/src/core/Environment.h @@ -54,6 +54,16 @@ namespace shellanything /// static const std::string SYSTEM_LOGGING_VERBOSE_ENVIRONMENT_VARIABLE_NAME; + /// + /// Name of the environment variable that defines the configurations directory path override. + /// + static const std::string SYSTEM_CONFIGURATIONS_DIR_OVERRIDE_ENVIRONMENT_VARIABLE_NAME; + + /// + /// Name of the environment variable that defines the logs directory path override. + /// + static const std::string SYSTEM_LOGS_DIR_OVERRIDE_ENVIRONMENT_VARIABLE_NAME; + public: /// @@ -64,6 +74,13 @@ namespace shellanything /// Returns true if the environment variable is set. Returns false otherwise. bool IsOptionSet(const std::string& name) const; + /// + /// Get the value of an option. + /// + /// The name of the environment variable to check. + /// Returns the value of the given environment variable. + std::string GetOptionValue(const std::string& name) const; + /// /// Check if an environment variable evaluates to true. /// An empty environment variable value returns false. diff --git a/src/core/LoggerHelper.cpp b/src/core/LoggerHelper.cpp index db3be371..5a0b10ec 100644 --- a/src/core/LoggerHelper.cpp +++ b/src/core/LoggerHelper.cpp @@ -30,6 +30,8 @@ #include #include "rapidassist/strings.h" +#include "rapidassist/filesystem_utf8.h" +#include "rapidassist/unicode.h" namespace shellanything { @@ -127,6 +129,27 @@ namespace shellanything return has_vebose_logging; } + bool LoggerHelper::IsValidLogFile(const std::string& path) + { + std::string file_extension = ra::filesystem::GetFileExtention(path); + file_extension = ra::strings::Uppercase(file_extension); + + if (file_extension != "LOG") + return false; + + // Peek at the file for validating content + std::string data; + bool peeked = ra::filesystem::PeekFileUtf8(path, 1024 * 1024, data); + if (!peeked) + return false; + + bool valid = ra::unicode::IsValidUtf8(data.c_str()); + if (!valid) + return false; // the file might contain binary data + + return true; + } + // ------------------------------------------------------------------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/src/core/LoggerHelper.h b/src/core/LoggerHelper.h index b3047401..5c990af6 100644 --- a/src/core/LoggerHelper.h +++ b/src/core/LoggerHelper.h @@ -66,6 +66,13 @@ namespace shellanything /// Returns true when verbose logging is enabled. Returns false otherwise. static bool IsVerboseLoggingEnabled(); + /// + /// Detect if a given file is a valid log file. + /// + /// The file path to check + /// Returns true if the file is a valid log file. Returns false otherwise. + static bool IsValidLogFile(const std::string& path); + private: ILoggerService::LOG_LEVEL mLevel; bool mIsVerboseStream; diff --git a/src/core/PropertyManager.cpp b/src/core/PropertyManager.cpp index 455f9ae7..f15bf696 100644 --- a/src/core/PropertyManager.cpp +++ b/src/core/PropertyManager.cpp @@ -648,9 +648,8 @@ namespace shellanything shellanything::App& app = shellanything::App::GetInstance(); //define global properties - std::string prop_core_module_path = GetCurrentModulePathUtf8(); - std::string prop_application_directory = ra::filesystem::GetParentPath(prop_core_module_path); - std::string prop_install_directory = ra::filesystem::GetParentPath(prop_application_directory); + std::string prop_application_directory = app.GetBinDirectory(); + std::string prop_install_directory = app.GetInstallDirectory(); std::string prop_path_separator = ra::filesystem::GetPathSeparatorStr(); std::string prop_line_separator = ra::environment::GetLineSeparator(); diff --git a/src/shared/SaUtils.cpp b/src/shared/SaUtils.cpp index 3e1478ef..d9f09c00 100644 --- a/src/shared/SaUtils.cpp +++ b/src/shared/SaUtils.cpp @@ -27,7 +27,7 @@ #include "rapidassist/errors.h" #include "rapidassist/unicode.h" #include "rapidassist/strings.h" -#include "rapidassist/filesystem.h" +#include "rapidassist/filesystem_utf8.h" #define WIN32_LEAN_AND_MEAN 1 #include //for MAX_PATH @@ -114,6 +114,44 @@ std::string GetCurrentModulePathUtf8() return path; } +//Test if a directory allow read access to the current user. +//Note: the only way to detect if read access is available is to actually try to list files in the directory +bool HasDirectoryReadAccess(const std::string& path) +{ + //Check if the directory already exists + if (!ra::filesystem::DirectoryExists(path.c_str())) + return false; //Directory not found. Denied read access. + + ra::strings::StringVector files; + bool success = ra::filesystem::FindFiles(files, path.c_str(), 0); + if (!success) + return false; // Unable to list files in the given directory + + if (files.empty()) + return true; // Assume directory is empty + + return true; +} + +//Test if a directory allow read access to the current user. +//Note: the only way to detect if read access is available is to actually try to list files in the directory +bool HasDirectoryReadAccessUtf8(const std::string& path) +{ + //Check if the directory already exists + if (!ra::filesystem::DirectoryExistsUtf8(path.c_str())) + return false; //Directory not found. Denied read access. + + ra::strings::StringVector files; + bool success = ra::filesystem::FindFilesUtf8(files, path.c_str(), 0); + if (!success) + return false; // Unable to list files in the given directory + + if (files.empty()) + return true; // Assume directory is empty + + return true; +} + //Test if a directory allow write access to the current user. //Note: the only way to detect if write access is available is to actually write a file bool HasDirectoryWriteAccess(const std::string& path) @@ -140,6 +178,57 @@ bool HasDirectoryWriteAccess(const std::string& path) return true; } +//Test if a directory allow write access to the current user. +//Note: the only way to detect if write access is available is to actually write a file +bool HasDirectoryWriteAccessUtf8(const std::string& path) +{ + //Check if the directory already exists + if (!ra::filesystem::DirectoryExistsUtf8(path.c_str())) + return false; //Directory not found. Denied write access. + + //Generate a random filename to use as a "temporary file". + std::string filename = ra::filesystem::GetTemporaryFileName(); + + //Try to create a file. This will validate that we have write access to the directory. + std::string file_path = path + ra::filesystem::GetPathSeparatorStr() + filename; + static const std::string data = __FUNCTION__; + bool file_created = ra::filesystem::WriteFileUtf8(file_path, data); + if (!file_created) + return false; //Write is denied + + //Write is granted + + //Cleaning up + bool deleted = ra::filesystem::DeleteFileUtf8(file_path.c_str()); + + return true; +} + +bool RenameFile(const std::string& old_path, const std::string& new_path) +{ + if (std::rename(old_path.c_str(), new_path.c_str()) < 0) + { + return false; + } + return true; +} + +bool RenameFileUtf8(const std::string& old_path, const std::string& new_path) +{ +#ifndef WIN32 + // Win32 API not available, proceed with a normal ansi rename + return RenameFile(old_path, new_path); +#else + std::wstring old_path_w = ra::unicode::Utf8ToUnicode(old_path); + std::wstring new_path_w = ra::unicode::Utf8ToUnicode(new_path); + if (_wrename(old_path_w.c_str(), new_path_w.c_str()) < 0) + { + return false; + } + return true; +#endif +} + bool IsFirstApplicationRun(const std::string& name, const std::string& version) { std::string key = ra::strings::Format("HKEY_CURRENT_USER\\Software\\%s\\%s", name.c_str(), version.c_str()); diff --git a/src/shared/SaUtils.h b/src/shared/SaUtils.h index 1603ae22..ff35e85b 100644 --- a/src/shared/SaUtils.h +++ b/src/shared/SaUtils.h @@ -39,12 +39,44 @@ std::string GetCurrentModulePath(); /// Returns the path of the current DLL if successful. Returns an empty string on error. std::string GetCurrentModulePathUtf8(); +/// +/// Test if a directory has read access. +/// +/// Returns true if read access is granted. Returns false otherwise. +bool HasDirectoryReadAccess(const std::string& path); + +/// +/// Test if a directory has read access. +/// +/// Returns true if read access is granted. Returns false otherwise. +bool HasDirectoryReadAccessUtf8(const std::string& path); + /// /// Test if a directory has write access. /// /// Returns true if write access is granted. Returns false otherwise. bool HasDirectoryWriteAccess(const std::string& path); +/// +/// Test if a directory has write access. +/// +/// Returns true if write access is granted. Returns false otherwise. +bool HasDirectoryWriteAccessUtf8(const std::string& path); + +/// +/// Rename a file name to another name. +/// Paths must be specified in as absolute path. If the source directory and the target directories are not the same, the file will be also moved. +/// +/// Returns true if the operation is successful. Returns false otherwise. +bool RenameFile(const std::string& old_path, const std::string& new_path); + +/// +/// Rename a file name to another name. +/// Paths must be specified in as absolute path. If the source directory and the target directories are not the same, the file will be also moved. +/// +/// Returns true if the operation is successful. Returns false otherwise. +bool RenameFileUtf8(const std::string& old_path, const std::string& new_path); + /// /// Returns true if the application is run for the first time. /// Note, for Windows users, the implementation is based on registry keys in HKEY_CURRENT_USER\Software\name\version. diff --git a/src/tests/TestConfigManager.cpp b/src/tests/TestConfigManager.cpp index 2950c257..917b5c5f 100644 --- a/src/tests/TestConfigManager.cpp +++ b/src/tests/TestConfigManager.cpp @@ -23,6 +23,7 @@ *********************************************************************************/ #include "TestConfigManager.h" +#include "App.h" #include "Workspace.h" #include "QuickLoader.h" #include "ConfigManager.h" @@ -338,7 +339,9 @@ namespace shellanything { ConfigManager& mgr = ConfigManager::GetInstance(); - const std::string path = "configurations/default.xml"; + const std::string install_dir = shellanything::App::GetInstallDirectory(); + + const std::string path = install_dir + "/resources/configurations/default.xml"; std::string error_message = ra::testing::GetTestQualifiedName(); //init error message to an unexpected string ConfigFile* config = ConfigFile::LoadFile(path, error_message); diff --git a/src/tests/TestConfiguration.cpp b/src/tests/TestConfiguration.cpp index e8b1ec5a..8e4842c8 100644 --- a/src/tests/TestConfiguration.cpp +++ b/src/tests/TestConfiguration.cpp @@ -23,11 +23,13 @@ *********************************************************************************/ #include "TestConfiguration.h" +#include "App.h" #include "Workspace.h" #include "ConfigManager.h" #include "ConfigFile.h" #include "Menu.h" #include "ActionExecute.h" +#include "SaUtils.h" #include "rapidassist/filesystem_utf8.h" #include "rapidassist/testing.h" @@ -94,17 +96,17 @@ namespace shellanything TEST_F(TestConfiguration, testIsValidConfigFile) { static const char* files[] = { - //default configuration files - "configurations\\default.xml", - "configurations\\Microsoft Office 2003.xml", - "configurations\\Microsoft Office 2007.xml", - "configurations\\Microsoft Office 2010.xml", - "configurations\\Microsoft Office 2013.xml", - "configurations\\Microsoft Office 2016.xml", - "configurations\\shellanything.xml", - "configurations\\WinDirStat.xml", - - //test configuration files + //default configuration files (relative to bin directory) + "..\\resources\\configurations\\default.xml", + "..\\resources\\configurations\\Microsoft Office 2003.xml", + "..\\resources\\configurations\\Microsoft Office 2007.xml", + "..\\resources\\configurations\\Microsoft Office 2010.xml", + "..\\resources\\configurations\\Microsoft Office 2013.xml", + "..\\resources\\configurations\\Microsoft Office 2016.xml", + "..\\resources\\configurations\\shellanything.xml", + "..\\resources\\configurations\\WinDirStat.xml", + + //test configuration files (relative to bin directory) "test_files\\samples.xml", "test_files\\TestConfigManager.testAssignCommandId.1.xml", "test_files\\TestConfigManager.testAssignCommandId.2.xml", @@ -129,10 +131,13 @@ namespace shellanything }; const size_t num_files = sizeof(files) / sizeof(files[0]); + const std::string bin_dir = shellanything::App::GetBinDirectory(); + //for each test files for (size_t i = 0; i < num_files; i++) { - const std::string path = files[i]; + const std::string relative_path = files[i]; + const std::string path = bin_dir + "\\" + relative_path; ASSERT_TRUE(ra::filesystem::FileExists(path.c_str())) << "File '" << path.c_str() << "' is not found."; ASSERT_TRUE(shellanything::ConfigFile::IsValidConfigFile(path)) << "The file '" << path.c_str() << "' is not a valid configuration file."; } @@ -141,7 +146,8 @@ namespace shellanything TEST_F(TestConfiguration, testIsValidConfigFileUtf8) { static const std::string separator = ra::filesystem::GetPathSeparatorStr(); - const std::string source_path = "configurations/default.xml"; + const std::string install_dir = shellanything::App::GetInstallDirectory(); + const std::string source_path = install_dir + "/resources/configurations/default.xml"; std::string target_path = ra::filesystem::GetTemporaryDirectory() + separator + ra::testing::GetTestQualifiedName() + ".psi_\xCE\xA8_psi.xml"; //copy default config to the new utf-8 path @@ -153,7 +159,8 @@ namespace shellanything //-------------------------------------------------------------------------------------------------- TEST_F(TestConfiguration, testLoadFile) { - const std::string path = "configurations/default.xml"; + const std::string install_dir = shellanything::App::GetInstallDirectory(); + const std::string path = install_dir + "/resources/configurations/default.xml"; std::string error_message = ra::testing::GetTestQualifiedName(); //init error message to an unexpected string ConfigFile* config = ConfigFile::LoadFile(path, error_message); @@ -168,8 +175,9 @@ namespace shellanything { //This test validates that Configuration::LoadFile() supports filename with utf-8 characters. + const std::string install_dir = shellanything::App::GetInstallDirectory(); static const std::string separator = ra::filesystem::GetPathSeparatorStr(); - const std::string source_path = "configurations/default.xml"; + const std::string source_path = install_dir + "/resources/configurations/default.xml"; std::string target_path = ra::filesystem::GetTemporaryDirectory() + separator + ra::testing::GetTestQualifiedName() + ".psi_\xCE\xA8_psi.xml"; //copy default config to the new utf-8 path diff --git a/src/tests/TestSaUtils.cpp b/src/tests/TestSaUtils.cpp index 1c634d08..6035d1d7 100644 --- a/src/tests/TestSaUtils.cpp +++ b/src/tests/TestSaUtils.cpp @@ -29,6 +29,10 @@ #include "TestSaUtils.h" #include "SaUtils.h" +#include "rapidassist/testing_utf8.h" +#include "rapidassist/environment_utf8.h" +#include "rapidassist/random.h" +#include "rapidassist/filesystem_utf8.h" namespace shellanything { @@ -64,6 +68,33 @@ namespace shellanything ASSERT_TRUE(IsPrintableUtf8("Espa" "\xc3" "\xb1" "ol")); // Español } //-------------------------------------------------------------------------------------------------- + TEST_F(TestSaUtils, testRenameFileUtf8) + { + static const std::string filename_characters = "abcdefghijklmnopqrstuvwxyz0123456789_"; + std::string temp_dir = ra::environment::GetEnvironmentVariableUtf8("TEMP"); + std::string old_filename = ra::random::GetRandomString(16, filename_characters.c_str()) + ".tmp"; + std::string new_filename = ra::random::GetRandomString(16, filename_characters.c_str()) + ".tmp"; + + std::string old_path = temp_dir + "\\" + old_filename; + std::string new_path = temp_dir + "\\" + new_filename; + + // assert pre-state + ASSERT_TRUE(ra::testing::CreateFileUtf8(old_path.c_str(), 10240)) << "Failed to create file: " << old_path; + ASSERT_FALSE(ra::filesystem::FileExistsUtf8(new_path.c_str())); + + // assert operation is successful + bool renamed = RenameFileUtf8(old_path, new_path); + ASSERT_TRUE(renamed); + + // assert files are actualy renamed + ASSERT_FALSE(ra::filesystem::FileExistsUtf8(old_path.c_str())); + ASSERT_TRUE (ra::filesystem::FileExistsUtf8(new_path.c_str())); + + // cleanup + ra::filesystem::DeleteFileUtf8(old_path.c_str()); + ra::filesystem::DeleteFileUtf8(new_path.c_str()); + } + //-------------------------------------------------------------------------------------------------- } //namespace test } //namespace shellanything diff --git a/src/tests/TestSelectionContext.cpp b/src/tests/TestSelectionContext.cpp index a58249e2..14a6f8d7 100644 --- a/src/tests/TestSelectionContext.cpp +++ b/src/tests/TestSelectionContext.cpp @@ -23,8 +23,11 @@ *********************************************************************************/ #include "TestSelectionContext.h" +#include "App.h" #include "SelectionContext.h" #include "PropertyManager.h" +#include "SaUtils.h" + #include "rapidassist/process.h" #include "rapidassist/filesystem.h" #include "rapidassist/testing.h" @@ -525,11 +528,13 @@ namespace shellanything { PropertyManager& pmgr = PropertyManager::GetInstance(); + const std::string install_dir = shellanything::App::GetInstallDirectory(); + SelectionContext context; #ifdef _WIN32 { StringList elements; - elements.push_back(ra::process::GetCurrentProcessDir() + "\\configurations\\default.xml"); + elements.push_back(install_dir + "\\resources\\configurations\\default.xml"); context.SetElements(elements); } #else