From e8fcb025a06eca6923f00228c3318501fbb93263 Mon Sep 17 00:00:00 2001 From: Mike Gelfand Date: Thu, 27 Apr 2023 15:10:33 +0100 Subject: [PATCH] fix: use user-preferred locale (#5444) * Use user-preferred locale Previous fix adding `L` format specifier was correct but only fixed half of the problem, as C++ locale is set up to be "C" by default. GTK client used to call `setlocale(LC_ALL, "")` to set up user-preferred locale which only affected C functions and `std::locale` (used by libfmt) was unaware of those changes. Apply the fix to all the binaries since they're all doing some sort of output to the user and calling libtransmission helpers, as well as using libfmt directly. * Improve libtransmission's json-test Set the locale C++ way to avoid any sort of inconsistencies, and also restore it to the old one once finished testing. * Improve transmission-show test runner script Quote outputs to avoid CMake error about `message()` being called with no arguments. Capture stderr to the same output file. Fallback to `git diff` if `diff` wasn't found. A few other minor changes. --- cli/cli.cc | 2 ++ daemon/daemon.cc | 2 ++ gtk/main.cc | 4 +--- libtransmission/utils.cc | 20 ++++++++++++++++++++ libtransmission/utils.h | 2 ++ macosx/main.mm | 6 ++++++ qt/Application.cc | 2 ++ tests/libtransmission/json-test.cc | 21 +++++++++++++++++++-- tests/utils/run_transmission_show.cmake | 25 ++++++++++++++++--------- utils/create.cc | 2 ++ utils/edit.cc | 2 ++ utils/remote.cc | 2 ++ utils/show.cc | 2 ++ 13 files changed, 78 insertions(+), 14 deletions(-) diff --git a/cli/cli.cc b/cli/cli.cc index d4858a388..c8ef33038 100644 --- a/cli/cli.cc +++ b/cli/cli.cc @@ -199,6 +199,8 @@ static std::string getConfigDir(int argc, char const** argv) int tr_main(int argc, char* argv[]) { + tr_locale_set_global(""); + tr_variant settings; tr_formatter_mem_init(MemK, MemKStr, MemMStr, MemGStr, MemTStr); diff --git a/daemon/daemon.cc b/daemon/daemon.cc index 08de7efd2..335b32fcd 100644 --- a/daemon/daemon.cc +++ b/daemon/daemon.cc @@ -939,6 +939,8 @@ void tr_daemon::handle_error(tr_error* error) const int tr_main(int argc, char* argv[]) { + tr_locale_set_global(""); + int ret; tr_daemon daemon; bool foreground; diff --git a/gtk/main.cc b/gtk/main.cc index 54252c21b..b59fec441 100644 --- a/gtk/main.cc +++ b/gtk/main.cc @@ -27,10 +27,8 @@ #include -#include #include #include -#include namespace { @@ -53,7 +51,7 @@ Glib::OptionEntry create_option_entry(Glib::ustring const& long_name, gchar shor int main(int argc, char** argv) { /* init i18n */ - std::ignore = std::setlocale(LC_ALL, ""); + tr_locale_set_global(""); bindtextdomain(AppTranslationDomainName, TRANSMISSIONLOCALEDIR); bind_textdomain_codeset(AppTranslationDomainName, "UTF-8"); textdomain(AppTranslationDomainName); diff --git a/libtransmission/utils.cc b/libtransmission/utils.cc index 6182ce393..38954e3b5 100644 --- a/libtransmission/utils.cc +++ b/libtransmission/utils.cc @@ -14,11 +14,14 @@ #include // getenv() #include /* strerror() */ #include // nanosleep() +#include #include // for std::back_inserter +#include #include #include #include #include +#include #include #ifdef _WIN32 @@ -60,6 +63,23 @@ time_t libtransmission::detail::tr_time::current_time = {}; // --- +void tr_locale_set_global(char const* locale_name) noexcept +{ + try + { + std::ignore = std::locale::global(std::locale{ locale_name }); + + std::ignore = std::cout.imbue(std::locale{}); + std::ignore = std::cerr.imbue(std::locale{}); + } + catch (std::exception const&) + { + // Ignore. + } +} + +// --- + bool tr_loadFile(std::string_view filename, std::vector& contents, tr_error** error) { auto const szfilename = tr_pathbuf{ filename }; diff --git a/libtransmission/utils.h b/libtransmission/utils.h index 24c3e9dc0..cd70dcf89 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -56,6 +56,8 @@ struct tr_error; #define tr_ngettext(singular, plural, count) ((count) == 1 ? (singular) : (plural)) #endif +void tr_locale_set_global(char const* locale_name) noexcept; + // --- [[nodiscard]] std::string_view tr_get_mime_type_for_filename(std::string_view filename); diff --git a/macosx/main.mm b/macosx/main.mm index bf915af14..17cfadb6f 100644 --- a/macosx/main.mm +++ b/macosx/main.mm @@ -4,7 +4,13 @@ @import AppKit; +#include + +#include + int main(int argc, char** argv) { + tr_locale_set_global(""); + return NSApplicationMain(argc, (char const**)argv); } diff --git a/qt/Application.cc b/qt/Application.cc index 76dd2b1b5..6b883a8fe 100644 --- a/qt/Application.cc +++ b/qt/Application.cc @@ -662,6 +662,8 @@ void Application::onNotificationActionInvoked(quint32 /* notification_id */, QSt int tr_main(int argc, char** argv) { + tr_locale_set_global(""); + InteropHelper::initialize(); Application const app(argc, argv); diff --git a/tests/libtransmission/json-test.cc b/tests/libtransmission/json-test.cc index 5e52b412d..5e53e8460 100644 --- a/tests/libtransmission/json-test.cc +++ b/tests/libtransmission/json-test.cc @@ -5,9 +5,11 @@ #define LIBTRANSMISSION_VARIANT_MODULE -#include // setlocale() +#include +#include #include #include +#include #include #include @@ -23,11 +25,26 @@ protected: void SetUp() override { auto const* locale_str = GetParam(); - if (setlocale(LC_NUMERIC, locale_str) == nullptr) + try + { + old_locale_ = std::locale::global(std::locale{ {}, new std::numpunct_byname{ locale_str } }); + } + catch (std::runtime_error const&) { GTEST_SKIP(); } } + + void TearDown() override + { + if (old_locale_) + { + std::ignore = std::locale::global(*old_locale_); + } + } + +private: + std::optional old_locale_; }; TEST_P(JSONTest, testElements) diff --git a/tests/utils/run_transmission_show.cmake b/tests/utils/run_transmission_show.cmake index e3c526cf4..8c29ff36b 100644 --- a/tests/utils/run_transmission_show.cmake +++ b/tests/utils/run_transmission_show.cmake @@ -4,7 +4,7 @@ if(CMAKE_VERSION VERSION_LESS 3.14) # --ignore-eol was introduced in CMake 3.14 - message(status "skipping transmission-show test; cmake version too old") + message(STATUS "skipping transmission-show test; cmake version too old") else() get_filename_component(torrent_basename "${torrent_file}" NAME) set(output_file ${CMAKE_CURRENT_BINARY_DIR}/${torrent_basename}.out) @@ -23,7 +23,8 @@ else() execute_process( COMMAND ${transmission_show} ${torrent_file} - OUTPUT_FILE ${output_file}) + OUTPUT_FILE ${output_file} + ERROR_FILE ${output_file}) execute_process( COMMAND ${CMAKE_COMMAND} -E compare_files --ignore-eol ${reference_file} ${output_file} @@ -31,23 +32,29 @@ else() if(STATUS AND NOT STATUS EQUAL 0) file(READ ${reference_file} CONTENTS) - message("EXPECTED CONTENTS (${reference_file}):") - message(${CONTENTS}) + message(STATUS "EXPECTED CONTENTS (${reference_file}):") + message("${CONTENTS}") file(READ ${output_file} CONTENTS) - message("RECEIVED CONTENTS (${output_file}):") - message(${CONTENTS}) + message(STATUS "RECEIVED CONTENTS (${output_file}):") + message("${CONTENTS}") find_program(DIFF_EXEC diff) + if(NOT DIFF_EXEC) + find_program(GIT_EXEC git) + if(GIT_EXEC) + set(DIFF_EXEC "${GIT_EXEC}" diff --no-index) + endif() + endif() if(DIFF_EXEC) - message("DIFF:") - execute_process(COMMAND ${DIFF_EXEC} -u ${output_file} ${reference_file}) + message(STATUS "DIFF:") + execute_process(COMMAND ${DIFF_EXEC} --unified ${reference_file} ${output_file}) endif() file(REMOVE ${output_file}) message(FATAL_ERROR "failed: files '${reference_file}' and '${output_file}' do not match") else() file(REMOVE ${output_file}) - message("passed") + message(STATUS "passed") endif() endif() diff --git a/utils/create.cc b/utils/create.cc index a0fe61569..b9b8e51dc 100644 --- a/utils/create.cc +++ b/utils/create.cc @@ -143,6 +143,8 @@ std::string tr_getcwd() int tr_main(int argc, char* argv[]) { + tr_locale_set_global(""); + tr_logSetLevel(TR_LOG_ERROR); tr_formatter_mem_init(MemK, MemKStr, MemMStr, MemGStr, MemTStr); tr_formatter_size_init(DiskK, DiskKStr, DiskMStr, DiskGStr, DiskTStr); diff --git a/utils/edit.cc b/utils/edit.cc index 396348c88..1dbee6a90 100644 --- a/utils/edit.cc +++ b/utils/edit.cc @@ -322,6 +322,8 @@ static bool setSource(tr_variant* metainfo, char const* source_value) int tr_main(int argc, char* argv[]) { + tr_locale_set_global(""); + int changedCount = 0; tr_logSetLevel(TR_LOG_ERROR); diff --git a/utils/remote.cc b/utils/remote.cc index 0f96d3078..e87088e16 100644 --- a/utils/remote.cc +++ b/utils/remote.cc @@ -3276,6 +3276,8 @@ static void getHostAndPortAndRpcUrl(int* argc, char** argv, std::string* host, i int tr_main(int argc, char* argv[]) { + tr_locale_set_global(""); + auto config = Config{}; auto port = DefaultPort; auto host = std::string{}; diff --git a/utils/show.cc b/utils/show.cc index 3834bc0e5..6cf313dd4 100644 --- a/utils/show.cc +++ b/utils/show.cc @@ -398,6 +398,8 @@ void doScrape(tr_torrent_metainfo const& metainfo) int tr_main(int argc, char* argv[]) { + tr_locale_set_global(""); + tr_logSetQueueEnabled(false); tr_logSetLevel(TR_LOG_ERROR); tr_formatter_mem_init(MemK, MemKStr, MemMStr, MemGStr, MemTStr);