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 <lagoho7@gmail.com>

* Update libtransmission/serializable.h

Co-authored-by: Yat Ho <lagoho7@gmail.com>

* Update libtransmission/serializable.h

Co-authored-by: Yat Ho <lagoho7@gmail.com>

* fixup! Update libtransmission/serializable.h

---------

Co-authored-by: Yat Ho <lagoho7@gmail.com>
This commit is contained in:
Charles Kerr
2025-12-09 22:09:49 -06:00
committed by GitHub
parent e671c0346c
commit bf48eadaeb
8 changed files with 424 additions and 338 deletions

View File

@@ -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 = "<group>"; };
ED5E0F0D2CD316450071433B /* zh-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-CN"; path = "zh-CN.lproj/Localizable.strings"; sourceTree = "<group>"; };
ED5E0F0E2CD3164D0071433B /* zh-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-TW"; path = "zh-TW.lproj/Localizable.strings"; sourceTree = "<group>"; };
ED67FB402B70FCE400D8A037 /* settings.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = settings.cc; sourceTree = "<group>"; };
ED67FB412B70FCE400D8A037 /* settings.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = settings.h; sourceTree = "<group>"; };
ED67FB402B70FCE400D8A037 /* serializable.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = serializable.cc; sourceTree = "<group>"; };
ED67FB412B70FCE400D8A037 /* serializable.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = serializable.h; sourceTree = "<group>"; };
ED6F16AF2EB8F1EB007CD864 /* FileCheckCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileCheckCellView.h; sourceTree = "<group>"; };
ED6F16B02EB8F1EB007CD864 /* FileCheckCellView.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FileCheckCellView.mm; sourceTree = "<group>"; };
ED6F16B12EB8F1EB007CD864 /* FileNameCellView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FileNameCellView.h; sourceTree = "<group>"; };
@@ -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 */,

View File

@@ -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

View File

