From bf48eadaeb8f019176d4461bb7647c7d1d3beb7f Mon Sep 17 00:00:00 2001 From: Charles Kerr Date: Tue, 9 Dec 2025 22:09:49 -0600 Subject: [PATCH] refactor: replace Settings class with Serializable (#7877) * refactor: replace Settings class with Serializable * Fields can now be declared as const static, so we only have to build this list once per class instead of once per iteration. * Add typesafe single-property getters & setters. * Split the converter registry into a generic standalone class. * refactor: make Serializable::Field::getter private refactor: make Serializable::Field::const_getter private * docs: tweak code comments * refactor: make Serializable::Field::Getter private refactor: make Serializable::Field::ConstGetter private refactor: make Serializable::Field::MemberStorage private * chore: fix readability-identifier-naming clang-tidy warnings * Update libtransmission/serializable.h Co-authored-by: Yat Ho * Update libtransmission/serializable.h Co-authored-by: Yat Ho * Update libtransmission/serializable.h Co-authored-by: Yat Ho * fixup! Update libtransmission/serializable.h --------- Co-authored-by: Yat Ho --- Transmission.xcodeproj/project.pbxproj | 16 +- libtransmission/CMakeLists.txt | 4 +- libtransmission/rpc-server.h | 42 +-- .../{settings.cc => serializable.cc} | 180 +++--------- libtransmission/serializable.h | 265 ++++++++++++++++++ libtransmission/session-alt-speeds.h | 28 +- libtransmission/session.h | 139 +++++---- libtransmission/settings.h | 88 ------ 8 files changed, 424 insertions(+), 338 deletions(-) rename libtransmission/{settings.cc => serializable.cc} (66%) create mode 100644 libtransmission/serializable.h delete mode 100644 libtransmission/settings.h diff --git a/Transmission.xcodeproj/project.pbxproj b/Transmission.xcodeproj/project.pbxproj index d5fefb058..5028027f6 100644 --- a/Transmission.xcodeproj/project.pbxproj +++ b/Transmission.xcodeproj/project.pbxproj @@ -452,8 +452,8 @@ ED5E0EA22CD314FE0071433B /* style.css in Resources */ = {isa = PBXBuildFile; fileRef = ED5E0EA12CD314FE0071433B /* style.css */; }; ED5E0EFA2CD315720071433B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = ED5E0EF72CD315720071433B /* Localizable.strings */; }; ED5E0F0F2CD31BC20071433B /* NSStringAdditions.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4DE5CC9C0980656F00BE280E /* NSStringAdditions.mm */; }; - ED67FB422B70FCE400D8A037 /* settings.cc in Sources */ = {isa = PBXBuildFile; fileRef = ED67FB402B70FCE400D8A037 /* settings.cc */; }; - ED67FB432B70FCE400D8A037 /* settings.h in Headers */ = {isa = PBXBuildFile; fileRef = ED67FB412B70FCE400D8A037 /* settings.h */; }; + ED67FB422B70FCE400D8A037 /* serializable.cc in Sources */ = {isa = PBXBuildFile; fileRef = ED67FB402B70FCE400D8A037 /* serializable.cc */; }; + ED67FB432B70FCE400D8A037 /* serializable.h in Headers */ = {isa = PBXBuildFile; fileRef = ED67FB412B70FCE400D8A037 /* serializable.h */; }; ED6F16B52EB8F1EB007CD864 /* FileNameCellView.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED6F16B22EB8F1EB007CD864 /* FileNameCellView.mm */; }; ED6F16B62EB8F1EB007CD864 /* FilePriorityCellView.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED6F16B42EB8F1EB007CD864 /* FilePriorityCellView.mm */; }; ED6F16B72EB8F1EB007CD864 /* FileCheckCellView.mm in Sources */ = {isa = PBXBuildFile; fileRef = ED6F16B02EB8F1EB007CD864 /* FileCheckCellView.mm */; }; @@ -1439,8 +1439,8 @@ ED5E0F0C2CD3163B0071433B /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Localizable.strings; sourceTree = ""; }; ED5E0F0D2CD316450071433B /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = ""; }; ED5E0F0E2CD3164D0071433B /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = ""; }; - ED67FB402B70FCE400D8A037 /* settings.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = settings.cc; sourceTree = ""; }; - ED67FB412B70FCE400D8A037 /* settings.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = settings.h; sourceTree = ""; }; + ED67FB402B70FCE400D8A037 /* serializable.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = serializable.cc; sourceTree = ""; }; + ED67FB412B70FCE400D8A037 /* serializable.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = serializable.h; sourceTree = ""; }; ED6F16AF2EB8F1EB007CD864 /* FileCheckCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileCheckCellView.h; sourceTree = ""; }; ED6F16B02EB8F1EB007CD864 /* FileCheckCellView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FileCheckCellView.mm; sourceTree = ""; }; ED6F16B12EB8F1EB007CD864 /* FileNameCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileNameCellView.h; sourceTree = ""; }; @@ -2005,8 +2005,8 @@ BEFC1E140C07861A00B0BB3C /* session.h */, CCEBA596277340F6DF9F4481 /* session-alt-speeds.cc */, CCEBA596277340F6DF9F4483 /* session-alt-speeds.h */, - ED67FB402B70FCE400D8A037 /* settings.cc */, - ED67FB412B70FCE400D8A037 /* settings.h */, + ED67FB402B70FCE400D8A037 /* serializable.cc */, + ED67FB412B70FCE400D8A037 /* serializable.h */, A25D2CBB0CF4C7190096A262 /* stats.cc */, A25D2CBA0CF4C7190096A262 /* stats.h */, C11DEA141FCD31C0009E22B9 /* subprocess-posix.cc */, @@ -2600,7 +2600,7 @@ A29DF8BE0DB2545F00D04E5A /* verify.h in Headers */, C1FEE57B1C3223CC00D62832 /* watchdir.h in Headers */, A2AAB6650DE0D08B00E04DDA /* blocklist.h in Headers */, - ED67FB432B70FCE400D8A037 /* settings.h in Headers */, + ED67FB432B70FCE400D8A037 /* serializable.h in Headers */, A2A4E9210DE0F7E9000CE197 /* web.h in Headers */, A25E03E20E4015380086C225 /* tr-getopt.h in Headers */, A21FBBAB0EDA78C300BC3C51 /* bandwidth.h in Headers */, @@ -3478,7 +3478,7 @@ C1033E081A3279B800EF44D8 /* crypto-utils-ccrypto.cc in Sources */, A22CFCA80FC24ED80009BD3E /* tr-dht.cc in Sources */, 0A6169A70FE5C9A200C66CE6 /* bitfield.cc in Sources */, - ED67FB422B70FCE400D8A037 /* settings.cc in Sources */, + ED67FB422B70FCE400D8A037 /* serializable.cc in Sources */, 1BB44E07B1B52E28291B4E32 /* file-piece-map.cc in Sources */, A25964A6106D73A800453B31 /* announcer.cc in Sources */, 66F977825E65AD498C028BB0 /* announce-list.cc in Sources */, diff --git a/libtransmission/CMakeLists.txt b/libtransmission/CMakeLists.txt index 500dd3cfd..b76522577 100644 --- a/libtransmission/CMakeLists.txt +++ b/libtransmission/CMakeLists.txt @@ -110,6 +110,8 @@ target_sources(${TR_NAME} rpc-server.h rpcimpl.cc rpcimpl.h + serializable.cc + serializable.h session-alt-speeds.cc session-alt-speeds.h session-id.cc @@ -118,8 +120,6 @@ target_sources(${TR_NAME} session-thread.h session.cc session.h - settings.cc - settings.h stats.cc stats.h subprocess-posix.cc diff --git a/libtransmission/rpc-server.h b/libtransmission/rpc-server.h index 3dc0b304d..2bb854493 100644 --- a/libtransmission/rpc-server.h +++ b/libtransmission/rpc-server.h @@ -9,6 +9,7 @@ #error only libtransmission should #include this header. #endif +#include #include // size_t #include #include @@ -19,7 +20,7 @@ #include "libtransmission/net.h" #include "libtransmission/quark.h" -#include "libtransmission/settings.h" +#include "libtransmission/serializable.h" #include "libtransmission/utils-ev.h" class tr_rpc_address; @@ -35,7 +36,7 @@ class Timer; class tr_rpc_server { public: - class Settings final : public libtransmission::Settings + class Settings final : public libtransmission::Serializable { public: Settings() = default; @@ -63,25 +64,24 @@ public: tr_port port = tr_port::from_host(TrDefaultRpcPort); private: - [[nodiscard]] Fields fields() override - { - return { - { TR_KEY_anti_brute_force_enabled, &is_anti_brute_force_enabled }, - { TR_KEY_anti_brute_force_threshold, &anti_brute_force_limit }, - { TR_KEY_rpc_authentication_required, &authentication_required }, - { TR_KEY_rpc_bind_address, &bind_address_str }, - { TR_KEY_rpc_enabled, &is_enabled }, - { TR_KEY_rpc_host_whitelist, &host_whitelist_str }, - { TR_KEY_rpc_host_whitelist_enabled, &is_host_whitelist_enabled }, - { TR_KEY_rpc_port, &port }, - { TR_KEY_rpc_password, &salted_password }, - { TR_KEY_rpc_socket_mode, &socket_mode }, - { TR_KEY_rpc_url, &url }, - { TR_KEY_rpc_username, &username }, - { TR_KEY_rpc_whitelist, &whitelist_str }, - { TR_KEY_rpc_whitelist_enabled, &is_whitelist_enabled }, - }; - } + friend libtransmission::Serializable; + + static inline auto const fields = std::array{ { + { TR_KEY_anti_brute_force_enabled, &Settings::is_anti_brute_force_enabled }, + { TR_KEY_anti_brute_force_threshold, &Settings::anti_brute_force_limit }, + { TR_KEY_rpc_authentication_required, &Settings::authentication_required }, + { TR_KEY_rpc_bind_address, &Settings::bind_address_str }, + { TR_KEY_rpc_enabled, &Settings::is_enabled }, + { TR_KEY_rpc_host_whitelist, &Settings::host_whitelist_str }, + { TR_KEY_rpc_host_whitelist_enabled, &Settings::is_host_whitelist_enabled }, + { TR_KEY_rpc_port, &Settings::port }, + { TR_KEY_rpc_password, &Settings::salted_password }, + { TR_KEY_rpc_socket_mode, &Settings::socket_mode }, + { TR_KEY_rpc_url, &Settings::url }, + { TR_KEY_rpc_username, &Settings::username }, + { TR_KEY_rpc_whitelist, &Settings::whitelist_str }, + { TR_KEY_rpc_whitelist_enabled, &Settings::is_whitelist_enabled }, + } }; }; tr_rpc_server(tr_session* session, Settings&& settings); diff --git a/libtransmission/settings.cc b/libtransmission/serializable.cc similarity index 66% rename from libtransmission/settings.cc rename to libtransmission/serializable.cc index 44d1ba8cf..74d8eb500 100644 --- a/libtransmission/settings.cc +++ b/libtransmission/serializable.cc @@ -24,7 +24,7 @@ #include "libtransmission/net.h" // for tr_port #include "libtransmission/open-files.h" // for tr_open_files::Preallocation #include "libtransmission/peer-io.h" // tr_preferred_transport -#include "libtransmission/settings.h" +#include "libtransmission/serializable.h" #include "libtransmission/utils.h" // for tr_strv_strip(), tr_strlower() #include "libtransmission/variant.h" #include "libtransmission/tr-assert.h" @@ -40,7 +40,7 @@ using Lookup = std::array, N>; // --- -bool load_bool(tr_variant const& src, bool* tgt) +bool to_bool(tr_variant const& src, bool* tgt) { if (auto val = src.value_if()) { @@ -51,14 +51,14 @@ bool load_bool(tr_variant const& src, bool* tgt) return false; } -tr_variant save_bool(bool const& val) +tr_variant from_bool(bool const& val) { return val; } // --- -bool load_double(tr_variant const& src, double* tgt) +bool to_double(tr_variant const& src, double* tgt) { if (auto val = src.value_if()) { @@ -69,7 +69,7 @@ bool load_double(tr_variant const& src, double* tgt) return false; } -tr_variant save_double(double const& val) +tr_variant from_double(double const& val) { return val; } @@ -82,7 +82,7 @@ auto constexpr EncryptionKeys = Lookup{ { { "allowed", TR_CLEAR_PREFERRED }, } }; -bool load_encryption_mode(tr_variant const& src, tr_encryption_mode* tgt) +bool to_encryption_mode(tr_variant const& src, tr_encryption_mode* tgt) { static constexpr auto& Keys = EncryptionKeys; @@ -115,7 +115,7 @@ bool load_encryption_mode(tr_variant const& src, tr_encryption_mode* tgt) return false; } -tr_variant save_encryption_mode(tr_encryption_mode const& val) +tr_variant from_encryption_mode(tr_encryption_mode const& val) { return static_cast(val); } @@ -132,7 +132,7 @@ auto constexpr LogKeys = Lookup{ { { "warn", TR_LOG_WARN }, } }; -bool load_log_level(tr_variant const& src, tr_log_level* tgt) +bool to_log_level(tr_variant const& src, tr_log_level* tgt) { static constexpr auto& Keys = LogKeys; @@ -165,14 +165,14 @@ bool load_log_level(tr_variant const& src, tr_log_level* tgt) return false; } -tr_variant save_log_level(tr_log_level const& val) +tr_variant from_log_level(tr_log_level const& val) { return static_cast(val); } // --- -bool load_mode_t(tr_variant const& src, tr_mode_t* tgt) +bool to_mode_t(tr_variant const& src, tr_mode_t* tgt) { if (auto const val = src.value_if()) { @@ -192,14 +192,14 @@ bool load_mode_t(tr_variant const& src, tr_mode_t* tgt) return false; } -tr_variant save_mode_t(tr_mode_t const& val) +tr_variant from_mode_t(tr_mode_t const& val) { return fmt::format("{:#03o}", val); } // --- -bool load_msec(tr_variant const& src, std::chrono::milliseconds* tgt) +bool to_msec(tr_variant const& src, std::chrono::milliseconds* tgt) { if (auto val = src.value_if()) { @@ -210,14 +210,14 @@ bool load_msec(tr_variant const& src, std::chrono::milliseconds* tgt) return false; } -tr_variant save_msec(std::chrono::milliseconds const& src) +tr_variant from_msec(std::chrono::milliseconds const& src) { return src.count(); } // --- -bool load_port(tr_variant const& src, tr_port* tgt) +bool to_port(tr_variant const& src, tr_port* tgt) { if (auto const val = src.value_if()) { @@ -228,7 +228,7 @@ bool load_port(tr_variant const& src, tr_port* tgt) return false; } -tr_variant save_port(tr_port const& val) +tr_variant from_port(tr_port const& val) { return int64_t{ val.host() }; } @@ -243,7 +243,7 @@ auto constexpr PreallocationKeys = Lookup{ { { "full", tr_open_files::Preallocation::Full }, } }; -bool load_preallocation_mode(tr_variant const& src, tr_open_files::Preallocation* tgt) +bool to_preallocation_mode(tr_variant const& src, tr_open_files::Preallocation* tgt) { static constexpr auto& Keys = PreallocationKeys; @@ -276,7 +276,7 @@ bool load_preallocation_mode(tr_variant const& src, tr_open_files::Preallocation return false; } -tr_variant save_preallocation_mode(tr_open_files::Preallocation const& val) +tr_variant from_preallocation_mode(tr_open_files::Preallocation const& val) { return static_cast(val); } @@ -288,7 +288,7 @@ auto constexpr PreferredTransportKeys = Lookup* tgt) { @@ -350,7 +350,7 @@ bool load_preferred_transport( return true; } -tr_variant save_preferred_transport(small::max_size_vector const& val) +tr_variant from_preferred_transport(small::max_size_vector const& val) { static auto constexpr SaveSingle = [](tr_preferred_transport const ele) -> tr_variant { @@ -377,7 +377,7 @@ tr_variant save_preferred_transport(small::max_size_vector()) { @@ -388,14 +388,14 @@ bool load_size_t(tr_variant const& src, size_t* tgt) return false; } -tr_variant save_size_t(size_t const& val) +tr_variant from_size_t(size_t const& val) { return uint64_t{ val }; } // --- -bool load_string(tr_variant const& src, std::string* tgt) +bool to_string(tr_variant const& src, std::string* tgt) { if (auto const val = src.value_if()) { @@ -406,14 +406,14 @@ bool load_string(tr_variant const& src, std::string* tgt) return false; } -tr_variant save_string(std::string const& val) +tr_variant from_string(std::string const& val) { return val; } // --- -bool load_nullable_string(tr_variant const& src, std::optional* tgt) +bool to_optional_string(tr_variant const& src, std::optional* tgt) { if (src.holds_alternative()) { @@ -430,14 +430,14 @@ bool load_nullable_string(tr_variant const& src, std::optional* tgt return false; } -tr_variant save_nullable_string(std::optional const& val) +tr_variant from_optional_string(std::optional const& val) { return val ? tr_variant{ *val } : nullptr; } // --- -bool load_tos_t(tr_variant const& src, tr_tos_t* tgt) +bool to_tos_t(tr_variant const& src, tr_tos_t* tgt) { if (auto const val = src.value_if()) { @@ -459,7 +459,7 @@ bool load_tos_t(tr_variant const& src, tr_tos_t* tgt) return false; } -tr_variant save_tos_t(tr_tos_t const& val) +tr_variant from_tos_t(tr_tos_t const& val) { return val.toString(); } @@ -471,7 +471,7 @@ auto constexpr VerifyModeKeys = Lookup{ { { "full", TR_VERIFY_ADDED_FULL }, } }; -bool load_verify_added_mode(tr_variant const& src, tr_verify_added_mode* tgt) +bool to_verify_added_mode(tr_variant const& src, tr_verify_added_mode* tgt) { static constexpr auto& Keys = VerifyModeKeys; @@ -504,7 +504,7 @@ bool load_verify_added_mode(tr_variant const& src, tr_verify_added_mode* tgt) return false; } -tr_variant save_verify_added_mode(tr_verify_added_mode const& val) +tr_variant from_verify_added_mode(tr_verify_added_mode const& val) { for (auto const& [key, value] : VerifyModeKeys) { @@ -518,111 +518,21 @@ tr_variant save_verify_added_mode(tr_verify_added_mode const& val) } } // unnamed namespace -Settings::Settings() -{ - add_type_handler(load_bool, save_bool); - add_type_handler(load_double, save_double); - add_type_handler(load_encryption_mode, save_encryption_mode); - add_type_handler(load_log_level, save_log_level); - add_type_handler(load_mode_t, save_mode_t); - add_type_handler(load_msec, save_msec); - add_type_handler(load_port, save_port); - add_type_handler(load_preallocation_mode, save_preallocation_mode); - add_type_handler(load_preferred_transport, save_preferred_transport); - add_type_handler(load_size_t, save_size_t); - add_type_handler(load_string, save_string); - add_type_handler(load_nullable_string, save_nullable_string); - add_type_handler(load_tos_t, save_tos_t); - add_type_handler(load_verify_added_mode, save_verify_added_mode); -} +Serializers::ConvertersMap Serializers::converters = { { + Serializers::build_converter_entry(to_bool, from_bool), + Serializers::build_converter_entry(to_double, from_double), + Serializers::build_converter_entry(to_encryption_mode, from_encryption_mode), + Serializers::build_converter_entry(to_log_level, from_log_level), + Serializers::build_converter_entry(to_mode_t, from_mode_t), + Serializers::build_converter_entry(to_msec, from_msec), + Serializers::build_converter_entry(to_optional_string, from_optional_string), + Serializers::build_converter_entry(to_port, from_port), + Serializers::build_converter_entry(to_preallocation_mode, from_preallocation_mode), + Serializers::build_converter_entry(to_preferred_transport, from_preferred_transport), + Serializers::build_converter_entry(to_size_t, from_size_t), + Serializers::build_converter_entry(to_string, from_string), + Serializers::build_converter_entry(to_tos_t, from_tos_t), + Serializers::build_converter_entry(to_verify_added_mode, from_verify_added_mode), +} }; -void Settings::load(tr_variant const& src) -{ - auto const* map = src.get_if(); - if (map == nullptr) - { - return; - } - - for (auto& field : fields()) - { - if (auto const iter = map->find(field.key); iter != std::end(*map)) - { - auto const type_index = std::type_index{ field.type }; - TR_ASSERT(load_.count(type_index) == 1U); - load_.at(type_index)(iter->second, field.ptr); - } - } -} - -bool Settings::load_single(tr_quark const key, tr_variant const& src) -{ - auto const fields = this->fields(); - auto const field_it = std::lower_bound( - std::begin(fields), - std::end(fields), - key, - [](Field const& f, tr_quark k) { return f.key < k; }); - if (field_it == std::end(fields) || field_it->key != key) - { - return false; - } - - auto const type_index = std::type_index{ field_it->type }; - TR_ASSERT(load_.count(type_index) == 1U); - return load_.at(type_index)(src, field_it->ptr); -} - -tr_variant::Map Settings::save_partial(std::vector quarks) const -{ - static constexpr struct - { - constexpr bool operator()(Field const& lhs, tr_quark const rhs) const noexcept - { - return lhs.key < rhs; - } - - constexpr bool operator()(tr_quark const lhs, Field const& rhs) const noexcept - { - return lhs < rhs.key; - } - } Compare; - - auto const fields = const_cast(this)->fields(); - auto to_save = Fields{}; - to_save.reserve(std::min(std::size(quarks), std::size(fields))); - - // N.B. `fields` is supposed to be unique and sorted, so we don't need to sort it, - // and we don't need to worry about duplicates in `to_save` - std::sort(std::begin(quarks), std::end(quarks)); - std::set_intersection( - std::begin(fields), - std::end(fields), - std::begin(quarks), - std::end(quarks), - std::back_inserter(to_save), - Compare); - - return save_impl(to_save); -} - -tr_variant Settings::save_single(tr_quark quark) const -{ - auto map = save_partial({ quark }); - return std::empty(map) ? tr_variant{} : std::move(std::begin(map)->second); -} - -tr_variant::Map Settings::save_impl(libtransmission::Settings::Fields const& fields) const -{ - auto map = tr_variant::Map{ std::size(fields) }; - - for (auto const& field : fields) - { - auto const type_index = std::type_index{ field.type }; - TR_ASSERT(save_.count(type_index) == 1U); - map.try_emplace(field.key, save_.at(type_index)(field.ptr)); - } - - return map; -} } // namespace libtransmission diff --git a/libtransmission/serializable.h b/libtransmission/serializable.h new file mode 100644 index 000000000..f05c62edc --- /dev/null +++ b/libtransmission/serializable.h @@ -0,0 +1,265 @@ +// This file Copyright © Mnemosyne LLC. +// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), +// or any future license endorsed by Mnemosyne LLC. +// License text can be found in the licenses/ folder. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "libtransmission/quark.h" +#include "libtransmission/variant.h" + +namespace libtransmission +{ + +/** + * Registry for `tr_variant` <-> `T` converters. + * Used by `Serializable` to serialize/deserialize fields in a class. + */ +class Serializers +{ +public: + template + using Deserialize = bool (*)(tr_variant const& src, T* ptgt); + + template + using Serialize = tr_variant (*)(T const& src); + + template + static tr_variant serialize(T const& src) + { + return converter_storage.serialize(src); + } + static tr_variant serialize(void const* const psrc, std::type_index const idx) + { + return converters.at(idx).second(psrc); + } + + template + static bool deserialize(tr_variant const& src, T* const ptgt) + { + return converter_storage.deserialize(src, ptgt); + } + static bool deserialize(tr_variant const& src, void* const vptgt, std::type_index const idx) + { + return converters.at(idx).first(src, vptgt); + } + + template + static void add_converter(Deserialize deserialize, Serialize serialize) + { + auto [key, val] = build_converter_entry(deserialize, serialize); + converters.try_emplace(std::move(key), std::move(val)); + } + +private: + using DeserializeFunc = bool (*)(tr_variant const& src, void* tgt); + using SerializeFunc = tr_variant (*)(void const* src); + using Converters = std::pair; + + template + struct ConverterStorage + { + Deserialize deserialize = nullptr; + Serialize serialize = nullptr; + }; + + template + static inline ConverterStorage converter_storage; + + template + static std::pair build_converter_entry(Deserialize deserialize, Serialize serialize) + { + converter_storage = ConverterStorage{ deserialize, serialize }; + return { std::type_index{ typeid(T*) }, Converters{ &deserialize_impl, &serialize_impl } }; + } + + template + static bool deserialize_impl(tr_variant const& src, void* const tgt) + { + return converter_storage.deserialize(src, static_cast(tgt)); + } + + template + static tr_variant serialize_impl(void const* const src) + { + return converter_storage.serialize(*static_cast(src)); + } + + using ConvertersMap = small::unordered_map; + static ConvertersMap converters; +}; + +/** + * Base class for classes that are convertble to a `tr_variant`. + * Settings classes use this to load and save state from `settings.json`. + * + * Subclasses must define a field named `fields` which is an iterable + * collection of `Serializable::Field`. This is typically declared as a + * `static const std::array`. + * + * If your subclass has a field with a bespoke type, it must use + * `Serializers::add_converter()` to register how to serialize that type. + */ +template +class Serializable +{ +public: + /** Update this object's state from a tr_variant. */ + void load(tr_variant const& src) + { + auto const* map = src.get_if(); + if (map == nullptr) + { + return; + } + + auto* const derived = static_cast(this); + for (auto const& field : derived->fields) + { + if (auto const iter = map->find(field.key); iter != std::end(*map)) + { + Serializers::deserialize(iter->second, field.get(derived), field.idx); + } + } + } + + /** Alias for load() */ + void deserialize(tr_variant const& src) + { + load(src); + } + + /** + * Save this object's fields to a tr_variant. + * @return a tr_variant::Map that holds the serialized form of this object. + */ + [[nodiscard]] auto save() const + { + auto const* const derived = static_cast(this); + auto map = tr_variant::Map{ std::size(derived->fields) }; + for (auto const& field : derived->fields) + { + map.try_emplace(field.key, Serializers::serialize(field.get(derived), field.idx)); + } + return map; + } + + /** Alias for save() */ + [[nodiscard]] tr_variant::Map serialize() const + { + return save(); + } + + /** + * Set a single property by key. + * Example: `settings_.set(TR_KEY_filename, "foo.txt");` + * @return true if the property was changed. + */ + template + bool set(Key const& key_in, T val_in) + { + auto const idx_in = std::type_index{ typeid(T*) }; + auto* const derived = static_cast(this); + for (auto const& field : derived->fields) + { + if (key_in == field.key && idx_in == field.idx) + { + if (T& val = *static_cast(field.get(derived)); val != val_in) + { + val = val_in; + return true; // changed + } + + return false; // found but unchanged + } + } + return false; // not found + } + + /** + * Get a single property by key. + * Example: `bool enabled = settings_.get(TR_KEY_incomplete_dir_enabled);` + * @return a std::optional<> which has the value if the key was found. + */ + template + [[nodiscard]] std::optional get(Key const& key_in) const + { + auto const idx_in = std::type_index{ typeid(T*) }; + auto const* const derived = static_cast(this); + for (auto const& field : derived->fields) + { + if (key_in == field.key && idx_in == field.idx) + { + return *static_cast(field.get(derived)); + } + } + return {}; + } + +protected: + struct Field + { + template + Field(Key key_in, T Derived::* ptr) + : key{ std::move(key_in) } + , idx{ typeid(T*) } + , getter_{ &Field::template get_impl } + , const_getter_{ &Field::template get_const_impl } + { + static_assert(sizeof(MemberStorage) >= sizeof(T Derived::*)); + static_assert(alignof(MemberStorage) >= alignof(T Derived::*)); + new (&storage_) T Derived::*(ptr); + } + + void* get(Derived* self) const noexcept + { + return getter_(self, &storage_); + } + + void const* get(Derived const* self) const noexcept + { + return const_getter_(self, &storage_); + } + + Key key; + std::type_index idx; + + private: + using Getter = void* (*)(Derived*, void const*); + using ConstGetter = void const* (*)(Derived const*, void const*); + using MemberStorage = std::aligned_storage_t; + + template + static void* get_impl(Derived* self, void const* opaque) noexcept + { + auto const member = *static_cast(opaque); + return &(self->*member); + } + + template + static void const* get_const_impl(Derived const* self, void const* opaque) noexcept + { + auto const member = *static_cast(opaque); + return &(self->*member); + } + + Getter getter_; + ConstGetter const_getter_; + MemberStorage storage_; + }; + +private: + friend Derived; + + Serializable() = default; +}; +} // namespace libtransmission diff --git a/libtransmission/session-alt-speeds.h b/libtransmission/session-alt-speeds.h index 8cf95d77d..d815e8ebc 100644 --- a/libtransmission/session-alt-speeds.h +++ b/libtransmission/session-alt-speeds.h @@ -9,6 +9,7 @@ #pragma once +#include #include #include // size_t #include // for time_t @@ -17,7 +18,7 @@ #include "libtransmission/transmission.h" // for TR_SCHED_ALL #include "libtransmission/quark.h" -#include "libtransmission/settings.h" +#include "libtransmission/serializable.h" #include "libtransmission/values.h" struct tr_variant; @@ -28,7 +29,7 @@ class tr_session_alt_speeds using Speed = libtransmission::Values::Speed; public: - class Settings final : public libtransmission::Settings + class Settings final : public libtransmission::Serializable { public: Settings() = default; @@ -49,18 +50,17 @@ public: size_t use_on_these_weekdays = TR_SCHED_ALL; private: - [[nodiscard]] Fields fields() override - { - return { - { TR_KEY_alt_speed_enabled, &is_active }, - { TR_KEY_alt_speed_up, &speed_up_kbyps }, - { TR_KEY_alt_speed_down, &speed_down_kbyps }, - { TR_KEY_alt_speed_time_enabled, &scheduler_enabled }, - { TR_KEY_alt_speed_time_day, &use_on_these_weekdays }, - { TR_KEY_alt_speed_time_begin, &minute_begin }, - { TR_KEY_alt_speed_time_end, &minute_end }, - }; - } + friend libtransmission::Serializable; + + static inline auto const fields = std::array{ { + { TR_KEY_alt_speed_enabled, &Settings::is_active }, + { TR_KEY_alt_speed_up, &Settings::speed_up_kbyps }, + { TR_KEY_alt_speed_down, &Settings::speed_down_kbyps }, + { TR_KEY_alt_speed_time_enabled, &Settings::scheduler_enabled }, + { TR_KEY_alt_speed_time_day, &Settings::use_on_these_weekdays }, + { TR_KEY_alt_speed_time_begin, &Settings::minute_begin }, + { TR_KEY_alt_speed_time_end, &Settings::minute_end }, + } }; }; enum class ChangeReason : uint8_t diff --git a/libtransmission/session.h b/libtransmission/session.h index d9a19b221..793476d84 100644 --- a/libtransmission/session.h +++ b/libtransmission/session.h @@ -56,7 +56,7 @@ #include "libtransmission/session-alt-speeds.h" #include "libtransmission/session-id.h" #include "libtransmission/session-thread.h" -#include "libtransmission/settings.h" +#include "libtransmission/serializable.h" #include "libtransmission/stats.h" #include "libtransmission/timer.h" #include "libtransmission/torrent-queue.h" @@ -382,7 +382,7 @@ private: }; public: - struct Settings final : public libtransmission::Settings + struct Settings final : public libtransmission::Serializable { public: Settings() = default; @@ -460,72 +460,71 @@ public: tr_verify_added_mode torrent_added_verify_mode = TR_VERIFY_ADDED_FAST; private: - [[nodiscard]] Fields fields() override - { - return { - { TR_KEY_announce_ip, &announce_ip }, - { TR_KEY_announce_ip_enabled, &announce_ip_enabled }, - { TR_KEY_bind_address_ipv4, &bind_address_ipv4 }, - { TR_KEY_bind_address_ipv6, &bind_address_ipv6 }, - { TR_KEY_blocklist_enabled, &blocklist_enabled }, - { TR_KEY_blocklist_url, &blocklist_url }, - { TR_KEY_cache_size_mb, &cache_size_mbytes }, - { TR_KEY_default_trackers, &default_trackers_str }, - { TR_KEY_dht_enabled, &dht_enabled }, - { TR_KEY_download_dir, &download_dir }, - { TR_KEY_download_queue_enabled, &download_queue_enabled }, - { TR_KEY_download_queue_size, &download_queue_size }, - { TR_KEY_encryption, &encryption_mode }, - { TR_KEY_idle_seeding_limit, &idle_seeding_limit_minutes }, - { TR_KEY_idle_seeding_limit_enabled, &idle_seeding_limit_enabled }, - { TR_KEY_incomplete_dir, &incomplete_dir }, - { TR_KEY_incomplete_dir_enabled, &incomplete_dir_enabled }, - { TR_KEY_lpd_enabled, &lpd_enabled }, - { TR_KEY_message_level, &log_level }, - { TR_KEY_peer_congestion_algorithm, &peer_congestion_algorithm }, - { TR_KEY_peer_limit_global, &peer_limit_global }, - { TR_KEY_peer_limit_per_torrent, &peer_limit_per_torrent }, - { TR_KEY_peer_port, &peer_port }, - { TR_KEY_peer_port_random_high, &peer_port_random_high }, - { TR_KEY_peer_port_random_low, &peer_port_random_low }, - { TR_KEY_peer_port_random_on_start, &peer_port_random_on_start }, - { TR_KEY_peer_socket_tos, &peer_socket_tos }, - { TR_KEY_pex_enabled, &pex_enabled }, - { TR_KEY_port_forwarding_enabled, &port_forwarding_enabled }, - { TR_KEY_preallocation, &preallocation_mode }, - { TR_KEY_preferred_transports, &preferred_transports }, - { TR_KEY_proxy_url, &proxy_url }, - { TR_KEY_queue_stalled_enabled, &queue_stalled_enabled }, - { TR_KEY_queue_stalled_minutes, &queue_stalled_minutes }, - { TR_KEY_ratio_limit, &ratio_limit }, - { TR_KEY_ratio_limit_enabled, &ratio_limit_enabled }, - { TR_KEY_rename_partial_files, &is_incomplete_file_naming_enabled }, - { TR_KEY_reqq, &reqq }, - { TR_KEY_scrape_paused_torrents_enabled, &should_scrape_paused_torrents }, - { TR_KEY_script_torrent_added_enabled, &script_torrent_added_enabled }, - { TR_KEY_script_torrent_added_filename, &script_torrent_added_filename }, - { TR_KEY_script_torrent_done_enabled, &script_torrent_done_enabled }, - { TR_KEY_script_torrent_done_filename, &script_torrent_done_filename }, - { TR_KEY_script_torrent_done_seeding_enabled, &script_torrent_done_seeding_enabled }, - { TR_KEY_script_torrent_done_seeding_filename, &script_torrent_done_seeding_filename }, - { TR_KEY_seed_queue_enabled, &seed_queue_enabled }, - { TR_KEY_seed_queue_size, &seed_queue_size }, - { TR_KEY_sequential_download, &sequential_download }, - { TR_KEY_sleep_per_seconds_during_verify, &sleep_per_seconds_during_verify }, - { TR_KEY_speed_limit_down, &speed_limit_down }, - { TR_KEY_speed_limit_down_enabled, &speed_limit_down_enabled }, - { TR_KEY_speed_limit_up, &speed_limit_up }, - { TR_KEY_speed_limit_up_enabled, &speed_limit_up_enabled }, - { TR_KEY_start_added_torrents, &should_start_added_torrents }, - { TR_KEY_tcp_enabled, &tcp_enabled }, - { TR_KEY_torrent_added_verify_mode, &torrent_added_verify_mode }, - { TR_KEY_torrent_complete_verify_enabled, &torrent_complete_verify_enabled }, - { TR_KEY_trash_original_torrent_files, &should_delete_source_torrents }, - { TR_KEY_umask, &umask }, - { TR_KEY_upload_slots_per_torrent, &upload_slots_per_torrent }, - { TR_KEY_utp_enabled, &utp_enabled }, - }; - } + friend class libtransmission::Serializable; + + static inline auto const fields = std::array{ { + { TR_KEY_announce_ip, &Settings::announce_ip }, + { TR_KEY_announce_ip_enabled, &Settings::announce_ip_enabled }, + { TR_KEY_bind_address_ipv4, &Settings::bind_address_ipv4 }, + { TR_KEY_bind_address_ipv6, &Settings::bind_address_ipv6 }, + { TR_KEY_blocklist_enabled, &Settings::blocklist_enabled }, + { TR_KEY_blocklist_url, &Settings::blocklist_url }, + { TR_KEY_cache_size_mb, &Settings::cache_size_mbytes }, + { TR_KEY_default_trackers, &Settings::default_trackers_str }, + { TR_KEY_dht_enabled, &Settings::dht_enabled }, + { TR_KEY_download_dir, &Settings::download_dir }, + { TR_KEY_download_queue_enabled, &Settings::download_queue_enabled }, + { TR_KEY_download_queue_size, &Settings::download_queue_size }, + { TR_KEY_encryption, &Settings::encryption_mode }, + { TR_KEY_idle_seeding_limit, &Settings::idle_seeding_limit_minutes }, + { TR_KEY_idle_seeding_limit_enabled, &Settings::idle_seeding_limit_enabled }, + { TR_KEY_incomplete_dir, &Settings::incomplete_dir }, + { TR_KEY_incomplete_dir_enabled, &Settings::incomplete_dir_enabled }, + { TR_KEY_lpd_enabled, &Settings::lpd_enabled }, + { TR_KEY_message_level, &Settings::log_level }, + { TR_KEY_peer_congestion_algorithm, &Settings::peer_congestion_algorithm }, + { TR_KEY_peer_limit_global, &Settings::peer_limit_global }, + { TR_KEY_peer_limit_per_torrent, &Settings::peer_limit_per_torrent }, + { TR_KEY_peer_port, &Settings::peer_port }, + { TR_KEY_peer_port_random_high, &Settings::peer_port_random_high }, + { TR_KEY_peer_port_random_low, &Settings::peer_port_random_low }, + { TR_KEY_peer_port_random_on_start, &Settings::peer_port_random_on_start }, + { TR_KEY_peer_socket_tos, &Settings::peer_socket_tos }, + { TR_KEY_pex_enabled, &Settings::pex_enabled }, + { TR_KEY_port_forwarding_enabled, &Settings::port_forwarding_enabled }, + { TR_KEY_preallocation, &Settings::preallocation_mode }, + { TR_KEY_preferred_transports, &Settings::preferred_transports }, + { TR_KEY_proxy_url, &Settings::proxy_url }, + { TR_KEY_queue_stalled_enabled, &Settings::queue_stalled_enabled }, + { TR_KEY_queue_stalled_minutes, &Settings::queue_stalled_minutes }, + { TR_KEY_ratio_limit, &Settings::ratio_limit }, + { TR_KEY_ratio_limit_enabled, &Settings::ratio_limit_enabled }, + { TR_KEY_rename_partial_files, &Settings::is_incomplete_file_naming_enabled }, + { TR_KEY_reqq, &Settings::reqq }, + { TR_KEY_scrape_paused_torrents_enabled, &Settings::should_scrape_paused_torrents }, + { TR_KEY_script_torrent_added_enabled, &Settings::script_torrent_added_enabled }, + { TR_KEY_script_torrent_added_filename, &Settings::script_torrent_added_filename }, + { TR_KEY_script_torrent_done_enabled, &Settings::script_torrent_done_enabled }, + { TR_KEY_script_torrent_done_filename, &Settings::script_torrent_done_filename }, + { TR_KEY_script_torrent_done_seeding_enabled, &Settings::script_torrent_done_seeding_enabled }, + { TR_KEY_script_torrent_done_seeding_filename, &Settings::script_torrent_done_seeding_filename }, + { TR_KEY_seed_queue_enabled, &Settings::seed_queue_enabled }, + { TR_KEY_seed_queue_size, &Settings::seed_queue_size }, + { TR_KEY_sequential_download, &Settings::sequential_download }, + { TR_KEY_sleep_per_seconds_during_verify, &Settings::sleep_per_seconds_during_verify }, + { TR_KEY_speed_limit_down, &Settings::speed_limit_down }, + { TR_KEY_speed_limit_down_enabled, &Settings::speed_limit_down_enabled }, + { TR_KEY_speed_limit_up, &Settings::speed_limit_up }, + { TR_KEY_speed_limit_up_enabled, &Settings::speed_limit_up_enabled }, + { TR_KEY_start_added_torrents, &Settings::should_start_added_torrents }, + { TR_KEY_tcp_enabled, &Settings::tcp_enabled }, + { TR_KEY_torrent_added_verify_mode, &Settings::torrent_added_verify_mode }, + { TR_KEY_torrent_complete_verify_enabled, &Settings::torrent_complete_verify_enabled }, + { TR_KEY_trash_original_torrent_files, &Settings::should_delete_source_torrents }, + { TR_KEY_umask, &Settings::umask }, + { TR_KEY_upload_slots_per_torrent, &Settings::upload_slots_per_torrent }, + { TR_KEY_utp_enabled, &Settings::utp_enabled }, + } }; }; explicit tr_session(std::string_view config_dir, tr_variant const& settings_dict); @@ -1009,14 +1008,14 @@ public: [[nodiscard]] auto save_preferred_transports() const { - auto var = settings().save_single(TR_KEY_preferred_transports); + auto var = libtransmission::Serializers::serialize(settings_.preferred_transports); TR_ASSERT(var.has_value()); return var; } bool load_preferred_transports(tr_variant const& var) noexcept { - return settings_.load_single(TR_KEY_preferred_transports, var); + return libtransmission::Serializers::deserialize(var, &settings_.preferred_transports); } [[nodiscard]] constexpr auto isIdleLimited() const noexcept diff --git a/libtransmission/settings.h b/libtransmission/settings.h deleted file mode 100644 index b68f63ea0..000000000 --- a/libtransmission/settings.h +++ /dev/null @@ -1,88 +0,0 @@ -// This file Copyright © Mnemosyne LLC. -// It may be used under GPLv2 (SPDX: GPL-2.0-only), GPLv3 (SPDX: GPL-3.0-only), -// or any future license endorsed by Mnemosyne LLC. -// License text can be found in the licenses/ folder. - -#pragma once - -#include -#include -#include -#include -#include - -#include "libtransmission/quark.h" -#include "libtransmission/variant.h" - -namespace libtransmission -{ - -class Settings -{ -public: - virtual ~Settings() = default; - Settings(Settings const& settings) = default; - Settings& operator=(Settings const& other) = default; - Settings(Settings&& settings) noexcept = default; - Settings& operator=(Settings&& other) noexcept = default; - - void load(tr_variant const& src); - bool load_single(tr_quark key, tr_variant const& src); - - [[nodiscard]] tr_variant::Map save() const - { - return save_impl(const_cast(this)->fields()); - } - [[nodiscard]] tr_variant::Map save_partial(std::vector quarks) const; - [[nodiscard]] tr_variant save_single(tr_quark quark) const; - -protected: - Settings(); - - // convert from tr_variant to T - template - using Load = bool (*)(tr_variant const& src, T* tgt); - - // convert from T to tr_variant - template - using Save = tr_variant (*)(T const& src); - - template - void add_type_handler(Load load_handler, Save save_handler) - { - auto const key = std::type_index(typeid(T*)); - - // wrap load_handler + save_handler with void* wrappers so that - // they can be stored in the save_ and load_ maps - load_.insert_or_assign( - key, - [load_handler](tr_variant const& src, void* tgt) { return load_handler(src, static_cast(tgt)); }); - save_.insert_or_assign(key, [save_handler](void const* src) { return save_handler(*static_cast(src)); }); - } - - struct Field - { - template - Field(tr_quark key_in, T* ptr_in) - : key{ key_in } - , type{ typeid(T*) } - , ptr{ ptr_in } - { - } - - tr_quark key; - std::type_info const& type; - void* ptr; - }; - - using Fields = std::vector; - - [[nodiscard]] virtual Fields fields() = 0; - -private: - [[nodiscard]] tr_variant::Map save_impl(Fields const& fields) const; - - std::unordered_map> save_; - std::unordered_map> load_; -}; -} // namespace libtransmission