feat: add libtransmission::api_compat::convert() (#7917)

Add a module to convert between tr4 and tr5 RPC / settings / config file payloads.

This will be used as a compatibility layer between Transmission 5's naming scheme
and Transmission <= 4.

Co-authored-by: Yat Ho <lagoho7@gmail.com>
Co-authored-by: Dzmitry Neviadomski <nevack.d@gmail.com>
This commit is contained in:
Charles Kerr
2025-12-14 10:56:07 -06:00
committed by GitHub
parent 98fdf2dc0b
commit 9a792046f3
13 changed files with 1600 additions and 335 deletions

View File

@@ -480,6 +480,8 @@
EDBAAC8C29E486BC00D9495F /* ip-cache.h in Headers */ = {isa = PBXBuildFile; fileRef = EDBAAC8B29E486BC00D9495F /* ip-cache.h */; };
EDBAAC8E29E486C200D9495F /* ip-cache.cc in Sources */ = {isa = PBXBuildFile; fileRef = EDBAAC8D29E486C200D9495F /* ip-cache.cc */; };
EDBDFA9E25AFCCA60093D9C1 /* evutil_time.c in Sources */ = {isa = PBXBuildFile; fileRef = EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */; };
EDC37BCD2EE9C2AD001E2612 /* api-compat.cc in Sources */ = {isa = PBXBuildFile; fileRef = EDC37BCC2EE9C2AD001E2612 /* api-compat.cc */; };
EDC37BCE2EE9C2AD001E2612 /* api-compat.h in Headers */ = {isa = PBXBuildFile; fileRef = EDC37BCB2EE9C2AD001E2612 /* api-compat.h */; };
EDC749F92D98AE3000A12D0F /* PowerManager.mm in Sources */ = {isa = PBXBuildFile; fileRef = EDC749F82D98AE2900A12D0F /* PowerManager.mm */; };
F11545ACA7C4D7A464F703AB /* block-info.h in Headers */ = {isa = PBXBuildFile; fileRef = 6A044CBD8C049AFCBD4DB411 /* block-info.h */; settings = {ATTRIBUTES = (Project, ); }; };
F63480631E1D7274005B9E09 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F63480621E1D7274005B9E09 /* Images.xcassets */; };
@@ -1472,6 +1474,8 @@
EDBAAC8B29E486BC00D9495F /* ip-cache.h */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.h; fileEncoding = 4; path = "ip-cache.h"; sourceTree = "<group>"; };
EDBAAC8D29E486C200D9495F /* ip-cache.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = "ip-cache.cc"; sourceTree = "<group>"; };
EDBDFA9D25AFCCA60093D9C1 /* evutil_time.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = evutil_time.c; sourceTree = "<group>"; };
EDC37BCB2EE9C2AD001E2612 /* api-compat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "api-compat.h"; sourceTree = "<group>"; };
EDC37BCC2EE9C2AD001E2612 /* api-compat.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = "api-compat.cc"; sourceTree = "<group>"; };
EDC749F72D98ADE200A12D0F /* PowerManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PowerManager.h; sourceTree = "<group>"; };
EDC749F82D98AE2900A12D0F /* PowerManager.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = PowerManager.mm; sourceTree = "<group>"; };
F63480621E1D7274005B9E09 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Images/Images.xcassets; sourceTree = "<group>"; };
@@ -1919,6 +1923,8 @@
4D1838DC09DEC04A0047D688 /* libtransmission */ = {
isa = PBXGroup;
children = (
EDC37BCB2EE9C2AD001E2612 /* api-compat.h */,
EDC37BCC2EE9C2AD001E2612 /* api-compat.cc */,
66F977825E65AD498C028BB1 /* announce-list.cc */,
66F977825E65AD498C028BB3 /* announce-list.h */,
A23F299F132A447400E9A83B /* announcer-common.h */,
@@ -2561,6 +2567,7 @@
buildActionMask = 2147483647;
files = (
C1077A51183EB29600634C22 /* file.h in Headers */,
EDC37BCE2EE9C2AD001E2612 /* api-compat.h in Headers */,
BEFC1E290C07861A00B0BB3C /* version.h in Headers */,
BEFC1E2A0C07861A00B0BB3C /* utils.h in Headers */,
BE7AA337F6752914B0C416B0 /* utils-ev.h in Headers */,
@@ -3489,6 +3496,7 @@
C1FEE57A1C3223CC00D62832 /* watchdir.cc in Sources */,
A23547E211CD0B090046EAE6 /* cache.cc in Sources */,
C843FC8429C51B9400491854 /* utils.mm in Sources */,
EDC37BCD2EE9C2AD001E2612 /* api-compat.cc in Sources */,
A284214412DA663E00FBDDBB /* tr-udp.cc in Sources */,
C17740D5273A002C00E455D2 /* web-utils.cc in Sources */,
A2679294130E00A000CB7464 /* tr-utp.cc in Sources */,

View File

@@ -6,6 +6,7 @@ Users can set environmental variables to override Transmission's default behavio
* If `TR_CURL_SSL_NO_VERIFY` is set, Transmission will not validate SSL certificate for HTTPS connections when talking to trackers. See CURL's documentation ([CURLOPT_SSL_VERIFYHOST](https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYHOST.html) and [CURLOPT_SSL_VERIFYPEER](https://curl.se/libcurl/c/CURLOPT_SSL_VERIFYPEER.html)) for more details.
* If `TR_CURL_VERBOSE` is set, debugging information for libcurl will be enabled. More information about libcurl's debugging mode [is available here](https://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTVERBOSE).
* If `TR_DHT_VERBOSE` is set, Transmission will log all of the DHT's activities in excruciating detail to standard error.
* If `TR_SAVE_VERSION_FORMAT` is set to `4` or `5`, it will save settings.json, stats.json, etc. files to either Transmission 4 or Transmission 5 format.
## Standard Variables Used by Transmission
* If `TRANSMISSION_WEB_HOME` is _not_ set, non-Mac platforms will look for the [Web Interface](Web-Interface.md) files in `XDG_DATA_HOME` and in `XDG_DATA_DIRS` as described in [the XDG Base Directory Specification](https://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables). `XDG_DATA_HOME` has a default value of `$HOME/.local/share/`.

View File

@@ -28,6 +28,8 @@ target_sources(${TR_NAME}
announcer-udp.cc
announcer.cc
announcer.h
api-compat.cc
api-compat.h
bandwidth.cc
bandwidth.h
benc.h

View File

@@ -0,0 +1,800 @@
// 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.
#include <iostream>
#include <algorithm>
#include <array>
#include <cstddef>
#include <string_view>
#include <vector>
#include "libtransmission/api-compat.h"
#include "libtransmission/quark.h"
#include "libtransmission/rpcimpl.h"
#include "libtransmission/utils.h"
#include "libtransmission/variant.h"
namespace libtransmission::api_compat
{
namespace
{
struct ApiKey
{
// snake-case quark
tr_quark current;
// legacy mixed-case RPC quark (pre-05aef3e7)
tr_quark legacy;
};
auto constexpr RpcKeys = std::array<ApiKey, 212U>{ {
{ TR_KEY_active_torrent_count, TR_KEY_active_torrent_count_camel },
{ TR_KEY_activity_date, TR_KEY_activity_date_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_added_date, TR_KEY_added_date_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_alt_speed_down, TR_KEY_alt_speed_down_kebab },
{ TR_KEY_alt_speed_enabled, TR_KEY_alt_speed_enabled_kebab },
{ TR_KEY_alt_speed_time_begin, TR_KEY_alt_speed_time_begin_kebab },
{ TR_KEY_alt_speed_time_day, TR_KEY_alt_speed_time_day_kebab },
{ TR_KEY_alt_speed_time_enabled, TR_KEY_alt_speed_time_enabled_kebab },
{ TR_KEY_alt_speed_time_end, TR_KEY_alt_speed_time_end_kebab },
{ TR_KEY_alt_speed_up, TR_KEY_alt_speed_up_kebab },
{ TR_KEY_announce_state, TR_KEY_announce_state_camel },
{ TR_KEY_anti_brute_force_enabled, TR_KEY_anti_brute_force_enabled_kebab },
{ TR_KEY_anti_brute_force_threshold, TR_KEY_anti_brute_force_threshold_kebab },
{ TR_KEY_bandwidth_priority, TR_KEY_bandwidth_priority_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_blocklist_enabled, TR_KEY_blocklist_enabled_kebab },
{ TR_KEY_blocklist_size, TR_KEY_blocklist_size_kebab },
{ TR_KEY_blocklist_url, TR_KEY_blocklist_url_kebab },
{ TR_KEY_bytes_completed, TR_KEY_bytes_completed_camel },
{ TR_KEY_cache_size_mb, TR_KEY_cache_size_mb_kebab },
{ TR_KEY_client_is_choked, TR_KEY_client_is_choked_camel },
{ TR_KEY_client_is_interested, TR_KEY_client_is_interested_camel },
{ TR_KEY_client_name, TR_KEY_client_name_camel },
{ TR_KEY_config_dir, TR_KEY_config_dir_kebab },
{ TR_KEY_corrupt_ever, TR_KEY_corrupt_ever_camel },
{ TR_KEY_cumulative_stats, TR_KEY_cumulative_stats_kebab },
{ TR_KEY_current_stats, TR_KEY_current_stats_kebab },
{ TR_KEY_date_created, TR_KEY_date_created_camel },
{ TR_KEY_default_trackers, TR_KEY_default_trackers_kebab },
{ TR_KEY_delete_local_data, TR_KEY_delete_local_data_kebab },
{ TR_KEY_desired_available, TR_KEY_desired_available_camel },
{ TR_KEY_dht_enabled, TR_KEY_dht_enabled_kebab },
{ TR_KEY_done_date, TR_KEY_done_date_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_download_count, TR_KEY_download_count_camel },
{ TR_KEY_download_dir, TR_KEY_download_dir_kebab }, // crazy case 1: camel in torrent-get/set, kebab everywhere else
{ TR_KEY_download_dir_free_space, TR_KEY_download_dir_free_space_kebab },
{ TR_KEY_download_limit, TR_KEY_download_limit_camel },
{ TR_KEY_download_limited, TR_KEY_download_limited_camel },
{ TR_KEY_download_queue_enabled, TR_KEY_download_queue_enabled_kebab },
{ TR_KEY_download_queue_size, TR_KEY_download_queue_size_kebab },
{ TR_KEY_download_speed, TR_KEY_download_speed_camel },
{ TR_KEY_downloaded_bytes, TR_KEY_downloaded_bytes_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_downloaded_ever, TR_KEY_downloaded_ever_camel },
{ TR_KEY_edit_date, TR_KEY_edit_date_camel },
{ TR_KEY_error_string, TR_KEY_error_string_camel },
{ TR_KEY_eta_idle, TR_KEY_eta_idle_camel },
{ TR_KEY_file_count, TR_KEY_file_count_kebab },
{ TR_KEY_file_stats, TR_KEY_file_stats_camel },
{ TR_KEY_files_added, TR_KEY_files_added_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_files_unwanted, TR_KEY_files_unwanted_kebab },
{ TR_KEY_files_wanted, TR_KEY_files_wanted_kebab },
{ TR_KEY_flag_str, TR_KEY_flag_str_camel },
{ TR_KEY_from_cache, TR_KEY_from_cache_camel },
{ TR_KEY_from_dht, TR_KEY_from_dht_camel },
{ TR_KEY_from_incoming, TR_KEY_from_incoming_camel },
{ TR_KEY_from_lpd, TR_KEY_from_lpd_camel },
{ TR_KEY_from_ltep, TR_KEY_from_ltep_camel },
{ TR_KEY_from_pex, TR_KEY_from_pex_camel },
{ TR_KEY_from_tracker, TR_KEY_from_tracker_camel },
{ TR_KEY_has_announced, TR_KEY_has_announced_camel },
{ TR_KEY_has_scraped, TR_KEY_has_scraped_camel },
{ TR_KEY_hash_string, TR_KEY_hash_string_camel },
{ TR_KEY_have_unchecked, TR_KEY_have_unchecked_camel },
{ TR_KEY_have_valid, TR_KEY_have_valid_camel },
{ TR_KEY_honors_session_limits, TR_KEY_honors_session_limits_camel },
{ TR_KEY_idle_seeding_limit, TR_KEY_idle_seeding_limit_kebab },
{ TR_KEY_idle_seeding_limit_enabled, TR_KEY_idle_seeding_limit_enabled_kebab },
{ TR_KEY_incomplete_dir, TR_KEY_incomplete_dir_kebab },
{ TR_KEY_incomplete_dir_enabled, TR_KEY_incomplete_dir_enabled_kebab },
{ TR_KEY_is_backup, TR_KEY_is_backup_camel },
{ TR_KEY_is_downloading_from, TR_KEY_is_downloading_from_camel },
{ TR_KEY_is_encrypted, TR_KEY_is_encrypted_camel },
{ TR_KEY_is_finished, TR_KEY_is_finished_camel },
{ TR_KEY_is_incoming, TR_KEY_is_incoming_camel },
{ TR_KEY_is_private, TR_KEY_is_private_camel },
{ TR_KEY_is_stalled, TR_KEY_is_stalled_camel },
{ TR_KEY_is_uploading_to, TR_KEY_is_uploading_to_camel },
{ TR_KEY_is_utp, TR_KEY_is_utp_camel },
{ TR_KEY_last_announce_peer_count, TR_KEY_last_announce_peer_count_camel },
{ TR_KEY_last_announce_result, TR_KEY_last_announce_result_camel },
{ TR_KEY_last_announce_start_time, TR_KEY_last_announce_start_time_camel },
{ TR_KEY_last_announce_succeeded, TR_KEY_last_announce_succeeded_camel },
{ TR_KEY_last_announce_time, TR_KEY_last_announce_time_camel },
{ TR_KEY_last_announce_timed_out, TR_KEY_last_announce_timed_out_camel },
{ TR_KEY_last_scrape_result, TR_KEY_last_scrape_result_camel },
{ TR_KEY_last_scrape_start_time, TR_KEY_last_scrape_start_time_camel },
{ TR_KEY_last_scrape_succeeded, TR_KEY_last_scrape_succeeded_camel },
{ TR_KEY_last_scrape_time, TR_KEY_last_scrape_time_camel },
{ TR_KEY_last_scrape_timed_out, TR_KEY_last_scrape_timed_out_camel },
{ TR_KEY_leecher_count, TR_KEY_leecher_count_camel },
{ TR_KEY_left_until_done, TR_KEY_left_until_done_camel },
{ TR_KEY_lpd_enabled, TR_KEY_lpd_enabled_kebab },
{ TR_KEY_magnet_link, TR_KEY_magnet_link_camel },
{ TR_KEY_manual_announce_time, TR_KEY_manual_announce_time_camel },
{ TR_KEY_max_connected_peers, TR_KEY_max_connected_peers_camel },
{ TR_KEY_memory_bytes, TR_KEY_memory_bytes_kebab },
{ TR_KEY_memory_units, TR_KEY_memory_units_kebab },
{ TR_KEY_metadata_percent_complete, TR_KEY_metadata_percent_complete_camel },
{ TR_KEY_next_announce_time, TR_KEY_next_announce_time_camel },
{ TR_KEY_next_scrape_time, TR_KEY_next_scrape_time_camel },
{ TR_KEY_paused_torrent_count, TR_KEY_paused_torrent_count_camel },
{ TR_KEY_peer_is_choked, TR_KEY_peer_is_choked_camel },
{ TR_KEY_peer_is_interested, TR_KEY_peer_is_interested_camel },
{ TR_KEY_peer_limit, TR_KEY_peer_limit_kebab },
{ TR_KEY_peer_limit_global, TR_KEY_peer_limit_global_kebab },
{ TR_KEY_peer_limit_per_torrent, TR_KEY_peer_limit_per_torrent_kebab },
{ TR_KEY_peer_port, TR_KEY_peer_port_kebab },
{ TR_KEY_peer_port_random_on_start, TR_KEY_peer_port_random_on_start_kebab },
{ TR_KEY_peers_connected, TR_KEY_peers_connected_camel },
{ TR_KEY_peers_from, TR_KEY_peers_from_camel },
{ TR_KEY_peers_getting_from_us, TR_KEY_peers_getting_from_us_camel },
{ TR_KEY_peers_sending_to_us, TR_KEY_peers_sending_to_us_camel },
{ TR_KEY_percent_complete, TR_KEY_percent_complete_camel },
{ TR_KEY_percent_done, TR_KEY_percent_done_camel },
{ TR_KEY_pex_enabled, TR_KEY_pex_enabled_kebab },
{ TR_KEY_piece_count, TR_KEY_piece_count_camel },
{ TR_KEY_piece_size, TR_KEY_piece_size_camel },
{ TR_KEY_port_forwarding_enabled, TR_KEY_port_forwarding_enabled_kebab },
{ TR_KEY_port_is_open, TR_KEY_port_is_open_kebab },
{ TR_KEY_primary_mime_type, TR_KEY_primary_mime_type_kebab },
{ TR_KEY_priority_high, TR_KEY_priority_high_kebab },
{ TR_KEY_priority_low, TR_KEY_priority_low_kebab },
{ TR_KEY_priority_normal, TR_KEY_priority_normal_kebab },
{ TR_KEY_queue_position, TR_KEY_queue_position_camel },
{ TR_KEY_queue_stalled_enabled, TR_KEY_queue_stalled_enabled_kebab },
{ TR_KEY_queue_stalled_minutes, TR_KEY_queue_stalled_minutes_kebab },
{ TR_KEY_rate_download, TR_KEY_rate_download_camel },
{ TR_KEY_rate_to_client, TR_KEY_rate_to_client_camel },
{ TR_KEY_rate_to_peer, TR_KEY_rate_to_peer_camel },
{ TR_KEY_rate_upload, TR_KEY_rate_upload_camel },
{ TR_KEY_recently_active, TR_KEY_recently_active_kebab },
{ TR_KEY_recheck_progress, TR_KEY_recheck_progress_camel },
{ TR_KEY_rename_partial_files, TR_KEY_rename_partial_files_kebab },
{ TR_KEY_rpc_host_whitelist, TR_KEY_rpc_host_whitelist_kebab },
{ TR_KEY_rpc_host_whitelist_enabled, TR_KEY_rpc_host_whitelist_enabled_kebab },
{ TR_KEY_rpc_version, TR_KEY_rpc_version_kebab },
{ TR_KEY_rpc_version_minimum, TR_KEY_rpc_version_minimum_kebab },
{ TR_KEY_rpc_version_semver, TR_KEY_rpc_version_semver_kebab },
{ TR_KEY_scrape_state, TR_KEY_scrape_state_camel },
{ TR_KEY_script_torrent_added_enabled, TR_KEY_script_torrent_added_enabled_kebab },
{ TR_KEY_script_torrent_added_filename, TR_KEY_script_torrent_added_filename_kebab },
{ TR_KEY_script_torrent_done_enabled, TR_KEY_script_torrent_done_enabled_kebab },
{ TR_KEY_script_torrent_done_filename, TR_KEY_script_torrent_done_filename_kebab },
{ TR_KEY_script_torrent_done_seeding_enabled, TR_KEY_script_torrent_done_seeding_enabled_kebab },
{ TR_KEY_script_torrent_done_seeding_filename, TR_KEY_script_torrent_done_seeding_filename_kebab },
{ TR_KEY_seconds_active, TR_KEY_seconds_active_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_seconds_downloading, TR_KEY_seconds_downloading_camel },
{ TR_KEY_seconds_seeding, TR_KEY_seconds_seeding_camel },
{ TR_KEY_seed_idle_limit, TR_KEY_seed_idle_limit_camel },
{ TR_KEY_seed_idle_mode, TR_KEY_seed_idle_mode_camel },
{ TR_KEY_seed_queue_enabled, TR_KEY_seed_queue_enabled_kebab },
{ TR_KEY_seed_queue_size, TR_KEY_seed_queue_size_kebab },
{ TR_KEY_seed_ratio_limit, TR_KEY_seed_ratio_limit_camel },
{ TR_KEY_seed_ratio_limited, TR_KEY_seed_ratio_limited_camel },
{ TR_KEY_seed_ratio_mode, TR_KEY_seed_ratio_mode_camel },
{ TR_KEY_seeder_count, TR_KEY_seeder_count_camel },
{ TR_KEY_session_count, TR_KEY_session_count_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_session_id, TR_KEY_session_id_kebab },
{ TR_KEY_size_bytes, TR_KEY_size_bytes_kebab },
{ TR_KEY_size_units, TR_KEY_size_units_kebab },
{ TR_KEY_size_when_done, TR_KEY_size_when_done_camel },
{ TR_KEY_speed_bytes, TR_KEY_speed_bytes_kebab },
{ TR_KEY_speed_limit_down, TR_KEY_speed_limit_down_kebab },
{ TR_KEY_speed_limit_down_enabled, TR_KEY_speed_limit_down_enabled_kebab },
{ TR_KEY_speed_limit_up, TR_KEY_speed_limit_up_kebab },
{ TR_KEY_speed_limit_up_enabled, TR_KEY_speed_limit_up_enabled_kebab },
{ TR_KEY_speed_units, TR_KEY_speed_units_kebab },
{ TR_KEY_start_added_torrents, TR_KEY_start_added_torrents_kebab },
{ TR_KEY_start_date, TR_KEY_start_date_camel },
{ TR_KEY_tcp_enabled, TR_KEY_tcp_enabled_kebab },
{ TR_KEY_torrent_added, TR_KEY_torrent_added_kebab },
{ TR_KEY_torrent_count, TR_KEY_torrent_count_camel },
{ TR_KEY_torrent_duplicate, TR_KEY_torrent_duplicate_kebab },
{ TR_KEY_torrent_file, TR_KEY_torrent_file_camel },
{ TR_KEY_total_size, TR_KEY_total_size_camel },
{ TR_KEY_tracker_add, TR_KEY_tracker_add_camel },
{ TR_KEY_tracker_list, TR_KEY_tracker_list_camel },
{ TR_KEY_tracker_remove, TR_KEY_tracker_remove_camel },
{ TR_KEY_tracker_replace, TR_KEY_tracker_replace_camel },
{ TR_KEY_tracker_stats, TR_KEY_tracker_stats_camel },
{ TR_KEY_trash_original_torrent_files, TR_KEY_trash_original_torrent_files_kebab },
{ TR_KEY_upload_limit, TR_KEY_upload_limit_camel },
{ TR_KEY_upload_limited, TR_KEY_upload_limited_camel },
{ TR_KEY_upload_ratio, TR_KEY_upload_ratio_camel },
{ TR_KEY_upload_speed, TR_KEY_upload_speed_camel },
{ TR_KEY_uploaded_bytes, TR_KEY_uploaded_bytes_camel }, // TODO(ckerr) legacy duplicate
{ TR_KEY_uploaded_ever, TR_KEY_uploaded_ever_camel },
{ TR_KEY_utp_enabled, TR_KEY_utp_enabled_kebab },
{ TR_KEY_webseeds_sending_to_us, TR_KEY_webseeds_sending_to_us_camel },
{ TR_KEY_blocklist_update, TR_KEY_blocklist_update_kebab },
{ TR_KEY_free_space, TR_KEY_free_space_kebab },
{ TR_KEY_group_get, TR_KEY_group_get_kebab },
{ TR_KEY_group_set, TR_KEY_group_set_kebab },
{ TR_KEY_port_test, TR_KEY_port_test_kebab },
{ TR_KEY_queue_move_bottom, TR_KEY_queue_move_bottom_kebab },
{ TR_KEY_queue_move_down, TR_KEY_queue_move_down_kebab },
{ TR_KEY_queue_move_top, TR_KEY_queue_move_top_kebab },
{ TR_KEY_queue_move_up, TR_KEY_queue_move_up_kebab },
{ TR_KEY_session_close, TR_KEY_session_close_kebab },
{ TR_KEY_session_get, TR_KEY_session_get_kebab },
{ TR_KEY_session_set, TR_KEY_session_set_kebab },
{ TR_KEY_session_stats, TR_KEY_session_stats_kebab },
{ TR_KEY_torrent_add, TR_KEY_torrent_add_kebab },
{ TR_KEY_torrent_get, TR_KEY_torrent_get_kebab },
{ TR_KEY_torrent_reannounce, TR_KEY_torrent_reannounce_kebab },
{ TR_KEY_torrent_remove, TR_KEY_torrent_remove_kebab },
{ TR_KEY_torrent_rename_path, TR_KEY_torrent_rename_path_kebab },
{ TR_KEY_torrent_set, TR_KEY_torrent_set_kebab },
{ TR_KEY_torrent_set_location, TR_KEY_torrent_set_location_kebab },
{ TR_KEY_torrent_start, TR_KEY_torrent_start_kebab },
{ TR_KEY_torrent_start_now, TR_KEY_torrent_start_now_kebab },
{ TR_KEY_torrent_stop, TR_KEY_torrent_stop_kebab },
{ TR_KEY_torrent_verify, TR_KEY_torrent_verify_kebab },
} };
auto constexpr SessionKeys = std::array<ApiKey, 139U>{ {
{ TR_KEY_activity_date, TR_KEY_activity_date_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_added_date, TR_KEY_added_date_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_alt_speed_down, TR_KEY_alt_speed_down_kebab },
{ TR_KEY_alt_speed_enabled, TR_KEY_alt_speed_enabled_kebab },
{ TR_KEY_alt_speed_time_begin, TR_KEY_alt_speed_time_begin_kebab },
{ TR_KEY_alt_speed_time_day, TR_KEY_alt_speed_time_day_kebab },
{ TR_KEY_alt_speed_time_enabled, TR_KEY_alt_speed_time_enabled_kebab },
{ TR_KEY_alt_speed_time_end, TR_KEY_alt_speed_time_end_kebab },
{ TR_KEY_alt_speed_up, TR_KEY_alt_speed_up_kebab },
{ TR_KEY_announce_ip, TR_KEY_announce_ip_kebab },
{ TR_KEY_announce_ip_enabled, TR_KEY_announce_ip_enabled_kebab },
{ TR_KEY_anti_brute_force_enabled, TR_KEY_anti_brute_force_enabled_kebab },
{ TR_KEY_anti_brute_force_threshold, TR_KEY_anti_brute_force_threshold_kebab },
{ TR_KEY_bandwidth_priority, TR_KEY_bandwidth_priority_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_bind_address_ipv4, TR_KEY_bind_address_ipv4_kebab },
{ TR_KEY_bind_address_ipv6, TR_KEY_bind_address_ipv6_kebab },
{ TR_KEY_blocklist_date, TR_KEY_blocklist_date_kebab },
{ TR_KEY_blocklist_enabled, TR_KEY_blocklist_enabled_kebab },
{ TR_KEY_blocklist_updates_enabled, TR_KEY_blocklist_updates_enabled_kebab },
{ TR_KEY_blocklist_url, TR_KEY_blocklist_url_kebab },
{ TR_KEY_cache_size_mb, TR_KEY_cache_size_mb_kebab },
{ TR_KEY_compact_view, TR_KEY_compact_view_kebab },
{ TR_KEY_default_trackers, TR_KEY_default_trackers_kebab },
{ TR_KEY_details_window_height, TR_KEY_details_window_height_kebab },
{ TR_KEY_details_window_width, TR_KEY_details_window_width_kebab },
{ TR_KEY_dht_enabled, TR_KEY_dht_enabled_kebab },
{ TR_KEY_done_date, TR_KEY_done_date_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_download_dir, TR_KEY_download_dir_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_download_queue_enabled, TR_KEY_download_queue_enabled_kebab },
{ TR_KEY_download_queue_size, TR_KEY_download_queue_size_kebab },
{ TR_KEY_downloaded_bytes, TR_KEY_downloaded_bytes_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_downloading_time_seconds, TR_KEY_downloading_time_seconds_kebab },
{ TR_KEY_files_added, TR_KEY_files_added_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_filter_mode, TR_KEY_filter_mode_kebab },
{ TR_KEY_filter_text, TR_KEY_filter_text_kebab },
{ TR_KEY_filter_trackers, TR_KEY_filter_trackers_kebab },
{ TR_KEY_idle_limit, TR_KEY_idle_limit_kebab },
{ TR_KEY_idle_mode, TR_KEY_idle_mode_kebab },
{ TR_KEY_idle_seeding_limit, TR_KEY_idle_seeding_limit_kebab },
{ TR_KEY_idle_seeding_limit_enabled, TR_KEY_idle_seeding_limit_enabled_kebab },
{ TR_KEY_incomplete_dir, TR_KEY_incomplete_dir_kebab },
{ TR_KEY_incomplete_dir_enabled, TR_KEY_incomplete_dir_enabled_kebab },
{ TR_KEY_inhibit_desktop_hibernation, TR_KEY_inhibit_desktop_hibernation_kebab },
{ TR_KEY_lpd_enabled, TR_KEY_lpd_enabled_kebab },
{ TR_KEY_main_window_height, TR_KEY_main_window_height_kebab },
{ TR_KEY_main_window_is_maximized, TR_KEY_main_window_is_maximized_kebab },
{ TR_KEY_main_window_layout_order, TR_KEY_main_window_layout_order_kebab },
{ TR_KEY_main_window_width, TR_KEY_main_window_width_kebab },
{ TR_KEY_main_window_x, TR_KEY_main_window_x_kebab },
{ TR_KEY_main_window_y, TR_KEY_main_window_y_kebab },
{ TR_KEY_max_peers, TR_KEY_max_peers_kebab },
{ TR_KEY_message_level, TR_KEY_message_level_kebab },
{ TR_KEY_open_dialog_dir, TR_KEY_open_dialog_dir_kebab },
{ TR_KEY_peer_congestion_algorithm, TR_KEY_peer_congestion_algorithm_kebab },
{ TR_KEY_peer_limit_global, TR_KEY_peer_limit_global_kebab },
{ TR_KEY_peer_limit_per_torrent, TR_KEY_peer_limit_per_torrent_kebab },
{ TR_KEY_peer_port, TR_KEY_peer_port_kebab },
{ TR_KEY_peer_port_random_high, TR_KEY_peer_port_random_high_kebab },
{ TR_KEY_peer_port_random_low, TR_KEY_peer_port_random_low_kebab },
{ TR_KEY_peer_port_random_on_start, TR_KEY_peer_port_random_on_start_kebab },
{ TR_KEY_peer_socket_tos, TR_KEY_peer_socket_tos_kebab },
{ TR_KEY_peers2_6, TR_KEY_peers2_6_kebab },
{ TR_KEY_pex_enabled, TR_KEY_pex_enabled_kebab },
{ TR_KEY_port_forwarding_enabled, TR_KEY_port_forwarding_enabled_kebab },
{ TR_KEY_prompt_before_exit, TR_KEY_prompt_before_exit_kebab },
{ TR_KEY_queue_stalled_enabled, TR_KEY_queue_stalled_enabled_kebab },
{ TR_KEY_queue_stalled_minutes, TR_KEY_queue_stalled_minutes_kebab },
{ TR_KEY_ratio_limit, TR_KEY_ratio_limit_kebab },
{ TR_KEY_ratio_limit_enabled, TR_KEY_ratio_limit_enabled_kebab },
{ TR_KEY_ratio_mode, TR_KEY_ratio_mode_kebab },
{ TR_KEY_read_clipboard, TR_KEY_read_clipboard_kebab },
{ TR_KEY_remote_session_enabled, TR_KEY_remote_session_enabled_kebab },
{ TR_KEY_remote_session_host, TR_KEY_remote_session_host_kebab },
{ TR_KEY_remote_session_https, TR_KEY_remote_session_https_kebab },
{ TR_KEY_remote_session_password, TR_KEY_remote_session_password_kebab },
{ TR_KEY_remote_session_port, TR_KEY_remote_session_port_kebab },
{ TR_KEY_remote_session_requires_authentication, TR_KEY_remote_session_requres_authentication_kebab },
{ TR_KEY_remote_session_username, TR_KEY_remote_session_username_kebab },
{ TR_KEY_rename_partial_files, TR_KEY_rename_partial_files_kebab },
{ TR_KEY_rpc_authentication_required, TR_KEY_rpc_authentication_required_kebab },
{ TR_KEY_rpc_bind_address, TR_KEY_rpc_bind_address_kebab },
{ TR_KEY_rpc_enabled, TR_KEY_rpc_enabled_kebab },
{ TR_KEY_rpc_host_whitelist, TR_KEY_rpc_host_whitelist_kebab },
{ TR_KEY_rpc_host_whitelist_enabled, TR_KEY_rpc_host_whitelist_enabled_kebab },
{ TR_KEY_rpc_password, TR_KEY_rpc_password_kebab },
{ TR_KEY_rpc_port, TR_KEY_rpc_port_kebab },
{ TR_KEY_rpc_socket_mode, TR_KEY_rpc_socket_mode_kebab },
{ TR_KEY_rpc_url, TR_KEY_rpc_url_kebab },
{ TR_KEY_rpc_username, TR_KEY_rpc_username_kebab },
{ TR_KEY_rpc_whitelist, TR_KEY_rpc_whitelist_kebab },
{ TR_KEY_rpc_whitelist_enabled, TR_KEY_rpc_whitelist_enabled_kebab },
{ TR_KEY_scrape_paused_torrents_enabled, TR_KEY_scrape_paused_torrents_enabled_kebab },
{ TR_KEY_script_torrent_added_enabled, TR_KEY_script_torrent_added_enabled_kebab },
{ TR_KEY_script_torrent_added_filename, TR_KEY_script_torrent_added_filename_kebab },
{ TR_KEY_script_torrent_done_enabled, TR_KEY_script_torrent_done_enabled_kebab },
{ TR_KEY_script_torrent_done_filename, TR_KEY_script_torrent_done_filename_kebab },
{ TR_KEY_script_torrent_done_seeding_enabled, TR_KEY_script_torrent_done_seeding_enabled_kebab },
{ TR_KEY_script_torrent_done_seeding_filename, TR_KEY_script_torrent_done_seeding_filename_kebab },
{ TR_KEY_seconds_active, TR_KEY_seconds_active_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_seed_queue_enabled, TR_KEY_seed_queue_enabled_kebab },
{ TR_KEY_seed_queue_size, TR_KEY_seed_queue_size_kebab },
{ TR_KEY_seeding_time_seconds, TR_KEY_seeding_time_seconds_kebab },
{ TR_KEY_session_count, TR_KEY_session_count_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_show_backup_trackers, TR_KEY_show_backup_trackers_kebab },
{ TR_KEY_show_extra_peer_details, TR_KEY_show_extra_peer_details_kebab },
{ TR_KEY_show_filterbar, TR_KEY_show_filterbar_kebab },
{ TR_KEY_show_notification_area_icon, TR_KEY_show_notification_area_icon_kebab },
{ TR_KEY_show_options_window, TR_KEY_show_options_window_kebab },
{ TR_KEY_show_statusbar, TR_KEY_show_statusbar_kebab },
{ TR_KEY_show_toolbar, TR_KEY_show_toolbar_kebab },
{ TR_KEY_show_tracker_scrapes, TR_KEY_show_tracker_scrapes_kebab },
{ TR_KEY_sleep_per_seconds_during_verify, TR_KEY_sleep_per_seconds_during_verify_kebab },
{ TR_KEY_sort_mode, TR_KEY_sort_mode_kebab },
{ TR_KEY_sort_reversed, TR_KEY_sort_reversed_kebab },
{ TR_KEY_speed_Bps, TR_KEY_speed_Bps_kebab },
{ TR_KEY_speed_limit_down, TR_KEY_speed_limit_down_kebab },
{ TR_KEY_speed_limit_down_enabled, TR_KEY_speed_limit_down_enabled_kebab },
{ TR_KEY_speed_limit_up, TR_KEY_speed_limit_up_kebab },
{ TR_KEY_speed_limit_up_enabled, TR_KEY_speed_limit_up_enabled_kebab },
{ TR_KEY_start_added_torrents, TR_KEY_start_added_torrents_kebab },
{ TR_KEY_start_minimized, TR_KEY_start_minimized_kebab },
{ TR_KEY_statusbar_stats, TR_KEY_statusbar_stats_kebab },
{ TR_KEY_tcp_enabled, TR_KEY_tcp_enabled_kebab },
{ TR_KEY_time_checked, TR_KEY_time_checked_kebab },
{ TR_KEY_torrent_added_notification_enabled, TR_KEY_torrent_added_notification_enabled_kebab },
{ TR_KEY_torrent_added_verify_mode, TR_KEY_torrent_added_verify_mode_kebab },
{ TR_KEY_torrent_complete_notification_enabled, TR_KEY_torrent_complete_notification_enabled_kebab },
{ TR_KEY_torrent_complete_sound_command, TR_KEY_torrent_complete_sound_command_kebab },
{ TR_KEY_torrent_complete_sound_enabled, TR_KEY_torrent_complete_sound_enabled_kebab },
{ TR_KEY_trash_can_enabled, TR_KEY_trash_can_enabled_kebab },
{ TR_KEY_trash_original_torrent_files, TR_KEY_trash_original_torrent_files_kebab },
{ TR_KEY_upload_slots_per_torrent, TR_KEY_upload_slots_per_torrent_kebab },
{ TR_KEY_uploaded_bytes, TR_KEY_uploaded_bytes_kebab }, // TODO(ckerr) legacy duplicate
{ TR_KEY_use_global_speed_limit, TR_KEY_use_global_speed_limit_kebab },
{ TR_KEY_use_speed_limit, TR_KEY_use_speed_limit_kebab },
{ TR_KEY_utp_enabled, TR_KEY_utp_enabled_kebab },
{ TR_KEY_watch_dir, TR_KEY_watch_dir_kebab },
{ TR_KEY_watch_dir_enabled, TR_KEY_watch_dir_enabled_kebab },
{ TR_KEY_watch_dir_force_generic, TR_KEY_watch_dir_force_generic_kebab },
} };
auto constexpr MethodNotFoundLegacyErrmsg = std::string_view{ "no method name" };
[[nodiscard]] constexpr tr_quark convert_key(tr_quark const src, Style const style, bool const is_rpc)
{
if (style == Style::Tr5)
{
for (auto const [current, legacy] : RpcKeys)
{
if (src == current || src == legacy)
{
return current;
}
}
for (auto const [current, legacy] : SessionKeys)
{
if (src == current || src == legacy)
{
return current;
}
}
}
else if (is_rpc) // legacy RPC
{
for (auto const [current, legacy] : RpcKeys)
{
if (src == current || src == legacy)
{
return legacy;
}
}
}
else // legacy datafiles
{
for (auto const [current, legacy] : SessionKeys)
{
if (src == current || src == legacy)
{
return legacy;
}
}
}
return src;
}
/**
* Guess the error code from a legacy RPC response message.
*
* Use case: a modern Transmission client that parses jsonrpc needs to
* connect to a legacy Transmission RPC server.
*
* We're not going to always get this right: There are some edge cases
* where legacy Transmission's error messages are unhelpful.
*/
[[nodiscard]] JsonRpc::Error::Code guess_error_code(std::string_view const result_in)
{
using namespace JsonRpc;
auto const result = tr_strlower(result_in);
if (result == "success")
{
return Error::SUCCESS;
}
static auto constexpr Phrases = std::array<std::pair<std::string_view, Error::Code>, 14U>{ {
{ "absolute", Error::PATH_NOT_ABSOLUTE },
{ "couldn't fetch blocklist", Error::HTTP_ERROR },
{ "couldn't save", Error::SYSTEM_ERROR },
{ "couldn't test port", Error::HTTP_ERROR },
{ "file index out of range", Error::FILE_IDX_OOR },
{ "invalid ip protocol", Error::INVALID_PARAMS },
{ "invalid or corrupt torrent", Error::CORRUPT_TORRENT },
{ "invalid tracker list", Error::INVALID_TRACKER_LIST },
{ "labels cannot", Error::INVALID_PARAMS },
{ "no filename or metainfo specified", Error::INVALID_PARAMS },
{ "no location", Error::INVALID_PARAMS },
{ "torrent-rename-path requires 1 torrent", Error::INVALID_PARAMS },
{ "unrecognized info", Error::UNRECOGNIZED_INFO },
{ MethodNotFoundLegacyErrmsg, Error::METHOD_NOT_FOUND },
} };
for (auto const& [substr, code] : Phrases)
{
if (tr_strv_contains(result, substr))
{
return code;
}
}
return {};
}
struct CloneState
{
api_compat::Style style = {};
bool is_rpc = false;
bool convert_strings = false;
bool is_torrent = false;
bool is_free_space_response = false;
};
[[nodiscard]] tr_variant convert_impl(tr_variant const& self, CloneState& state)
{
struct Visitor
{
tr_variant operator()(std::monostate const& /*unused*/) const
{
return tr_variant{};
}
tr_variant operator()(std::nullptr_t const& /*unused*/) const
{
return tr_variant{ nullptr };
}
tr_variant operator()(bool const& val) const
{
return tr_variant{ val };
}
tr_variant operator()(int64_t const& val) const
{
return tr_variant{ val };
}
tr_variant operator()(double const& val) const
{
return tr_variant{ val };
}
tr_variant operator()(std::string_view const& sv) const
{
if (!state_.convert_strings)
{
return sv;
}
auto const lookup = tr_quark_lookup(sv);
if (!lookup)
{
return sv;
}
auto key = *lookup;
// crazy case 1: downloadDir in torrent-get, download-dir in session-get
if (state_.is_rpc &&
(key == TR_KEY_download_dir_camel || key == TR_KEY_download_dir_kebab || key == TR_KEY_download_dir))
{
if (state_.style == Style::Tr5)
{
key = TR_KEY_download_dir;
}
else
{
key = state_.is_torrent ? TR_KEY_download_dir_camel : TR_KEY_download_dir_kebab;
}
}
else
{
key = convert_key(key, state_.style, state_.is_rpc);
}
return tr_variant::unmanaged_string(key);
}
tr_variant operator()(tr_variant::Vector const& src)
{
auto tgt = tr_variant::Vector();
tgt.reserve(std::size(src));
for (auto const& val : src)
{
tgt.emplace_back(convert_impl(val, state_));
}
return tgt;
}
tr_variant operator()(tr_variant::Map const& src)
{
auto tgt = tr_variant::Map{ std::size(src) };
for (auto const& [key, val] : src)
{
auto const pop = state_.convert_strings;
auto new_key = convert_key(key, state_.style, state_.is_rpc);
auto const special =
(state_.is_rpc &&
(new_key == TR_KEY_method || new_key == TR_KEY_fields || new_key == TR_KEY_ids ||
new_key == TR_KEY_torrents));
// TODO(ckerr): replace `new_key == TR_KEY_TORRENTS` on previous line with logic to turn on convert
// if it's an array inside an array val whose key was `torrents`.
// This is for the edge case of table mode: `torrents : [ [ 'key1', 'key2' ], [ ... ] ]`
state_.convert_strings |= special;
// Crazy case: total_size in free-space, totalSize in torrent-get
if (state_.is_free_space_response && new_key == TR_KEY_total_size_camel)
{
new_key = TR_KEY_total_size;
}
tgt.insert_or_assign(new_key, convert_impl(val, state_));
state_.convert_strings = pop;
}
return tgt;
}
CloneState& state_;
};
return self.visit(Visitor{ state });
}
} // namespace
tr_variant convert(tr_variant const& src, Style const tgt_style)
{
// TODO: yes I know this method is ugly rn.
// I've just been trying to get the tests passing.
auto const* const src_top = src.get_if<tr_variant::Map>();
// if it's not a Map, just clone it
if (src_top == nullptr)
{
return src.clone();
}
auto const is_request = src_top->contains(TR_KEY_method);
auto const was_jsonrpc = src_top->contains(TR_KEY_jsonrpc);
auto const was_legacy = !was_jsonrpc;
auto const was_jsonrpc_response = was_jsonrpc && (src_top->contains(TR_KEY_result) || src_top->contains(TR_KEY_error));
auto const was_legacy_response = was_legacy && src_top->contains(TR_KEY_result);
auto const is_response = was_jsonrpc_response || was_legacy_response;
auto const is_rpc = is_request || is_response;
auto state = CloneState{};
state.style = tgt_style;
state.is_rpc = is_rpc;
auto const is_success = is_response &&
(was_jsonrpc_response ? src_top->contains(TR_KEY_result) :
src_top->value_if<std::string_view>(TR_KEY_result).value_or("") == "success");
if (auto const method = src_top->value_if<std::string_view>(TR_KEY_method))
{
auto const key = tr_quark_convert(tr_quark_new(*method));
state.is_torrent = key == TR_KEY_torrent_get || key == TR_KEY_torrent_set;
}
if (is_response)
{
if (auto const* const args = src_top->find_if<tr_variant::Map>(was_jsonrpc ? TR_KEY_result : TR_KEY_arguments))
{
state.is_free_space_response = args->contains(TR_KEY_path) &&
args->contains(was_jsonrpc ? TR_KEY_size_bytes : TR_KEY_size_bytes_kebab);
}
}
auto ret = convert_impl(src, state);
auto* const tgt_top = ret.get_if<tr_variant::Map>();
// jsonrpc <-> legacy rpc conversion
if (is_rpc)
{
auto const is_jsonrpc = tgt_style == Style::Tr5;
auto const is_legacy = tgt_style != Style::Tr5;
// - use `jsonrpc` in jsonrpc, but not in legacy
// - use `id` in jsonrpc; use `tag` in legacy
if (is_jsonrpc)
{
tgt_top->try_emplace(TR_KEY_jsonrpc, tr_variant::unmanaged_string(JsonRpc::Version));
tgt_top->replace_key(TR_KEY_tag, TR_KEY_id);
}
else
{
tgt_top->erase(TR_KEY_jsonrpc);
tgt_top->replace_key(TR_KEY_id, TR_KEY_tag);
}
if (is_response && is_legacy && is_success && was_jsonrpc)
{
// in legacy messages:
// - move `result` to `arguments`
// - add `result: "success"`
tgt_top->replace_key(TR_KEY_result, TR_KEY_arguments);
tgt_top->try_emplace(TR_KEY_result, tr_variant::unmanaged_string("success"));
}
if (is_response && is_legacy && !is_success)
{
// in legacy error responses:
// - copy `error.data.error_string` to `result`
// - remove `error` object
// - add an empty `arguments` object
if (auto* error_ptr = tgt_top->find_if<tr_variant::Map>(TR_KEY_error))
{
// move the `error` object before memory reallocations invalidate the pointer
auto error = std::move(*error_ptr);
tgt_top->erase(TR_KEY_error);
// crazy case: current and legacy METHOD_NOT_FOUND has different error messages
if (auto const code = error.value_if<int64_t>(TR_KEY_code); code && *code == JsonRpc::Error::METHOD_NOT_FOUND)
{
tgt_top->try_emplace(TR_KEY_result, tr_variant::unmanaged_string(MethodNotFoundLegacyErrmsg));
}
if (auto* data = error.find_if<tr_variant::Map>(TR_KEY_data))
{
if (auto const* errmsg = data->find_if<std::string_view>(TR_KEY_error_string_camel))
{
tgt_top->try_emplace(TR_KEY_result, *errmsg);
}
if (auto const result = data->find(TR_KEY_result); result != std::end(*data))
{
tgt_top->try_emplace(TR_KEY_arguments, std::move(result->second));
}
}
if (auto const* errmsg = error.find_if<std::string_view>(TR_KEY_message))
{
tgt_top->try_emplace(TR_KEY_result, *errmsg);
}
}
tgt_top->try_emplace(TR_KEY_arguments, tr_variant::make_map());
}
if (is_response && is_jsonrpc && is_success && was_legacy)
{
tgt_top->erase(TR_KEY_result);
tgt_top->replace_key(TR_KEY_arguments, TR_KEY_result);
}
if (is_response && is_jsonrpc && !is_success && was_legacy)
{
// in jsonrpc error message:
// - copy `result` to `error.data.error_string`
// - ensure `error` object exists and is well-formatted
// - remove `result`
auto const errstr = tgt_top->value_if<std::string_view>(TR_KEY_result).value_or("unknown error");
auto error = tr_variant::Map{ 3U };
auto data = tr_variant::Map{ 2U };
auto const code = guess_error_code(errstr);
auto const errmsg = JsonRpc::Error::to_string(code);
error.try_emplace(TR_KEY_code, code);
error.try_emplace(TR_KEY_message, errmsg);
// crazy case: current and legacy METHOD_NOT_FOUND has different error messages
if (errstr != errmsg && errstr != MethodNotFoundLegacyErrmsg)
{
data.try_emplace(TR_KEY_error_string, errstr);
}
tgt_top->erase(TR_KEY_result);
if (auto const args_it = tgt_top->find(TR_KEY_arguments); args_it != std::end(*tgt_top))
{
auto args = std::move(args_it->second);
tgt_top->erase(TR_KEY_arguments);
if (auto const* args_map = args.get_if<tr_variant::Map>(); args_map != nullptr && !std::empty(*args_map))
{
data.try_emplace(TR_KEY_result, std::move(args));
}
}
if (!std::empty(data))
{
error.try_emplace(TR_KEY_data, std::move(data));
}
tgt_top->try_emplace(TR_KEY_error, std::move(error));
}
if (is_request && is_jsonrpc)
{
tgt_top->replace_key(TR_KEY_arguments, TR_KEY_params);
}
if (is_request && is_legacy)
{
tgt_top->replace_key(TR_KEY_params, TR_KEY_arguments);
}
}
return ret;
}
[[nodiscard]] Style get_export_settings_style()
{
// TODO: change default to Tr5 in transmission 5.0.0-beta.1
static auto const style = tr_env_get_string("TR_SAVE_VERSION_FORMAT", "4") == "5" ? Style::Tr5 : Style::Tr4;
return style;
}
[[nodiscard]] tr_variant convert_outgoing_data(tr_variant const& src)
{
return convert(src, get_export_settings_style());
}
[[nodiscard]] tr_variant convert_incoming_data(tr_variant const& src)
{
return convert(src, Style::Tr5);
}
} // namespace libtransmission::api_compat
tr_quark tr_quark_convert(tr_quark const quark)
{
using namespace libtransmission::api_compat;
return convert_key(quark, Style::Tr5, false /*ignored for Style::Tr5*/);
}

View File

@@ -0,0 +1,36 @@
// 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 <cstdint> // uint8_t
#include "libtransmission/quark.h"
struct tr_variant;
namespace libtransmission::api_compat
{
enum class Style : uint8_t
{
Tr4, // bespoke RPC, mixed-case keys,
Tr5, // jsonrpc, all snake_case keys
};
[[nodiscard]] tr_variant convert(tr_variant const& src, Style tgt_style);
[[nodiscard]] Style get_export_settings_style();
[[nodiscard]] tr_variant convert_incoming_data(tr_variant const& src);
[[nodiscard]] tr_variant convert_outgoing_data(tr_variant const& src);
} // namespace libtransmission::api_compat
/**
* Get the replacement quark from old deprecated quarks.
*
* Note: Temporary shim just for the transition period to snake_case.
*/
[[nodiscard]] tr_quark tr_quark_convert(tr_quark quark);

View File

@@ -795,324 +795,3 @@ std::string_view tr_quark_get_string_view(tr_quark q)
{
return q < TR_N_KEYS ? MyStatic[q] : my_runtime[q - TR_N_KEYS];
}
tr_quark tr_quark_convert(tr_quark const q)
{
// clang-format off
switch (q)
{
case TR_KEY_active_torrent_count_camel: return TR_KEY_active_torrent_count;
case TR_KEY_activity_date_camel:
case TR_KEY_activity_date_kebab:
return TR_KEY_activity_date;
case TR_KEY_added_date_camel:
case TR_KEY_added_date_kebab:
return TR_KEY_added_date;
case TR_KEY_alt_speed_down_kebab: return TR_KEY_alt_speed_down;
case TR_KEY_alt_speed_enabled_kebab: return TR_KEY_alt_speed_enabled;
case TR_KEY_alt_speed_time_begin_kebab: return TR_KEY_alt_speed_time_begin;
case TR_KEY_alt_speed_time_day_kebab: return TR_KEY_alt_speed_time_day;
case TR_KEY_alt_speed_time_enabled_kebab: return TR_KEY_alt_speed_time_enabled;
case TR_KEY_alt_speed_time_end_kebab: return TR_KEY_alt_speed_time_end;
case TR_KEY_alt_speed_up_kebab: return TR_KEY_alt_speed_up;
case TR_KEY_announce_ip_kebab: return TR_KEY_announce_ip;
case TR_KEY_announce_ip_enabled_kebab: return TR_KEY_announce_ip_enabled;
case TR_KEY_announce_state_camel: return TR_KEY_announce_state;
case TR_KEY_anti_brute_force_enabled_kebab: return TR_KEY_anti_brute_force_enabled;
case TR_KEY_anti_brute_force_threshold_kebab: return TR_KEY_anti_brute_force_threshold;
case TR_KEY_bandwidth_priority_camel:
case TR_KEY_bandwidth_priority_kebab:
return TR_KEY_bandwidth_priority;
case TR_KEY_bind_address_ipv4_kebab: return TR_KEY_bind_address_ipv4;
case TR_KEY_bind_address_ipv6_kebab: return TR_KEY_bind_address_ipv6;
case TR_KEY_blocklist_date_kebab: return TR_KEY_blocklist_date;
case TR_KEY_blocklist_enabled_kebab: return TR_KEY_blocklist_enabled;
case TR_KEY_blocklist_size_kebab: return TR_KEY_blocklist_size;
case TR_KEY_blocklist_update_kebab: return TR_KEY_blocklist_update;
case TR_KEY_blocklist_updates_enabled_kebab: return TR_KEY_blocklist_updates_enabled;
case TR_KEY_blocklist_url_kebab: return TR_KEY_blocklist_url;
case TR_KEY_bytes_completed_camel: return TR_KEY_bytes_completed;
case TR_KEY_cache_size_mb_kebab: return TR_KEY_cache_size_mb;
case TR_KEY_client_is_choked_camel: return TR_KEY_client_is_choked;
case TR_KEY_client_is_interested_camel: return TR_KEY_client_is_interested;
case TR_KEY_client_name_camel: return TR_KEY_client_name;
case TR_KEY_compact_view_kebab: return TR_KEY_compact_view;
case TR_KEY_config_dir_kebab: return TR_KEY_config_dir;
case TR_KEY_corrupt_ever_camel: return TR_KEY_corrupt_ever;
case TR_KEY_cumulative_stats_kebab: return TR_KEY_cumulative_stats;
case TR_KEY_current_stats_kebab: return TR_KEY_current_stats;
case TR_KEY_date_created_camel: return TR_KEY_date_created;
case TR_KEY_default_trackers_kebab: return TR_KEY_default_trackers;
case TR_KEY_delete_local_data_kebab: return TR_KEY_delete_local_data;
case TR_KEY_desired_available_camel: return TR_KEY_desired_available;
case TR_KEY_details_window_height_kebab: return TR_KEY_details_window_height;
case TR_KEY_details_window_width_kebab: return TR_KEY_details_window_width;
case TR_KEY_dht_enabled_kebab: return TR_KEY_dht_enabled;
case TR_KEY_done_date_camel:
case TR_KEY_done_date_kebab:
return TR_KEY_done_date;
case TR_KEY_download_count_camel: return TR_KEY_download_count;
case TR_KEY_download_dir_camel:
case TR_KEY_download_dir_kebab:
return TR_KEY_download_dir;
case TR_KEY_download_dir_free_space_kebab: return TR_KEY_download_dir_free_space;
case TR_KEY_download_limit_camel: return TR_KEY_download_limit;
case TR_KEY_download_limited_camel: return TR_KEY_download_limited;
case TR_KEY_download_queue_enabled_kebab: return TR_KEY_download_queue_enabled;
case TR_KEY_download_queue_size_kebab: return TR_KEY_download_queue_size;
case TR_KEY_download_speed_camel: return TR_KEY_download_speed;
case TR_KEY_downloaded_bytes_camel:
case TR_KEY_downloaded_bytes_kebab:
return TR_KEY_downloaded_bytes;
case TR_KEY_downloaded_ever_camel: return TR_KEY_downloaded_ever;
case TR_KEY_downloading_time_seconds_kebab: return TR_KEY_downloading_time_seconds;
case TR_KEY_edit_date_camel: return TR_KEY_edit_date;
case TR_KEY_error_string_camel: return TR_KEY_error_string;
case TR_KEY_eta_idle_camel: return TR_KEY_eta_idle;
case TR_KEY_file_count_kebab: return TR_KEY_file_count;
case TR_KEY_file_stats_camel: return TR_KEY_file_stats;
case TR_KEY_files_added_camel:
case TR_KEY_files_added_kebab:
return TR_KEY_files_added;
case TR_KEY_files_unwanted_kebab: return TR_KEY_files_unwanted;
case TR_KEY_files_wanted_kebab: return TR_KEY_files_wanted;
case TR_KEY_filter_mode_kebab: return TR_KEY_filter_mode;
case TR_KEY_filter_text_kebab: return TR_KEY_filter_text;
case TR_KEY_filter_trackers_kebab: return TR_KEY_filter_trackers;
case TR_KEY_flag_str_camel: return TR_KEY_flag_str;
case TR_KEY_free_space_kebab: return TR_KEY_free_space;
case TR_KEY_from_cache_camel: return TR_KEY_from_cache;
case TR_KEY_from_dht_camel: return TR_KEY_from_dht;
case TR_KEY_from_incoming_camel: return TR_KEY_from_incoming;
case TR_KEY_from_lpd_camel: return TR_KEY_from_lpd;
case TR_KEY_from_ltep_camel: return TR_KEY_from_ltep;
case TR_KEY_from_pex_camel: return TR_KEY_from_pex;
case TR_KEY_from_tracker_camel: return TR_KEY_from_tracker;
case TR_KEY_group_get_kebab: return TR_KEY_group_get;
case TR_KEY_group_set_kebab: return TR_KEY_group_set;
case TR_KEY_hash_string_camel: return TR_KEY_hash_string;
case TR_KEY_has_announced_camel: return TR_KEY_has_announced;
case TR_KEY_has_scraped_camel: return TR_KEY_has_scraped;
case TR_KEY_have_unchecked_camel: return TR_KEY_have_unchecked;
case TR_KEY_have_valid_camel: return TR_KEY_have_valid;
case TR_KEY_honors_session_limits_camel: return TR_KEY_honors_session_limits;
case TR_KEY_idle_limit_kebab: return TR_KEY_idle_limit;
case TR_KEY_idle_mode_kebab: return TR_KEY_idle_mode;
case TR_KEY_idle_seeding_limit_kebab: return TR_KEY_idle_seeding_limit;
case TR_KEY_idle_seeding_limit_enabled_kebab: return TR_KEY_idle_seeding_limit_enabled;
case TR_KEY_incomplete_dir_kebab: return TR_KEY_incomplete_dir;
case TR_KEY_incomplete_dir_enabled_kebab: return TR_KEY_incomplete_dir_enabled;
case TR_KEY_inhibit_desktop_hibernation_kebab: return TR_KEY_inhibit_desktop_hibernation;
case TR_KEY_is_backup_camel: return TR_KEY_is_backup;
case TR_KEY_is_downloading_from_camel: return TR_KEY_is_downloading_from;
case TR_KEY_is_encrypted_camel: return TR_KEY_is_encrypted;
case TR_KEY_is_finished_camel: return TR_KEY_is_finished;
case TR_KEY_is_incoming_camel: return TR_KEY_is_incoming;
case TR_KEY_is_private_camel: return TR_KEY_is_private;
case TR_KEY_is_stalled_camel: return TR_KEY_is_stalled;
case TR_KEY_is_uploading_to_camel: return TR_KEY_is_uploading_to;
case TR_KEY_is_utp_camel: return TR_KEY_is_utp;
case TR_KEY_last_announce_peer_count_camel: return TR_KEY_last_announce_peer_count;
case TR_KEY_last_announce_result_camel: return TR_KEY_last_announce_result;
case TR_KEY_last_announce_start_time_camel: return TR_KEY_last_announce_start_time;
case TR_KEY_last_announce_succeeded_camel: return TR_KEY_last_announce_succeeded;
case TR_KEY_last_announce_time_camel: return TR_KEY_last_announce_time;
case TR_KEY_last_announce_timed_out_camel: return TR_KEY_last_announce_timed_out;
case TR_KEY_last_scrape_result_camel: return TR_KEY_last_scrape_result;
case TR_KEY_last_scrape_start_time_camel: return TR_KEY_last_scrape_start_time;
case TR_KEY_last_scrape_succeeded_camel: return TR_KEY_last_scrape_succeeded;
case TR_KEY_last_scrape_time_camel: return TR_KEY_last_scrape_time;
case TR_KEY_last_scrape_timed_out_camel: return TR_KEY_last_scrape_timed_out;
case TR_KEY_leecher_count_camel: return TR_KEY_leecher_count;
case TR_KEY_left_until_done_camel: return TR_KEY_left_until_done;
case TR_KEY_lpd_enabled_kebab: return TR_KEY_lpd_enabled;
case TR_KEY_magnet_link_camel: return TR_KEY_magnet_link;
case TR_KEY_main_window_height_kebab: return TR_KEY_main_window_height;
case TR_KEY_main_window_is_maximized_kebab: return TR_KEY_main_window_is_maximized;
case TR_KEY_main_window_layout_order_kebab: return TR_KEY_main_window_layout_order;
case TR_KEY_main_window_width_kebab: return TR_KEY_main_window_width;
case TR_KEY_main_window_x_kebab: return TR_KEY_main_window_x;
case TR_KEY_main_window_y_kebab: return TR_KEY_main_window_y;
case TR_KEY_manual_announce_time_camel: return TR_KEY_manual_announce_time;
case TR_KEY_max_connected_peers_camel: return TR_KEY_max_connected_peers;
case TR_KEY_max_peers_kebab: return TR_KEY_max_peers;
case TR_KEY_memory_bytes_kebab: return TR_KEY_memory_bytes;
case TR_KEY_memory_units_kebab: return TR_KEY_memory_units;
case TR_KEY_message_level_kebab: return TR_KEY_message_level;
case TR_KEY_metadata_percent_complete_camel: return TR_KEY_metadata_percent_complete;
case TR_KEY_next_announce_time_camel: return TR_KEY_next_announce_time;
case TR_KEY_next_scrape_time_camel: return TR_KEY_next_scrape_time;
case TR_KEY_open_dialog_dir_kebab: return TR_KEY_open_dialog_dir;
case TR_KEY_paused_torrent_count_camel: return TR_KEY_paused_torrent_count;
case TR_KEY_peer_congestion_algorithm_kebab: return TR_KEY_peer_congestion_algorithm;
case TR_KEY_peer_is_choked_camel: return TR_KEY_peer_is_choked;
case TR_KEY_peer_is_interested_camel: return TR_KEY_peer_is_interested;
case TR_KEY_peer_limit_kebab: return TR_KEY_peer_limit;
case TR_KEY_peer_limit_global_kebab: return TR_KEY_peer_limit_global;
case TR_KEY_peer_limit_per_torrent_kebab: return TR_KEY_peer_limit_per_torrent;
case TR_KEY_peer_port_kebab: return TR_KEY_peer_port;
case TR_KEY_peer_port_random_high_kebab: return TR_KEY_peer_port_random_high;
case TR_KEY_peer_port_random_low_kebab: return TR_KEY_peer_port_random_low;
case TR_KEY_peer_port_random_on_start_kebab: return TR_KEY_peer_port_random_on_start;
case TR_KEY_peer_socket_tos_kebab: return TR_KEY_peer_socket_tos;
case TR_KEY_peers2_6_kebab: return TR_KEY_peers2_6;
case TR_KEY_peers_connected_camel: return TR_KEY_peers_connected;
case TR_KEY_peers_from_camel: return TR_KEY_peers_from;
case TR_KEY_peers_getting_from_us_camel: return TR_KEY_peers_getting_from_us;
case TR_KEY_peers_sending_to_us_camel: return TR_KEY_peers_sending_to_us;
case TR_KEY_percent_complete_camel: return TR_KEY_percent_complete;
case TR_KEY_percent_done_camel: return TR_KEY_percent_done;
case TR_KEY_pex_enabled_kebab: return TR_KEY_pex_enabled;
case TR_KEY_piece_count_camel: return TR_KEY_piece_count;
case TR_KEY_piece_size_camel: return TR_KEY_piece_size;
case TR_KEY_port_forwarding_enabled_kebab: return TR_KEY_port_forwarding_enabled;
case TR_KEY_port_is_open_kebab: return TR_KEY_port_is_open;
case TR_KEY_port_test_kebab: return TR_KEY_port_test;
case TR_KEY_primary_mime_type_kebab: return TR_KEY_primary_mime_type;
case TR_KEY_priority_high_kebab: return TR_KEY_priority_high;
case TR_KEY_priority_low_kebab: return TR_KEY_priority_low;
case TR_KEY_priority_normal_kebab: return TR_KEY_priority_normal;
case TR_KEY_prompt_before_exit_kebab: return TR_KEY_prompt_before_exit;
case TR_KEY_queue_position_camel: return TR_KEY_queue_position;
case TR_KEY_queue_move_bottom_kebab: return TR_KEY_queue_move_bottom;
case TR_KEY_queue_move_down_kebab: return TR_KEY_queue_move_down;
case TR_KEY_queue_move_top_kebab: return TR_KEY_queue_move_top;
case TR_KEY_queue_move_up_kebab: return TR_KEY_queue_move_up;
case TR_KEY_queue_stalled_enabled_kebab: return TR_KEY_queue_stalled_enabled;
case TR_KEY_queue_stalled_minutes_kebab: return TR_KEY_queue_stalled_minutes;
case TR_KEY_rate_download_camel: return TR_KEY_rate_download;
case TR_KEY_rate_to_client_camel: return TR_KEY_rate_to_client;
case TR_KEY_rate_to_peer_camel: return TR_KEY_rate_to_peer;
case TR_KEY_rate_upload_camel: return TR_KEY_rate_upload;
case TR_KEY_ratio_limit_kebab: return TR_KEY_ratio_limit;
case TR_KEY_ratio_limit_enabled_kebab: return TR_KEY_ratio_limit_enabled;
case TR_KEY_ratio_mode_kebab: return TR_KEY_ratio_mode;
case TR_KEY_read_clipboard_kebab: return TR_KEY_read_clipboard;
case TR_KEY_recheck_progress_camel: return TR_KEY_recheck_progress;
case TR_KEY_remote_session_enabled_kebab: return TR_KEY_remote_session_enabled;
case TR_KEY_remote_session_host_kebab: return TR_KEY_remote_session_host;
case TR_KEY_remote_session_https_kebab: return TR_KEY_remote_session_https;
case TR_KEY_remote_session_password_kebab: return TR_KEY_remote_session_password;
case TR_KEY_remote_session_port_kebab: return TR_KEY_remote_session_port;
case TR_KEY_remote_session_requres_authentication_kebab: return TR_KEY_remote_session_requires_authentication;
case TR_KEY_remote_session_username_kebab: return TR_KEY_remote_session_username;
case TR_KEY_rename_partial_files_kebab: return TR_KEY_rename_partial_files;
case TR_KEY_rpc_authentication_required_kebab: return TR_KEY_rpc_authentication_required;
case TR_KEY_rpc_bind_address_kebab: return TR_KEY_rpc_bind_address;
case TR_KEY_rpc_enabled_kebab: return TR_KEY_rpc_enabled;
case TR_KEY_rpc_host_whitelist_kebab: return TR_KEY_rpc_host_whitelist;
case TR_KEY_rpc_host_whitelist_enabled_kebab: return TR_KEY_rpc_host_whitelist_enabled;
case TR_KEY_rpc_password_kebab: return TR_KEY_rpc_password;
case TR_KEY_rpc_port_kebab: return TR_KEY_rpc_port;
case TR_KEY_rpc_socket_mode_kebab: return TR_KEY_rpc_socket_mode;
case TR_KEY_rpc_url_kebab: return TR_KEY_rpc_url;
case TR_KEY_rpc_username_kebab: return TR_KEY_rpc_username;
case TR_KEY_rpc_version_kebab: return TR_KEY_rpc_version;
case TR_KEY_rpc_version_minimum_kebab: return TR_KEY_rpc_version_minimum;
case TR_KEY_rpc_version_semver_kebab: return TR_KEY_rpc_version_semver;
case TR_KEY_rpc_whitelist_kebab: return TR_KEY_rpc_whitelist;
case TR_KEY_rpc_whitelist_enabled_kebab: return TR_KEY_rpc_whitelist_enabled;
case TR_KEY_seconds_downloading_camel: return TR_KEY_seconds_downloading;
case TR_KEY_scrape_paused_torrents_enabled_kebab: return TR_KEY_scrape_paused_torrents_enabled;
case TR_KEY_scrape_state_camel: return TR_KEY_scrape_state;
case TR_KEY_script_torrent_added_enabled_kebab: return TR_KEY_script_torrent_added_enabled;
case TR_KEY_script_torrent_added_filename_kebab: return TR_KEY_script_torrent_added_filename;
case TR_KEY_script_torrent_done_enabled_kebab: return TR_KEY_script_torrent_done_enabled;
case TR_KEY_script_torrent_done_filename_kebab: return TR_KEY_script_torrent_done_filename;
case TR_KEY_script_torrent_done_seeding_enabled_kebab: return TR_KEY_script_torrent_done_seeding_enabled;
case TR_KEY_script_torrent_done_seeding_filename_kebab: return TR_KEY_script_torrent_done_seeding_filename;
case TR_KEY_seconds_active_camel:
case TR_KEY_seconds_active_kebab:
return TR_KEY_seconds_active;
case TR_KEY_seconds_seeding_camel: return TR_KEY_seconds_seeding;
case TR_KEY_seed_idle_limit_camel: return TR_KEY_seed_idle_limit;
case TR_KEY_seed_idle_mode_camel: return TR_KEY_seed_idle_mode;
case TR_KEY_seed_queue_enabled_kebab: return TR_KEY_seed_queue_enabled;
case TR_KEY_seed_queue_size_kebab: return TR_KEY_seed_queue_size;
case TR_KEY_seed_ratio_limit_camel: return TR_KEY_seed_ratio_limit;
case TR_KEY_seed_ratio_limited_camel: return TR_KEY_seed_ratio_limited;
case TR_KEY_seed_ratio_mode_camel: return TR_KEY_seed_ratio_mode;
case TR_KEY_seeding_time_seconds_kebab: return TR_KEY_seeding_time_seconds;
case TR_KEY_seeder_count_camel: return TR_KEY_seeder_count;
case TR_KEY_session_close_kebab: return TR_KEY_session_close;
case TR_KEY_session_count_camel:
case TR_KEY_session_count_kebab:
return TR_KEY_session_count;
case TR_KEY_session_get_kebab: return TR_KEY_session_get;
case TR_KEY_session_id_kebab: return TR_KEY_session_id;
case TR_KEY_session_set_kebab: return TR_KEY_session_set;
case TR_KEY_session_stats_kebab: return TR_KEY_session_stats;
case TR_KEY_show_backup_trackers_kebab: return TR_KEY_show_backup_trackers;
case TR_KEY_show_extra_peer_details_kebab: return TR_KEY_show_extra_peer_details;
case TR_KEY_show_filterbar_kebab: return TR_KEY_show_filterbar;
case TR_KEY_show_notification_area_icon_kebab: return TR_KEY_show_notification_area_icon;
case TR_KEY_show_options_window_kebab: return TR_KEY_show_options_window;
case TR_KEY_show_statusbar_kebab: return TR_KEY_show_statusbar;
case TR_KEY_show_toolbar_kebab: return TR_KEY_show_toolbar;
case TR_KEY_show_tracker_scrapes_kebab: return TR_KEY_show_tracker_scrapes;
case TR_KEY_size_bytes_kebab: return TR_KEY_size_bytes;
case TR_KEY_size_units_kebab: return TR_KEY_size_units;
case TR_KEY_size_when_done_camel: return TR_KEY_size_when_done;
case TR_KEY_sleep_per_seconds_during_verify_kebab: return TR_KEY_sleep_per_seconds_during_verify;
case TR_KEY_sort_mode_kebab: return TR_KEY_sort_mode;
case TR_KEY_sort_reversed_kebab: return TR_KEY_sort_reversed;
case TR_KEY_speed_Bps_kebab: return TR_KEY_speed_Bps;
case TR_KEY_speed_bytes_kebab: return TR_KEY_speed_bytes;
case TR_KEY_speed_limit_down_kebab: return TR_KEY_speed_limit_down;
case TR_KEY_speed_limit_down_enabled_kebab: return TR_KEY_speed_limit_down_enabled;
case TR_KEY_speed_limit_up_kebab: return TR_KEY_speed_limit_up;
case TR_KEY_speed_limit_up_enabled_kebab: return TR_KEY_speed_limit_up_enabled;
case TR_KEY_speed_units_kebab: return TR_KEY_speed_units;
case TR_KEY_start_added_torrents_kebab: return TR_KEY_start_added_torrents;
case TR_KEY_start_date_camel: return TR_KEY_start_date;
case TR_KEY_start_minimized_kebab: return TR_KEY_start_minimized;
case TR_KEY_statusbar_stats_kebab: return TR_KEY_statusbar_stats;
case TR_KEY_tcp_enabled_kebab: return TR_KEY_tcp_enabled;
case TR_KEY_torrent_add_kebab: return TR_KEY_torrent_add;
case TR_KEY_torrent_added_kebab: return TR_KEY_torrent_added;
case TR_KEY_torrent_added_notification_enabled_kebab: return TR_KEY_torrent_added_notification_enabled;
case TR_KEY_torrent_added_verify_mode_kebab: return TR_KEY_torrent_added_verify_mode;
case TR_KEY_torrent_complete_notification_enabled_kebab: return TR_KEY_torrent_complete_notification_enabled;
case TR_KEY_torrent_complete_sound_command_kebab: return TR_KEY_torrent_complete_sound_command;
case TR_KEY_torrent_complete_sound_enabled_kebab: return TR_KEY_torrent_complete_sound_enabled;
case TR_KEY_torrent_count_camel: return TR_KEY_torrent_count;
case TR_KEY_torrent_duplicate_kebab: return TR_KEY_torrent_duplicate;
case TR_KEY_torrent_file_camel: return TR_KEY_torrent_file;
case TR_KEY_torrent_get_kebab: return TR_KEY_torrent_get;
case TR_KEY_torrent_reannounce_kebab: return TR_KEY_torrent_reannounce;
case TR_KEY_torrent_remove_kebab: return TR_KEY_torrent_remove;
case TR_KEY_torrent_rename_path_kebab: return TR_KEY_torrent_rename_path;
case TR_KEY_torrent_set_kebab: return TR_KEY_torrent_set;
case TR_KEY_torrent_set_location_kebab: return TR_KEY_torrent_set_location;
case TR_KEY_torrent_start_kebab: return TR_KEY_torrent_start;
case TR_KEY_torrent_start_now_kebab: return TR_KEY_torrent_start_now;
case TR_KEY_torrent_stop_kebab: return TR_KEY_torrent_stop;
case TR_KEY_torrent_verify_kebab: return TR_KEY_torrent_verify;
case TR_KEY_total_size_camel: return TR_KEY_total_size;
case TR_KEY_tracker_add_camel: return TR_KEY_tracker_add;
case TR_KEY_tracker_list_camel: return TR_KEY_tracker_list;
case TR_KEY_tracker_remove_camel: return TR_KEY_tracker_remove;
case TR_KEY_tracker_replace_camel: return TR_KEY_tracker_replace;
case TR_KEY_tracker_stats_camel: return TR_KEY_tracker_stats;
case TR_KEY_trash_can_enabled_kebab: return TR_KEY_trash_can_enabled;
case TR_KEY_trash_original_torrent_files_kebab: return TR_KEY_trash_original_torrent_files;
case TR_KEY_upload_limit_camel: return TR_KEY_upload_limit;
case TR_KEY_upload_limited_camel: return TR_KEY_upload_limited;
case TR_KEY_upload_slots_per_torrent_kebab: return TR_KEY_upload_slots_per_torrent;
case TR_KEY_upload_ratio_camel: return TR_KEY_upload_ratio;
case TR_KEY_upload_speed_camel: return TR_KEY_upload_speed;
case TR_KEY_uploaded_bytes_camel:
case TR_KEY_uploaded_bytes_kebab:
return TR_KEY_uploaded_bytes;
case TR_KEY_uploaded_ever_camel: return TR_KEY_uploaded_ever;
case TR_KEY_use_global_speed_limit_kebab: return TR_KEY_use_global_speed_limit;
case TR_KEY_use_speed_limit_kebab: return TR_KEY_use_speed_limit;
case TR_KEY_utp_enabled_kebab: return TR_KEY_utp_enabled;
case TR_KEY_watch_dir_kebab: return TR_KEY_watch_dir;
case TR_KEY_watch_dir_enabled_kebab: return TR_KEY_watch_dir_enabled;
case TR_KEY_watch_dir_force_generic_kebab: return TR_KEY_watch_dir_force_generic;
case TR_KEY_webseeds_sending_to_us_camel: return TR_KEY_webseeds_sending_to_us;
default: return q;
}
// clang-format on
}

View File

@@ -752,10 +752,3 @@ enum // NOLINT(performance-enum-size)
* created.
*/
[[nodiscard]] tr_quark tr_quark_new(std::string_view str);
/**
* Get the replacement quark from old deprecated quarks.
*
* Note: Temporary shim just for the transition period to snake_case.
*/
[[nodiscard]] tr_quark tr_quark_convert(tr_quark quark);

View File

@@ -32,6 +32,7 @@
#include "libtransmission/log.h"
#include "libtransmission/net.h"
#include "libtransmission/peer-mgr.h"
#include "libtransmission/api-compat.h"
#include "libtransmission/quark.h"
#include "libtransmission/rpcimpl.h"
#include "libtransmission/session.h"
@@ -54,9 +55,7 @@ namespace JsonRpc
// https://www.jsonrpc.org/specification#error_object
namespace Error
{
namespace
{
[[nodiscard]] constexpr std::string_view get_message(Code code)
[[nodiscard]] std::string_view to_string(Code const code)
{
switch (code)
{
@@ -95,6 +94,8 @@ namespace
}
}
namespace
{
[[nodiscard]] tr_variant::Map build_data(std::string_view error_string, tr_variant::Map&& result)
{
auto ret = tr_variant::Map{ 2U };
@@ -116,7 +117,7 @@ namespace
{
auto ret = tr_variant::Map{ 3U };
ret.try_emplace(TR_KEY_code, code);
ret.try_emplace(TR_KEY_message, tr_variant::unmanaged_string(get_message(code)));
ret.try_emplace(TR_KEY_message, tr_variant::unmanaged_string(to_string(code)));
if (!std::empty(data))
{
ret.try_emplace(TR_KEY_data, std::move(data));
@@ -205,7 +206,7 @@ void tr_rpc_idle_done_legacy(struct tr_rpc_idle_data* data, JsonRpc::Error::Code
// build the response
auto response_map = tr_variant::Map{ 3U };
response_map.try_emplace(TR_KEY_arguments, std::move(data->args_out));
response_map.try_emplace(TR_KEY_result, std::empty(result) ? JsonRpc::Error::get_message(code) : result);
response_map.try_emplace(TR_KEY_result, std::empty(result) ? JsonRpc::Error::to_string(code) : result);
if (auto& tag = data->id; tag.has_value())
{
response_map.try_emplace(TR_KEY_tag, std::move(tag));

View File

@@ -5,6 +5,7 @@
#pragma once
#include <cstdint> // int16_t
#include <functional>
struct tr_session;
@@ -34,7 +35,9 @@ enum Code : int16_t
HTTP_ERROR,
CORRUPT_TORRENT
};
}
[[nodiscard]] std::string_view to_string(Code code);
} // namespace Error
} // namespace JsonRpc
using tr_rpc_response_func = std::function<void(tr_session* session, tr_variant&& response)>;

View File

@@ -22,6 +22,7 @@
#define LIBTRANSMISSION_VARIANT_MODULE
#include "libtransmission/api-compat.h"
#include "libtransmission/error.h"
#include "libtransmission/log.h"
#include "libtransmission/quark.h"

View File

@@ -19,6 +19,8 @@
#endif
#include <libtransmission/transmission.h>
#include <libtransmission/api-compat.h>
#include <libtransmission/variant.h>
#include "CustomVariantType.h"

View File

@@ -7,6 +7,7 @@ target_sources(libtransmission-test
announce-list-test.cc
announcer-test.cc
announcer-udp-test.cc
api-compat-test.cc
benc-test.cc
bitfield-test.cc
block-info-test.cc
@@ -16,8 +17,8 @@ target_sources(libtransmission-test
completion-test.cc
copy-test.cc
crypto-test.cc
error-test.cc
dht-test.cc
error-test.cc
file-piece-map-test.cc
file-test.cc
getopt-test.cc

View File

@@ -0,0 +1,738 @@
// 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.
#include <string_view>
#include <libtransmission/api-compat.h>
#include <libtransmission/quark.h>
#include <libtransmission/variant.h>
#include "gtest/gtest.h"
#include "test-fixtures.h"
namespace
{
constexpr std::string_view LegacySessionGetJson = R"json({
"method": "session-get",
"tag": 0
})json";
constexpr std::string_view CurrentSessionGetJson = R"json({
"id": 0,
"jsonrpc": "2.0",
"method": "session_get"
})json";
constexpr std::string_view LegacySessionGetResponseJson = R"json({
"arguments": {
"alt-speed-down": 50,
"alt-speed-enabled": false,
"alt-speed-time-begin": 540,
"alt-speed-time-day": 127,
"alt-speed-time-enabled": false,
"alt-speed-time-end": 1020,
"alt-speed-up": 50,
"anti-brute-force-enabled": false,
"anti-brute-force-threshold": 100,
"blocklist-enabled": false,
"blocklist-size": 0,
"blocklist-url": "http://www.example.com/blocklist",
"cache-size-mb": 4,
"config-dir": "/home/user/.config/transmission",
"default-trackers": "",
"dht-enabled": true,
"download-dir": "/home/user/Downloads",
"download-dir-free-space": 2199023255552,
"download-queue-enabled": true,
"download-queue-size": 4,
"encryption": "preferred",
"idle-seeding-limit": 30,
"idle-seeding-limit-enabled": false,
"incomplete-dir": "/home/user/Downloads",
"incomplete-dir-enabled": false,
"lpd-enabled": false,
"peer-limit-global": 200,
"peer-limit-per-torrent": 50,
"peer-port": 51413,
"peer-port-random-on-start": false,
"pex-enabled": true,
"port-forwarding-enabled": true,
"preferred_transports": [
"utp",
"tcp"
],
"queue-stalled-enabled": true,
"queue-stalled-minutes": 30,
"rename-partial-files": true,
"reqq": 2000,
"rpc-version": 18,
"rpc-version-minimum": 14,
"rpc-version-semver": "6.0.0",
"script-torrent-added-enabled": false,
"script-torrent-added-filename": "",
"script-torrent-done-enabled": false,
"script-torrent-done-filename": "/home/user/scripts/script.sh",
"script-torrent-done-seeding-enabled": false,
"script-torrent-done-seeding-filename": "",
"seed-queue-enabled": false,
"seed-queue-size": 10,
"seedRatioLimit": 2.0,
"seedRatioLimited": false,
"sequential_download": false,
"session-id": "pdvuklydohaohwwluzpmpmllkaopzzlzzpvupkpuavhjhlzhyjfwoly",
"speed-limit-down": 100.0,
"speed-limit-down-enabled": false,
"speed-limit-up": 100.0,
"speed-limit-up-enabled": false,
"start-added-torrents": true,
"tcp-enabled": true,
"trash-original-torrent-files": false,
"units": {
"memory-bytes": 1024,
"memory-units": [
"B",
"KiB",
"MiB",
"GiB",
"TiB"
],
"size-bytes": 1000,
"size-units": [
"B",
"kB",
"MB",
"GB",
"TB"
],
"speed-bytes": 1000,
"speed-units": [
"B/s",
"kB/s",
"MB/s",
"GB/s",
"TB/s"
]
},
"utp-enabled": true,
"version": "4.1.0-beta.4 (b11bfd9712)"
},
"result": "success",
"tag": 40
})json";
constexpr std::string_view CurrentSessionGetResponseJson = R"json({
"id": 40,
"jsonrpc": "2.0",
"result": {
"alt_speed_down": 50,
"alt_speed_enabled": false,
"alt_speed_time_begin": 540,
"alt_speed_time_day": 127,
"alt_speed_time_enabled": false,
"alt_speed_time_end": 1020,
"alt_speed_up": 50,
"anti_brute_force_enabled": false,
"anti_brute_force_threshold": 100,
"blocklist_enabled": false,
"blocklist_size": 0,
"blocklist_url": "http://www.example.com/blocklist",
"cache_size_mb": 4,
"config_dir": "/home/user/.config/transmission",
"default_trackers": "",
"dht_enabled": true,
"download_dir": "/home/user/Downloads",
"download_dir_free_space": 2199023255552,
"download_queue_enabled": true,
"download_queue_size": 4,
"encryption": "preferred",
"idle_seeding_limit": 30,
"idle_seeding_limit_enabled": false,
"incomplete_dir": "/home/user/Downloads",
"incomplete_dir_enabled": false,
"lpd_enabled": false,
"peer_limit_global": 200,
"peer_limit_per_torrent": 50,
"peer_port": 51413,
"peer_port_random_on_start": false,
"pex_enabled": true,
"port_forwarding_enabled": true,
"preferred_transports": [
"utp",
"tcp"
],
"queue_stalled_enabled": true,
"queue_stalled_minutes": 30,
"rename_partial_files": true,
"reqq": 2000,
"rpc_version": 18,
"rpc_version_minimum": 14,
"rpc_version_semver": "6.0.0",
"script_torrent_added_enabled": false,
"script_torrent_added_filename": "",
"script_torrent_done_enabled": false,
"script_torrent_done_filename": "/home/user/scripts/script.sh",
"script_torrent_done_seeding_enabled": false,
"script_torrent_done_seeding_filename": "",
"seed_queue_enabled": false,
"seed_queue_size": 10,
"seed_ratio_limit": 2.0,
"seed_ratio_limited": false,
"sequential_download": false,
"session_id": "pdvuklydohaohwwluzpmpmllkaopzzlzzpvupkpuavhjhlzhyjfwoly",
"speed_limit_down": 100.0,
"speed_limit_down_enabled": false,
"speed_limit_up": 100.0,
"speed_limit_up_enabled": false,
"start_added_torrents": true,
"tcp_enabled": true,
"trash_original_torrent_files": false,
"units": {
"memory_bytes": 1024,
"memory_units": [
"B",
"KiB",
"MiB",
"GiB",
"TiB"
],
"size_bytes": 1000,
"size_units": [
"B",
"kB",
"MB",
"GB",
"TB"
],
"speed_bytes": 1000,
"speed_units": [
"B/s",
"kB/s",
"MB/s",
"GB/s",
"TB/s"
]
},
"utp_enabled": true,
"version": "4.1.0-beta.4 (b11bfd9712)"
}
})json";
constexpr std::string_view LegacyTorrentGetJson = R"json({
"arguments": {
"fields": [
"downloadDir",
"downloadedEver",
"editDate",
"error",
"errorString",
"eta",
"haveUnchecked",
"haveValid",
"id",
"isFinished",
"leftUntilDone",
"manualAnnounceTime",
"metadataPercentComplete",
"name",
"peersConnected",
"peersGettingFromUs",
"peersSendingToUs",
"percentDone",
"queuePosition",
"rateDownload",
"rateUpload",
"recheckProgress",
"seedRatioLimit",
"seedRatioMode",
"sizeWhenDone",
"status",
"uploadRatio",
"uploadedEver",
"webseedsSendingToUs"
],
"ids": "recently-active"
},
"method": "torrent-get",
"tag": 6
})json";
constexpr std::string_view CurrentTorrentGetJson = R"json({
"id": 6,
"jsonrpc": "2.0",
"method": "torrent_get",
"params": {
"fields": [
"download_dir",
"downloaded_ever",
"edit_date",
"error",
"error_string",
"eta",
"have_unchecked",
"have_valid",
"id",
"is_finished",
"left_until_done",
"manual_announce_time",
"metadata_percent_complete",
"name",
"peers_connected",
"peers_getting_from_us",
"peers_sending_to_us",
"percent_done",
"queue_position",
"rate_download",
"rate_upload",
"recheck_progress",
"seed_ratio_limit",
"seed_ratio_mode",
"size_when_done",
"status",
"upload_ratio",
"uploaded_ever",
"webseeds_sending_to_us"
],
"ids": "recently_active"
}
})json";
constexpr std::string_view CurrentPortTestErrorResponse = R"json({
"error": {
"code": 8,
"data": {
"error_string": "Couldn't test port: No Response (0)",
"result": {
"ip_protocol": "ipv6"
}
},
"message": "HTTP error from backend service"
},
"id": 9,
"jsonrpc": "2.0"
})json";
constexpr std::string_view LegacyPortTestErrorResponse = R"json({
"arguments": {
"ip_protocol": "ipv6"
},
"result": "Couldn't test port: No Response (0)",
"tag": 9
})json";
constexpr std::string_view LegacyStatsJson = R"json({
"downloaded-bytes": 12,
"files-added": 34,
"seconds-active": 56,
"session-count": 78,
"uploaded-bytes": 90
})json";
constexpr std::string_view CurrentStatsJson = R"json({
"downloaded_bytes": 12,
"files_added": 34,
"seconds_active": 56,
"session_count": 78,
"uploaded_bytes": 90
})json";
constexpr std::string_view LegacySettingsJson = R"json({
"alt-speed-down": 50,
"alt-speed-enabled": false,
"alt-speed-time-begin": 540,
"alt-speed-time-day": 127,
"alt-speed-time-enabled": false,
"alt-speed-time-end": 1020,
"alt-speed-up": 50,
"blocklist-date": 0,
"blocklist-enabled": false,
"blocklist-updates-enabled": true,
"blocklist-url": "http://www.example.com/blocklist",
"compact-view": false,
"default-trackers": "",
"dht-enabled": true,
"download-dir": "/home/user/Downloads",
"download-queue-enabled": true,
"download-queue-size": 5,
"encryption": 1,
"filter-mode": "show-all",
"filter-trackers": "",
"idle-seeding-limit": 30,
"idle-seeding-limit-enabled": false,
"incomplete-dir": "/home/user/Downloads",
"incomplete-dir-enabled": false,
"inhibit-desktop-hibernation": false,
"lpd-enabled": true,
"main-window-height": 500,
"main-window-layout-order": "menu,toolbar,filter,list,statusbar",
"main-window-width": 650,
"main-window-x": 3840,
"main-window-y": 0,
"message-level": 4,
"open-dialog-dir": "/home/user",
"peer-limit-global": 200,
"peer-limit-per-torrent": 50,
"peer-port": 51413,
"peer-port-random-high": 65535,
"peer-port-random-low": 49152,
"peer-port-random-on-start": false,
"peer-socket-tos": "le",
"pex-enabled": true,
"port-forwarding-enabled": true,
"preallocation": 1,
"prompt-before-exit": true,
"queue-stalled-minutes": 30,
"ratio-limit": 2.0,
"ratio-limit-enabled": false,
"read-clipboard": false,
"remote-session-enabled": false,
"remote-session-host": "localhost",
"remote-session-https": false,
"remote-session-password": "",
"remote-session-port": 9091,
"remote-session-requres-authentication": false,
"remote-session-username": "",
"rename-partial-files": true,
"rpc-authentication-required": false,
"rpc-enabled": false,
"rpc-password": "",
"rpc-port": 9091,
"rpc-username": "",
"rpc-whitelist": "127.0.0.1,::1",
"rpc-whitelist-enabled": true,
"script-torrent-done-enabled": false,
"script-torrent-done-filename": "",
"script-torrent-done-seeding-enabled": false,
"script-torrent-done-seeding-filename": "",
"show-backup-trackers": false,
"show-filterbar": true,
"show-notification-area-icon": false,
"show-options-window": true,
"show-statusbar": true,
"show-toolbar": true,
"show-tracker-scrapes": false,
"sleep-per-seconds-during-verify": 100,
"sort-mode": "sort-by-name",
"sort-reversed": false,
"speed-limit-down": 100,
"speed-limit-down-enabled": false,
"speed-limit-up": 100,
"speed-limit-up-enabled": false,
"start-added-torrents": true,
"start-minimized": false,
"statusbar-stats": "total-ratio",
"torrent-added-notification-enabled": true,
"torrent-complete-notification-enabled": true,
"torrent-complete-sound-command": [
"canberra-gtk-play",
"-i",
"complete-download",
"-d",
"transmission torrent downloaded"
],
"torrent-complete-sound-enabled": true,
"trash-original-torrent-files": false,
"upload-slots-per-torrent": 8,
"utp-enabled": true,
"watch-dir": "/home/user/Downloads",
"watch-dir-enabled": false
})json";
constexpr std::string_view CurrentSettingsJson = R"json({
"alt_speed_down": 50,
"alt_speed_enabled": false,
"alt_speed_time_begin": 540,
"alt_speed_time_day": 127,
"alt_speed_time_enabled": false,
"alt_speed_time_end": 1020,
"alt_speed_up": 50,
"blocklist_date": 0,
"blocklist_enabled": false,
"blocklist_updates_enabled": true,
"blocklist_url": "http://www.example.com/blocklist",
"compact_view": false,
"default_trackers": "",
"dht_enabled": true,
"download_dir": "/home/user/Downloads",
"download_queue_enabled": true,
"download_queue_size": 5,
"encryption": 1,
"filter_mode": "show-all",
"filter_trackers": "",
"idle_seeding_limit": 30,
"idle_seeding_limit_enabled": false,
"incomplete_dir": "/home/user/Downloads",
"incomplete_dir_enabled": false,
"inhibit_desktop_hibernation": false,
"lpd_enabled": true,
"main_window_height": 500,
"main_window_layout_order": "menu,toolbar,filter,list,statusbar",
"main_window_width": 650,
"main_window_x": 3840,
"main_window_y": 0,
"message_level": 4,
"open_dialog_dir": "/home/user",
"peer_limit_global": 200,
"peer_limit_per_torrent": 50,
"peer_port": 51413,
"peer_port_random_high": 65535,
"peer_port_random_low": 49152,
"peer_port_random_on_start": false,
"peer_socket_tos": "le",
"pex_enabled": true,
"port_forwarding_enabled": true,
"preallocation": 1,
"prompt_before_exit": true,
"queue_stalled_minutes": 30,
"ratio_limit": 2.0,
"ratio_limit_enabled": false,
"read_clipboard": false,
"remote_session_enabled": false,
"remote_session_host": "localhost",
"remote_session_https": false,
"remote_session_password": "",
"remote_session_port": 9091,
"remote_session_requires_authentication": false,
"remote_session_username": "",
"rename_partial_files": true,
"rpc_authentication_required": false,
"rpc_enabled": false,
"rpc_password": "",
"rpc_port": 9091,
"rpc_username": "",
"rpc_whitelist": "127.0.0.1,::1",
"rpc_whitelist_enabled": true,
"script_torrent_done_enabled": false,
"script_torrent_done_filename": "",
"script_torrent_done_seeding_enabled": false,
"script_torrent_done_seeding_filename": "",
"show_backup_trackers": false,
"show_filterbar": true,
"show_notification_area_icon": false,
"show_options_window": true,
"show_statusbar": true,
"show_toolbar": true,
"show_tracker_scrapes": false,
"sleep_per_seconds_during_verify": 100,
"sort_mode": "sort-by-name",
"sort_reversed": false,
"speed_limit_down": 100,
"speed_limit_down_enabled": false,
"speed_limit_up": 100,
"speed_limit_up_enabled": false,
"start_added_torrents": true,
"start_minimized": false,
"statusbar_stats": "total-ratio",
"torrent_added_notification_enabled": true,
"torrent_complete_notification_enabled": true,
"torrent_complete_sound_command": [
"canberra-gtk-play",
"-i",
"complete-download",
"-d",
"transmission torrent downloaded"
],
"torrent_complete_sound_enabled": true,
"trash_original_torrent_files": false,
"upload_slots_per_torrent": 8,
"utp_enabled": true,
"watch_dir": "/home/user/Downloads",
"watch_dir_enabled": false
})json";
constexpr std::string_view BadFreeSpaceRequest = R"json({
"id": 39693,
"jsonrpc": "2.0",
"method": "free_space",
"params": {
"path": "this/path/is/not/absolute"
}
})json";
constexpr std::string_view BadFreeSpaceRequestLegacy = R"json({
"arguments": {
"path": "this/path/is/not/absolute"
},
"method": "free-space",
"tag": 39693
})json";
constexpr std::string_view BadFreeSpaceResponse = R"json({
"error": {
"code": 3,
"data": {
"error_string": "directory path is not absolute"
},
"message": "path is not absolute"
},
"id": 39693,
"jsonrpc": "2.0"
})json";
constexpr std::string_view BadFreeSpaceResponseLegacy = R"json({
"arguments": {},
"result": "directory path is not absolute",
"tag": 39693
})json";
constexpr std::string_view WellFormedFreeSpaceRequest = R"json({
"id": 41414,
"jsonrpc": "2.0",
"method": "free_space",
"params": {
"path": "/this/path/does/not/exist"
}
})json";
constexpr std::string_view WellFormedFreeSpaceResponse = R"json({
"id": 41414,
"jsonrpc": "2.0",
"result": {
"path": "/this/path/does/not/exist",
"size_bytes": -1,
"total_size": -1
}
})json";
constexpr std::string_view WellFormedFreeSpaceLegacyRequest = R"json({
"arguments": {
"path": "/this/path/does/not/exist"
},
"method": "free-space",
"tag": 41414
})json";
constexpr std::string_view WellFormedFreeSpaceLegacyResponse = R"json({
"arguments": {
"path": "/this/path/does/not/exist",
"size-bytes": -1,
"total_size": -1
},
"result": "success",
"tag": 41414
})json";
constexpr std::string_view BadMethodNameResponse = R"json({
"error": {
"code": -32601,
"message": "Method not found"
},
"id": 39693,
"jsonrpc": "2.0"
})json";
constexpr std::string_view BadMethodNameLegacyResponse = R"json({
"arguments": {},
"result": "no method name",
"tag": 39693
})json";
constexpr std::string_view UnrecognisedInfoResponse = R"json({
"error": {
"code": 4,
"message": "unrecognized info"
},
"id": 10,
"jsonrpc": "2.0"
})json";
constexpr std::string_view UnrecognisedInfoLegacyResponse = R"json({
"arguments": {},
"result": "unrecognized info",
"tag": 10
})json";
} // namespace
TEST(ApiCompatTest, canConvertRpc)
{
using Style = libtransmission::api_compat::Style;
using TestCase = std::tuple<std::string_view, std::string_view, Style, std::string_view>;
// clang-format off
static auto constexpr TestCases = std::array<TestCase, 40U>{ {
{ "free_space tr5 -> tr5", BadFreeSpaceRequest, Style::Tr5, BadFreeSpaceRequest },
{ "free_space tr5 -> tr4", BadFreeSpaceRequest, Style::Tr4, BadFreeSpaceRequestLegacy },
{ "free_space tr4 -> tr5", BadFreeSpaceRequestLegacy, Style::Tr5, BadFreeSpaceRequest },
{ "free_space tr4 -> tr4", BadFreeSpaceRequestLegacy, Style::Tr4, BadFreeSpaceRequestLegacy },
{ "free_space error response tr5 -> tr5", BadFreeSpaceResponse, Style::Tr5, BadFreeSpaceResponse },
{ "free_space error response tr5 -> tr4", BadFreeSpaceResponse, Style::Tr4, BadFreeSpaceResponseLegacy },
{ "free_space error response tr4 -> tr5", BadFreeSpaceResponseLegacy, Style::Tr5, BadFreeSpaceResponse },
{ "free_space error response tr4 -> tr4", BadFreeSpaceResponseLegacy, Style::Tr4, BadFreeSpaceResponseLegacy },
{ "free_space req tr5 -> tr5", WellFormedFreeSpaceRequest, Style::Tr5, WellFormedFreeSpaceRequest },
{ "free_space req tr5 -> tr4", WellFormedFreeSpaceRequest, Style::Tr4, WellFormedFreeSpaceLegacyRequest },
{ "free_space req tr4 -> tr5", WellFormedFreeSpaceLegacyRequest, Style::Tr5, WellFormedFreeSpaceRequest },
{ "free_space req tr4 -> tr4", WellFormedFreeSpaceLegacyRequest, Style::Tr4, WellFormedFreeSpaceLegacyRequest },
{ "free_space response tr5 -> tr5", WellFormedFreeSpaceResponse, Style::Tr5, WellFormedFreeSpaceResponse },
{ "free_space response tr5 -> tr4", WellFormedFreeSpaceResponse, Style::Tr4, WellFormedFreeSpaceLegacyResponse },
{ "free_space response tr4 -> tr5", WellFormedFreeSpaceLegacyResponse, Style::Tr5, WellFormedFreeSpaceResponse },
{ "free_space response tr4 -> tr4", WellFormedFreeSpaceLegacyResponse, Style::Tr4, WellFormedFreeSpaceLegacyResponse },
{ "session_get tr5 -> tr5", CurrentSessionGetJson, Style::Tr5, CurrentSessionGetJson },
{ "session_get tr5 -> tr4", CurrentSessionGetJson, Style::Tr4, LegacySessionGetJson },
{ "session_get tr4 -> tr5", LegacySessionGetJson, Style::Tr5, CurrentSessionGetJson },
{ "session_get tr4 -> tr4", LegacySessionGetJson, Style::Tr4, LegacySessionGetJson },
{ "session_get response tr5 -> tr5", CurrentSessionGetResponseJson, Style::Tr5, CurrentSessionGetResponseJson },
{ "session_get response tr5 -> tr4", CurrentSessionGetResponseJson, Style::Tr4, LegacySessionGetResponseJson },
{ "session_get response tr4 -> tr5", LegacySessionGetResponseJson, Style::Tr5, CurrentSessionGetResponseJson },
{ "session_get response tr4 -> tr4", LegacySessionGetResponseJson, Style::Tr4, LegacySessionGetResponseJson },
{ "torrent_get tr5 -> tr5", CurrentTorrentGetJson, Style::Tr5, CurrentTorrentGetJson },
{ "torrent_get tr5 -> tr4", CurrentTorrentGetJson, Style::Tr4, LegacyTorrentGetJson },
{ "torrent_get tr4 -> tr5", LegacyTorrentGetJson, Style::Tr5, CurrentTorrentGetJson },
{ "torrent_get tr4 -> tr4", LegacyTorrentGetJson, Style::Tr4, LegacyTorrentGetJson },
{ "port_test error response tr5 -> tr5", CurrentPortTestErrorResponse, Style::Tr5, CurrentPortTestErrorResponse },
{ "port_test error response tr5 -> tr4", CurrentPortTestErrorResponse, Style::Tr4, LegacyPortTestErrorResponse },
{ "port_test error response tr4 -> tr5", LegacyPortTestErrorResponse, Style::Tr5, CurrentPortTestErrorResponse },
{ "port_test error response tr4 -> tr4", LegacyPortTestErrorResponse, Style::Tr4, LegacyPortTestErrorResponse },
{ "bad method name tr5 -> tr5", BadMethodNameResponse, Style::Tr5, BadMethodNameResponse },
{ "bad method name tr5 -> tr4", BadMethodNameResponse, Style::Tr4, BadMethodNameLegacyResponse },
{ "bad method name tr4 -> tr5", BadMethodNameLegacyResponse, Style::Tr5, BadMethodNameResponse },
{ "bad method name tr4 -> tr4", BadMethodNameLegacyResponse, Style::Tr4, BadMethodNameLegacyResponse },
{ "unrecognised info tr5 -> tr5", UnrecognisedInfoResponse, Style::Tr5, UnrecognisedInfoResponse},
{ "unrecognised info tr5 -> tr4", UnrecognisedInfoResponse, Style::Tr4, UnrecognisedInfoLegacyResponse},
{ "unrecognised info tr4 -> tr5", UnrecognisedInfoLegacyResponse, Style::Tr5, UnrecognisedInfoResponse},
{ "unrecognised info tr4 -> tr4", UnrecognisedInfoLegacyResponse, Style::Tr4, UnrecognisedInfoLegacyResponse},
// TODO(ckerr): torrent-get with 'table'
} };
// clang-format on
for (auto const& [name, src, tgt_style, expected] : TestCases)
{
auto serde = tr_variant_serde::json();
auto parsed = serde.parse(src);
ASSERT_TRUE(parsed.has_value()) << name << ": " << serde.error_;
auto converted = libtransmission::api_compat::convert(*parsed, tgt_style);
EXPECT_EQ(expected, serde.to_string(converted)) << name;
}
}
TEST(ApiCompatTest, canConvertDataFiles)
{
using Style = libtransmission::api_compat::Style;
using TestCase = std::tuple<std::string_view, std::string_view, Style, std::string_view>;
// clang-format off
static auto constexpr TestCases = std::array<TestCase, 8U>{ {
{ "settings tr5 -> tr5", CurrentSettingsJson, Style::Tr5, CurrentSettingsJson },
{ "settings tr5 -> tr4", CurrentSettingsJson, Style::Tr4, LegacySettingsJson },
{ "settings tr4 -> tr5", LegacySettingsJson, Style::Tr5, CurrentSettingsJson },
{ "settings tr4 -> tr4", LegacySettingsJson, Style::Tr4, LegacySettingsJson },
{ "stats tr5 -> tr5", CurrentStatsJson, Style::Tr5, CurrentStatsJson },
{ "stats tr5 -> tr4", CurrentStatsJson, Style::Tr4, LegacyStatsJson },
{ "stats tr4 -> tr5", LegacyStatsJson, Style::Tr5, CurrentStatsJson },
{ "stats tr4 -> tr4", LegacyStatsJson, Style::Tr4, LegacyStatsJson },
} };
// clang-format on
for (auto const& [name, src, tgt_style, expected] : TestCases)
{
auto serde = tr_variant_serde::json();
auto parsed = serde.parse(src);
ASSERT_TRUE(parsed.has_value());
auto converted = libtransmission::api_compat::convert(*parsed, tgt_style);
EXPECT_EQ(expected, serde.to_string(converted)) << name;
}
}