@@ -9,6 +9,7 @@
#error only libtransmission should #include this header.
#endif
#include <array>
#include <cstddef> // size_t
#include <memory>
#include <string>
@@ -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<Settings>
{
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<Settings>;
static inline auto const fields = std::array<Field, 14U>{ {
{ 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);

View File

@@ -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<std::pair<std::string_view, T>, 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<bool>())
{
@@ -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<double>())
{
@@ -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<tr_encryption_mode, 3U>{ {
{ "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<int64_t>(val);
}
@@ -132,7 +132,7 @@ auto constexpr LogKeys = Lookup<tr_log_level, 7U>{ {
{ "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<int64_t>(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<std::string_view>())
{
@@ -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<int64_t>())
{
@@ -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<int64_t>())
{
@@ -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<tr_open_files::Preallocation, 5U>{ {
{ "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<int64_t>(val);
}
@@ -288,7 +288,7 @@ auto constexpr PreferredTransportKeys = Lookup<tr_preferred_transport, TR_NUM_PR
{ "tcp", TR_PREFER_TCP },
} };
bool load_preferred_transport(
bool to_preferred_transport(
tr_variant const& src,
small::max_size_vector<tr_preferred_transport, TR_NUM_PREFERRED_TRANSPORT>* tgt)
{
@@ -350,7 +350,7 @@ bool load_preferred_transport(
return true;
}
tr_variant save_preferred_transport(small::max_size_vector<tr_preferred_transport, TR_NUM_PREFERRED_TRANSPORT> const& val)
tr_variant from_preferred_transport(small::max_size_vector<tr_preferred_transport, TR_NUM_PREFERRED_TRANSPORT> 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<tr_preferred_transpor
// ---
bool load_size_t(tr_variant const& src, size_t* tgt)
bool to_size_t(tr_variant const& src, size_t* tgt)
{
if (auto const val = src.value_if<int64_t>())
{
@@ -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<std::string_view>())
{
@@ -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<std::string>* tgt)
bool to_optional_string(tr_variant const& src, std::optional<std::string>* tgt)
{
if (src.holds_alternative<std::nullptr_t>())
{
@@ -430,14 +430,14 @@ bool load_nullable_string(tr_variant const& src, std::optional<std::string>* tgt
return false;
}
tr_variant save_nullable_string(std::optional<std::string> const& val)
tr_variant from_optional_string(std::optional<std::string> 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<std::string_view>())
{
@@ -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<tr_verify_added_mode, 2U>{ {
{ "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<tr_variant::Map>();
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<tr_quark> 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<Settings*>(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

View File

@@ -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 <new>
#include <optional>
#include <type_traits>
#include <typeindex>
#include <typeinfo>
#include <utility>
#include <small/map.hpp>
#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<typename T>
using Deserialize = bool (*)(tr_variant const& src, T* ptgt);
template<typename T>
using Serialize = tr_variant (*)(T const& src);
template<typename T>
static tr_variant serialize(T const& src)
{
return converter_storage<T>.serialize(src);
}
static tr_variant serialize(void const* const psrc, std::type_index const idx)
{
return converters.at(idx).second(psrc);
}
template<typename T>
static bool deserialize(tr_variant const& src, T* const ptgt)
{
return converter_storage<T>.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<typename T>
static void add_converter(Deserialize<T> deserialize, Serialize<T> serialize)
{
auto [key, val] = build_converter_entry<T>(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<DeserializeFunc, SerializeFunc>;
template<typename T>
struct ConverterStorage
{
Deserialize<T> deserialize = nullptr;
Serialize<T> serialize = nullptr;
};
template<typename T>
static inline ConverterStorage<T> converter_storage;
template<typename T>
static std::pair<std::type_index, Converters> build_converter_entry(Deserialize<T> deserialize, Serialize<T> serialize)
{
converter_storage<T> = ConverterStorage<T>{ deserialize, serialize };
return { std::type_index{ typeid(T*) }, Converters{ &deserialize_impl<T>, &serialize_impl<T> } };
}
template<typename T>
static bool deserialize_impl(tr_variant const& src, void* const tgt)
{
return converter_storage<T>.deserialize(src, static_cast<T*>(tgt));
}
template<typename T>
static tr_variant serialize_impl(void const* const src)
{
return converter_storage<T>.serialize(*static_cast<T const*>(src));
}
using ConvertersMap = small::unordered_map<std::type_index, Converters>;
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<Field, N>`.
*
* If your subclass has a field with a bespoke type, it must use
* `Serializers::add_converter()` to register how to serialize that type.
*/
template<typename Derived, typename Key = tr_quark>
class Serializable
{
public:
/** Update this object's state from a tr_variant. */
void load(tr_variant const& src)
{
auto const* map = src.get_if<tr_variant::Map>();
if (map == nullptr)
{
return;
}
auto* const derived = static_cast<Derived*>(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<Derived const*>(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<typename T>
bool set(Key const& key_in, T val_in)
{
auto const idx_in = std::type_index{ typeid(T*) };
auto* const derived = static_cast<Derived*>(this);
for (auto const& field : derived->fields)
{
if (key_in == field.key && idx_in == field.idx)
{
if (T& val = *static_cast<T*>(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<bool>(TR_KEY_incomplete_dir_enabled);`
* @return a std::optional<> which has the value if the key was found.
*/
template<typename T>
[[nodiscard]] std::optional<T> get(Key const& key_in) const
{
auto const idx_in = std::type_index{ typeid(T*) };
auto const* const derived = static_cast<Derived const*>(this);
for (auto const& field : derived->fields)
{
if (key_in == field.key && idx_in == field.idx)
{
return *static_cast<T const*>(field.get(derived));
}
}
return {};
}
protected:
struct Field
{
template<typename T>
Field(Key key_in, T Derived::* ptr)
: key{ std::move(key_in) }
, idx{ typeid(T*) }
, getter_{ &Field::template get_impl<T> }
, const_getter_{ &Field::template get_const_impl<T> }
{
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<sizeof(char Derived::*), alignof(char Derived::*)>;
template<typename T>
static void* get_impl(Derived* self, void const* opaque) noexcept
{
auto const member = *static_cast<T Derived::* const*>(opaque);
return &(self->*member);
}
template<typename T>
static void const* get_const_impl(Derived const* self, void const* opaque) noexcept
{
auto const member = *static_cast<T Derived::* const*>(opaque);
return &(self->*member);
}
Getter getter_;
ConstGetter const_getter_;
MemberStorage storage_;
};
private:
friend Derived;
Serializable() = default;
};
} // namespace libtransmission

View File

@@ -9,6 +9,7 @@
#pragma once
#include <array>
#include <bitset>
#include <cstddef> // size_t
#include <ctime> // 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<Settings>
{
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<Settings>;
static inline auto const fields = std::array<Field, 7U>{ {
{ 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

View File

@@ -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<Settings>
{
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<Settings>;
static inline auto const fields = std::array<Field, 61U>{ {
{ 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

View File

@@ -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 <functional>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>
#include <vector>
#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<Settings*>(this)->fields());
}
[[nodiscard]] tr_variant::Map save_partial(std::vector<tr_quark> quarks) const;
[[nodiscard]] tr_variant save_single(tr_quark quark) const;
protected:
Settings();
// convert from tr_variant to T
template<typename T>
using Load = bool (*)(tr_variant const& src, T* tgt);
// convert from T to tr_variant
template<typename T>
using Save = tr_variant (*)(T const& src);
template<typename T>
void add_type_handler(Load<T> load_handler, Save<T> 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<T*>(tgt)); });
save_.insert_or_assign(key, [save_handler](void const* src) { return save_handler(*static_cast<T const*>(src)); });
}
struct Field
{
template<typename T>
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<Field>;
[[nodiscard]] virtual Fields fields() = 0;
private:
[[nodiscard]] tr_variant::Map save_impl(Fields const& fields) const;
std::unordered_map<std::type_index, std::function<tr_variant(void const* src)>> save_;
std::unordered_map<std::type_index, std::function<bool(tr_variant const& src, void* tgt)>> load_;
};
} // namespace libtransmission