Files
transmission/qt/Prefs.cc
Charles Kerr 87511c7123 test: add Session tests (#8185)
* build: disable some clang-tidy checks

we cannot use ranges while still supporting gcc 8

* refactor: add Prefs::keyval()

refactor: remove Prefs::getKey()

test: add Prefs::keyval() tests

* refactor: add Prefs::set(idx, tr_variant)

refactor: remove Prefs::type(idx)

refactor: remove Prefs::variant(idx)

* refactor: create RpcClient in main()

* test: add qt remote session tests
2026-01-21 22:36:41 -06:00

283 lines
8.5 KiB
C++

// 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 <array>
#include <cassert>
#include <optional>
#include <string_view>
#include <utility>
#include <QDateTime>
#include <QDir>
#include <libtransmission/transmission.h>
#include <libtransmission/api-compat.h>
#include <libtransmission/serializer.h>
#include <libtransmission/variant.h>
#include "Filters.h"
#include "Prefs.h"
#include "UserMetaType.h"
namespace api_compat = libtransmission::api_compat;
namespace ser = libtransmission::serializer;
using namespace std::string_view_literals;
// ---
namespace
{
template<typename T>
[[nodiscard]] QVariant qvarFromOptional(std::optional<T> const& val)
{
return val ? QVariant::fromValue(*val) : QVariant{};
}
[[nodiscard]] QVariant qvarFromTVar(tr_variant const& var, int const qt_metatype)
{
switch (qt_metatype)
{
case QMetaType::Int:
return qvarFromOptional(ser::to_value<int64_t>(var));
case UserMetaType::EncryptionModeType:
return qvarFromOptional(ser::to_value<tr_encryption_mode>(var));
case UserMetaType::SortModeType:
return qvarFromOptional(ser::to_value<SortMode>(var));
case UserMetaType::ShowModeType:
return qvarFromOptional(ser::to_value<ShowMode>(var));
case QMetaType::QString:
return qvarFromOptional(ser::to_value<QString>(var));
case QMetaType::QStringList:
return qvarFromOptional(ser::to_value<QStringList>(var));
case QMetaType::Bool:
return qvarFromOptional(ser::to_value<bool>(var));
case QMetaType::Double:
return qvarFromOptional(ser::to_value<double>(var));
case QMetaType::QDateTime:
return qvarFromOptional(ser::to_value<QDateTime>(var));
default:
assert(false && "unhandled type");
return {};
}
}
[[nodiscard]] tr_variant trvarFromQVar(QVariant const& var, int const qt_metatype)
{
switch (qt_metatype)
{
case QMetaType::Int:
return ser::to_variant(var.value<int>());
case UserMetaType::EncryptionModeType:
return ser::to_variant(var.value<tr_encryption_mode>());
case UserMetaType::SortModeType:
return ser::to_variant(var.value<SortMode>());
case UserMetaType::ShowModeType:
return ser::to_variant(var.value<ShowMode>());
case QMetaType::QString:
return ser::to_variant(var.value<QString>());
case QMetaType::QStringList:
return ser::to_variant(var.value<QStringList>());
case QMetaType::Bool:
return ser::to_variant(var.value<bool>());
case QMetaType::Double:
return ser::to_variant(var.value<double>());
case QMetaType::QDateTime:
return ser::to_variant(var.value<QDateTime>());
default:
assert(false && "unhandled type");
return {};
}
}
void ensureSoundCommandIsAList(tr_variant::Map& map)
{
auto constexpr Key = TR_KEY_torrent_complete_sound_command;
auto constexpr DefaultVal = std::array<std::string_view, 5U>{ "canberra-gtk-play",
"-i",
"complete-download",
"-d",
"transmission torrent downloaded" };
if (map.find_if<tr_variant::Vector>(Key) == nullptr)
{
map.insert_or_assign(Key, ser::to_variant(DefaultVal));
}
}
} // namespace
namespace
{
[[nodiscard]] constexpr auto prefIsSavable(int pref)
{
switch (pref)
{
// these are the prefs that don't get saved to settings.json
// when the application exits.
case Prefs::FILTER_TEXT:
return false;
default:
return true;
}
}
} // namespace
// ---
Prefs::Prefs()
{
static_assert(sizeof(Items) / sizeof(Items[0]) == PREFS_COUNT);
#ifndef NDEBUG
for (int i = 0; i < PREFS_COUNT; ++i)
{
assert(Items[i].id == i);
}
#endif
load(defaults());
}
void Prefs::loadFromConfigDir(QString const dir)
{
auto settings = tr_sessionLoadSettings(dir.toStdString());
if (auto* const map = settings.get_if<tr_variant::Map>())
{
ensureSoundCommandIsAList(*map);
load(*map);
}
}
void Prefs::load(tr_variant::Map const& settings)
{
for (int idx = 0; idx < PREFS_COUNT; ++idx)
{
if (auto const iter = settings.find(Items[idx].key); iter != settings.end())
{
values_[idx] = qvarFromTVar(iter->second, Items[idx].type);
}
}
}
void Prefs::set(int const key, tr_variant const& value)
{
auto const tmp = qvarFromTVar(value, Items[key].type);
if (tmp.isNull())
{
return;
}
auto& v = values_[key];
if (v.isNull() || v != tmp)
{
v = tmp;
emit changed(key);
}
}
tr_variant::Map Prefs::current_settings() const
{
auto map = tr_variant::Map{ PREFS_COUNT };
for (int idx = 0; idx < PREFS_COUNT; ++idx)
{
if (prefIsSavable(idx))
{
auto [key, val] = keyval(idx);
map.try_emplace(key, std::move(val));
}
}
return map;
}
std::pair<tr_quark, tr_variant> Prefs::keyval(int const idx) const
{
return { Items[idx].key, trvarFromQVar(values_[idx], Items[idx].type) };
}
void Prefs::save(QString const& filename) const
{
auto const filename_str = filename.toStdString();
auto serde = tr_variant_serde::json();
auto settings = tr_variant::make_map(PREFS_COUNT);
if (auto const var = serde.parse_file(filename_str))
{
settings.merge(*var);
}
settings.merge(tr_variant{ current_settings() });
api_compat::convert_outgoing_data(settings);
serde.to_file(settings, filename_str);
}
/**
* This is where we initialize the preferences file with the default values.
* If you add a new preferences key, you /must/ add a default value here.
*/
// static
tr_variant::Map Prefs::defaults()
{
auto const download_dir = tr_getDefaultDownloadDir();
auto map = tr_variant::Map{ 64U };
map.try_emplace(TR_KEY_blocklist_date, 0);
map.try_emplace(TR_KEY_blocklist_updates_enabled, true);
map.try_emplace(TR_KEY_compact_view, false);
map.try_emplace(TR_KEY_download_dir, download_dir);
map.try_emplace(TR_KEY_filter_mode, ser::to_variant(DefaultShowMode));
map.try_emplace(TR_KEY_inhibit_desktop_hibernation, false);
map.try_emplace(TR_KEY_main_window_height, 500);
map.try_emplace(TR_KEY_main_window_layout_order, tr_variant::unmanaged_string("menu,toolbar,filter,list,statusbar"sv));
map.try_emplace(TR_KEY_main_window_width, 600);
map.try_emplace(TR_KEY_main_window_x, 50);
map.try_emplace(TR_KEY_main_window_y, 50);
map.try_emplace(TR_KEY_open_dialog_dir, QDir::home().absolutePath().toStdString());
map.try_emplace(TR_KEY_prompt_before_exit, true);
map.try_emplace(TR_KEY_read_clipboard, false);
map.try_emplace(TR_KEY_remote_session_enabled, false);
map.try_emplace(TR_KEY_remote_session_host, tr_variant::unmanaged_string("localhost"sv));
map.try_emplace(TR_KEY_remote_session_https, false);
map.try_emplace(TR_KEY_remote_session_password, tr_variant::unmanaged_string(""sv));
map.try_emplace(TR_KEY_remote_session_port, TrDefaultRpcPort);
map.try_emplace(TR_KEY_remote_session_requires_authentication, false);
map.try_emplace(TR_KEY_remote_session_url_base_path, tr_variant::unmanaged_string(TrDefaultHttpServerBasePath));
map.try_emplace(TR_KEY_remote_session_username, tr_variant::unmanaged_string(""sv));
map.try_emplace(TR_KEY_show_backup_trackers, false);
map.try_emplace(TR_KEY_show_filterbar, true);
map.try_emplace(TR_KEY_show_notification_area_icon, false);
map.try_emplace(TR_KEY_show_options_window, true);
map.try_emplace(TR_KEY_show_statusbar, true);
map.try_emplace(TR_KEY_show_toolbar, true);
map.try_emplace(TR_KEY_show_tracker_scrapes, false);
map.try_emplace(TR_KEY_sort_mode, ser::to_variant(DefaultSortMode));
map.try_emplace(TR_KEY_sort_reversed, false);
map.try_emplace(TR_KEY_start_minimized, false);
map.try_emplace(TR_KEY_statusbar_stats, tr_variant::unmanaged_string("total-ratio"));
map.try_emplace(TR_KEY_torrent_added_notification_enabled, true);
map.try_emplace(TR_KEY_torrent_complete_notification_enabled, true);
map.try_emplace(TR_KEY_torrent_complete_sound_enabled, true);
map.try_emplace(TR_KEY_watch_dir, download_dir);
map.try_emplace(TR_KEY_watch_dir_enabled, false);
return map;
}