mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 00:09:39 +08:00
600 lines
20 KiB
C++
600 lines
20 KiB
C++
#include "features/settings/settings_manager.h"
|
|
|
|
#include <chrono>
|
|
#include <cstddef>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <functional>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <thread>
|
|
|
|
#if defined(_WIN32)
|
|
#include <io.h>
|
|
#include <windows.h>
|
|
#else
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "localization.h"
|
|
#include "rd_log.h"
|
|
#include "runtime/gui_runtime.h"
|
|
|
|
namespace crossdesk {
|
|
namespace {
|
|
|
|
constexpr uint32_t kCacheV3Magic = 0x33444358; // "XCD3"
|
|
constexpr uint32_t kCacheV3Version = 3;
|
|
|
|
template <size_t Size>
|
|
void CopyString(char (&destination)[Size], const char *source) {
|
|
static_assert(Size > 0);
|
|
std::memset(destination, 0, Size);
|
|
if (source) {
|
|
std::strncpy(destination, source, Size - 1);
|
|
}
|
|
}
|
|
|
|
uint32_t CacheChecksum(const void *data, size_t size) {
|
|
const auto *bytes = static_cast<const unsigned char *>(data);
|
|
uint32_t hash = 2166136261u;
|
|
for (size_t i = 0; i < size; ++i) {
|
|
hash ^= bytes[i];
|
|
hash *= 16777619u;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
std::filesystem::path TemporaryPathFor(
|
|
const std::filesystem::path &target) {
|
|
const auto timestamp = std::chrono::steady_clock::now()
|
|
.time_since_epoch()
|
|
.count();
|
|
const auto thread_id =
|
|
std::hash<std::thread::id>{}(std::this_thread::get_id());
|
|
std::filesystem::path temporary = target;
|
|
temporary += ".tmp-" + std::to_string(timestamp) + "-" +
|
|
std::to_string(thread_id);
|
|
return temporary;
|
|
}
|
|
|
|
bool FlushFileToDisk(FILE *file) {
|
|
if (!file || std::fflush(file) != 0) {
|
|
return false;
|
|
}
|
|
#if defined(_WIN32)
|
|
return _commit(_fileno(file)) == 0;
|
|
#else
|
|
return fsync(fileno(file)) == 0;
|
|
#endif
|
|
}
|
|
|
|
bool ReplaceFileAtomically(const std::filesystem::path &temporary,
|
|
const std::filesystem::path &target) {
|
|
#if defined(_WIN32)
|
|
return MoveFileExW(temporary.c_str(), target.c_str(),
|
|
MOVEFILE_REPLACE_EXISTING | MOVEFILE_WRITE_THROUGH) != 0;
|
|
#else
|
|
if (::rename(temporary.c_str(), target.c_str()) != 0) {
|
|
return false;
|
|
}
|
|
|
|
const std::filesystem::path parent = target.parent_path().empty()
|
|
? std::filesystem::path(".")
|
|
: target.parent_path();
|
|
int directory_flags = O_RDONLY;
|
|
#if defined(O_DIRECTORY)
|
|
directory_flags |= O_DIRECTORY;
|
|
#endif
|
|
const int directory = open(parent.c_str(), directory_flags);
|
|
if (directory >= 0) {
|
|
const bool synced = fsync(directory) == 0;
|
|
close(directory);
|
|
return synced;
|
|
}
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool WriteFileAtomically(const std::filesystem::path &target,
|
|
const void *data, size_t size) {
|
|
std::error_code ec;
|
|
const std::filesystem::path parent = target.parent_path();
|
|
if (!parent.empty()) {
|
|
std::filesystem::create_directories(parent, ec);
|
|
if (ec) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
const std::filesystem::path temporary = TemporaryPathFor(target);
|
|
#if defined(_WIN32)
|
|
FILE *file = _wfopen(temporary.c_str(), L"wb");
|
|
#else
|
|
FILE *file = std::fopen(temporary.c_str(), "wb");
|
|
#endif
|
|
if (!file) {
|
|
return false;
|
|
}
|
|
|
|
const bool written = std::fwrite(data, 1, size, file) == size;
|
|
const bool flushed = written && FlushFileToDisk(file);
|
|
const bool closed = std::fclose(file) == 0;
|
|
if (!written || !flushed || !closed ||
|
|
!ReplaceFileAtomically(temporary, target)) {
|
|
std::filesystem::remove(temporary, ec);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
SettingsManager::SettingsManager(GuiRuntime &owner) : owner_(owner) {}
|
|
|
|
int SettingsManager::Save() {
|
|
std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
return SaveLocked();
|
|
}
|
|
|
|
int SettingsManager::SaveLocked() {
|
|
CopyString(cache_v2_.client_id_with_password,
|
|
owner_.client_id_with_password_);
|
|
std::memcpy(cache_v2_.key, owner_.aes128_key_, sizeof(owner_.aes128_key_));
|
|
std::memcpy(cache_v2_.iv, owner_.aes128_iv_, sizeof(owner_.aes128_iv_));
|
|
CopyString(cache_v2_.self_hosted_id, owner_.self_hosted_id_);
|
|
|
|
cache_v3_.magic = kCacheV3Magic;
|
|
cache_v3_.version = kCacheV3Version;
|
|
cache_v3_.base = cache_v2_;
|
|
cache_v3_.pending_identity[sizeof(cache_v3_.pending_identity) - 1] = '\0';
|
|
cache_v3_.pending_server_host[sizeof(cache_v3_.pending_server_host) - 1] =
|
|
'\0';
|
|
cache_v3_.pending_request_id[sizeof(cache_v3_.pending_request_id) - 1] =
|
|
'\0';
|
|
cache_v3_.checksum =
|
|
CacheChecksum(&cache_v3_, offsetof(CacheV3, checksum));
|
|
|
|
if (!WriteFileAtomically(owner_.cache_path_ + "/secure_cache_v3.enc",
|
|
&cache_v3_, sizeof(cache_v3_))) {
|
|
return -1;
|
|
}
|
|
|
|
if (!WriteFileAtomically(owner_.cache_path_ + "/secure_cache_v2.enc",
|
|
&cache_v2_, sizeof(cache_v2_))) {
|
|
LOG_WARN("Failed to update legacy v2 credential cache");
|
|
}
|
|
|
|
// Keep writing the legacy cache while older installations may still read it.
|
|
CopyString(cache_v1_.client_id_with_password,
|
|
owner_.client_id_with_password_);
|
|
std::memcpy(cache_v1_.key, owner_.aes128_key_, sizeof(owner_.aes128_key_));
|
|
std::memcpy(cache_v1_.iv, owner_.aes128_iv_, sizeof(owner_.aes128_iv_));
|
|
if (!WriteFileAtomically(owner_.cache_path_ + "/secure_cache.enc",
|
|
&cache_v1_, sizeof(cache_v1_))) {
|
|
LOG_WARN("Failed to update legacy v1 credential cache");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool SettingsManager::ReadV3Locked() {
|
|
std::ifstream cache_file(owner_.cache_path_ + "/secure_cache_v3.enc",
|
|
std::ios::binary);
|
|
if (!cache_file) {
|
|
return false;
|
|
}
|
|
|
|
CacheV3 loaded{};
|
|
cache_file.read(reinterpret_cast<char *>(&loaded), sizeof(loaded));
|
|
if (cache_file.gcount() != static_cast<std::streamsize>(sizeof(loaded)) ||
|
|
loaded.magic != kCacheV3Magic ||
|
|
loaded.version != kCacheV3Version ||
|
|
loaded.checksum != CacheChecksum(&loaded, offsetof(CacheV3, checksum))) {
|
|
LOG_WARN("Ignore invalid v3 credential cache");
|
|
return false;
|
|
}
|
|
|
|
loaded.base.client_id_with_password
|
|
[sizeof(loaded.base.client_id_with_password) - 1] = '\0';
|
|
loaded.base.self_hosted_id[sizeof(loaded.base.self_hosted_id) - 1] = '\0';
|
|
loaded.pending_identity[sizeof(loaded.pending_identity) - 1] = '\0';
|
|
loaded.pending_server_host[sizeof(loaded.pending_server_host) - 1] = '\0';
|
|
loaded.pending_request_id[sizeof(loaded.pending_request_id) - 1] = '\0';
|
|
cache_v3_ = loaded;
|
|
cache_v2_ = loaded.base;
|
|
return true;
|
|
}
|
|
|
|
bool SettingsManager::ReadV2Locked() {
|
|
std::ifstream cache_v2_file(owner_.cache_path_ + "/secure_cache_v2.enc",
|
|
std::ios::binary);
|
|
if (!cache_v2_file) {
|
|
return false;
|
|
}
|
|
cache_v2_file.read(reinterpret_cast<char *>(&cache_v2_), sizeof(cache_v2_));
|
|
if (cache_v2_file.gcount() !=
|
|
static_cast<std::streamsize>(sizeof(cache_v2_))) {
|
|
return false;
|
|
}
|
|
cache_v2_
|
|
.client_id_with_password[sizeof(cache_v2_.client_id_with_password) - 1] =
|
|
'\0';
|
|
cache_v2_.self_hosted_id[sizeof(cache_v2_.self_hosted_id) - 1] = '\0';
|
|
return true;
|
|
}
|
|
|
|
int SettingsManager::Load() {
|
|
std::unique_lock<std::mutex> lock(cache_mutex_);
|
|
|
|
const bool loaded_v3 = ReadV3Locked();
|
|
if (loaded_v3 || ReadV2Locked()) {
|
|
CopyString(owner_.client_id_with_password_,
|
|
cache_v2_.client_id_with_password);
|
|
CopyString(owner_.self_hosted_id_, cache_v2_.self_hosted_id);
|
|
std::memcpy(owner_.aes128_key_, cache_v2_.key, sizeof(cache_v2_.key));
|
|
std::memcpy(owner_.aes128_iv_, cache_v2_.iv, sizeof(cache_v2_.iv));
|
|
if (loaded_v3) {
|
|
LOG_INFO("Load settings from v3 cache file");
|
|
} else {
|
|
cache_v3_ = {};
|
|
SaveLocked();
|
|
LOG_INFO("Migrated settings from v2 to v3 cache file");
|
|
}
|
|
} else {
|
|
std::ifstream cache_v1_file(owner_.cache_path_ + "/secure_cache.enc",
|
|
std::ios::binary);
|
|
if (!cache_v1_file) {
|
|
lock.unlock();
|
|
|
|
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
|
|
std::memset(owner_.aes128_key_, 0, sizeof(owner_.aes128_key_));
|
|
std::memset(owner_.aes128_iv_, 0, sizeof(owner_.aes128_iv_));
|
|
std::memset(owner_.self_hosted_id_, 0, sizeof(owner_.self_hosted_id_));
|
|
|
|
owner_.thumbnail_ =
|
|
std::make_shared<Thumbnail>(owner_.cache_path_ + "/thumbnails/");
|
|
owner_.thumbnail_->GetKeyAndIv(owner_.aes128_key_, owner_.aes128_iv_);
|
|
owner_.thumbnail_->DeleteAllFilesInDirectory();
|
|
|
|
Save();
|
|
return -1;
|
|
}
|
|
|
|
cache_v1_file.read(reinterpret_cast<char *>(&cache_v1_), sizeof(cache_v1_));
|
|
cache_v1_
|
|
.client_id_with_password[sizeof(cache_v1_.client_id_with_password) -
|
|
1] = '\0';
|
|
|
|
CopyString(cache_v2_.client_id_with_password,
|
|
cache_v1_.client_id_with_password);
|
|
std::memcpy(cache_v2_.key, cache_v1_.key, sizeof(cache_v1_.key));
|
|
std::memcpy(cache_v2_.iv, cache_v1_.iv, sizeof(cache_v1_.iv));
|
|
std::memset(cache_v2_.self_hosted_id, 0, sizeof(cache_v2_.self_hosted_id));
|
|
|
|
CopyString(owner_.client_id_with_password_,
|
|
cache_v1_.client_id_with_password);
|
|
std::memset(owner_.self_hosted_id_, 0, sizeof(owner_.self_hosted_id_));
|
|
std::memcpy(owner_.aes128_key_, cache_v1_.key, sizeof(cache_v1_.key));
|
|
std::memcpy(owner_.aes128_iv_, cache_v1_.iv, sizeof(cache_v1_.iv));
|
|
|
|
cache_v3_ = {};
|
|
SaveLocked();
|
|
LOG_INFO("Migrated settings from v1 to v3 cache file");
|
|
}
|
|
|
|
lock.unlock();
|
|
|
|
ActivateCachedPublicIdentity();
|
|
|
|
owner_.thumbnail_ =
|
|
std::make_shared<Thumbnail>(owner_.cache_path_ + "/thumbnails/",
|
|
owner_.aes128_key_, owner_.aes128_iv_);
|
|
|
|
owner_.language_button_value_ = localization::detail::ClampLanguageIndex(
|
|
static_cast<int>(owner_.config_center_->GetLanguage()));
|
|
owner_.video_encode_format_button_value_ =
|
|
static_cast<int>(owner_.config_center_->GetVideoEncodeFormat());
|
|
owner_.enable_hardware_video_codec_ =
|
|
owner_.config_center_->IsHardwareVideoCodec();
|
|
owner_.enable_turn_ = owner_.config_center_->IsEnableTurn();
|
|
owner_.enable_self_hosted_ = owner_.config_center_->IsSelfHosted();
|
|
owner_.enable_autostart_ = owner_.config_center_->IsEnableAutostart();
|
|
owner_.enable_daemon_ = owner_.config_center_->IsEnableDaemon();
|
|
#if _WIN32 && CROSSDESK_PORTABLE
|
|
owner_.portable_service_prompt_suppressed_ =
|
|
owner_.config_center_->IsPortableServicePromptSuppressed();
|
|
owner_.portable_service_do_not_remind_ =
|
|
owner_.portable_service_prompt_suppressed_;
|
|
#endif
|
|
|
|
const std::string saved_path =
|
|
owner_.config_center_->GetFileTransferSavePath();
|
|
CopyString(owner_.file_transfer_save_path_buf_, saved_path.c_str());
|
|
owner_.file_transfer_save_path_last_ = saved_path;
|
|
|
|
owner_.language_button_value_last_ = owner_.language_button_value_;
|
|
owner_.video_encode_format_button_value_last_ =
|
|
owner_.video_encode_format_button_value_;
|
|
owner_.enable_hardware_video_codec_last_ =
|
|
owner_.enable_hardware_video_codec_;
|
|
owner_.enable_turn_last_ = owner_.enable_turn_;
|
|
owner_.enable_self_hosted_last_ = owner_.enable_self_hosted_;
|
|
owner_.enable_autostart_last_ = owner_.enable_autostart_;
|
|
|
|
LOG_INFO("Load settings from cache file");
|
|
return 0;
|
|
}
|
|
|
|
bool SettingsManager::LoadCachedSelfHostedIdentity() {
|
|
std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
if ((!ReadV3Locked() && !ReadV2Locked()) ||
|
|
cache_v2_.self_hosted_id[0] == '\0') {
|
|
std::memset(owner_.self_hosted_id_, 0, sizeof(owner_.self_hosted_id_));
|
|
std::memset(owner_.client_id_, 0, sizeof(owner_.client_id_));
|
|
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
|
|
return false;
|
|
}
|
|
|
|
CopyString(owner_.self_hosted_id_, cache_v2_.self_hosted_id);
|
|
const char *at_pos = std::strchr(owner_.self_hosted_id_, '@');
|
|
if (at_pos == nullptr) {
|
|
CopyString(owner_.client_id_, owner_.self_hosted_id_);
|
|
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
|
|
} else {
|
|
const std::string id(owner_.self_hosted_id_,
|
|
at_pos - owner_.self_hosted_id_);
|
|
CopyString(owner_.client_id_, id.c_str());
|
|
CopyString(owner_.password_saved_, at_pos + 1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SettingsManager::ActivateCachedPublicIdentity() {
|
|
if (owner_.client_id_with_password_[0] == '\0') {
|
|
std::memset(owner_.client_id_, 0, sizeof(owner_.client_id_));
|
|
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
|
|
return false;
|
|
}
|
|
|
|
const char *at_pos = std::strchr(owner_.client_id_with_password_, '@');
|
|
if (at_pos == nullptr) {
|
|
CopyString(owner_.client_id_, owner_.client_id_with_password_);
|
|
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
|
|
} else {
|
|
const std::string id(owner_.client_id_with_password_,
|
|
at_pos - owner_.client_id_with_password_);
|
|
CopyString(owner_.client_id_, id.c_str());
|
|
CopyString(owner_.password_saved_, at_pos + 1);
|
|
}
|
|
return owner_.client_id_[0] != '\0';
|
|
}
|
|
|
|
void SettingsManager::ActivateIdentity(const char *identity,
|
|
bool self_hosted) {
|
|
if (!identity) {
|
|
return;
|
|
}
|
|
|
|
if (self_hosted) {
|
|
CopyString(owner_.self_hosted_id_, identity);
|
|
} else {
|
|
CopyString(owner_.client_id_with_password_, identity);
|
|
}
|
|
|
|
const char *at_pos = std::strchr(identity, '@');
|
|
if (at_pos == nullptr) {
|
|
CopyString(owner_.client_id_, identity);
|
|
std::memset(owner_.password_saved_, 0, sizeof(owner_.password_saved_));
|
|
return;
|
|
}
|
|
|
|
const std::string id(identity, at_pos - identity);
|
|
CopyString(owner_.client_id_, id.c_str());
|
|
CopyString(owner_.password_saved_, at_pos + 1);
|
|
}
|
|
|
|
bool SettingsManager::StagePendingPasswordChange(
|
|
const std::string &identity, bool self_hosted,
|
|
const std::string &server_host, int server_port,
|
|
const std::string &request_id) {
|
|
if (identity.empty() || identity.size() >= sizeof(cache_v3_.pending_identity) ||
|
|
server_host.empty() ||
|
|
server_host.size() >= sizeof(cache_v3_.pending_server_host) ||
|
|
server_port <= 0 || request_id.empty() ||
|
|
request_id.size() >= sizeof(cache_v3_.pending_request_id)) {
|
|
return false;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
if (cache_v3_.pending_identity[0] != '\0' &&
|
|
(cache_v3_.pending_self_hosted != self_hosted ||
|
|
cache_v3_.pending_server_port != server_port ||
|
|
server_host != cache_v3_.pending_server_host ||
|
|
request_id != cache_v3_.pending_request_id)) {
|
|
LOG_WARN("Refuse to overwrite an unresolved password change");
|
|
return false;
|
|
}
|
|
const CacheV3 previous = cache_v3_;
|
|
CopyString(cache_v3_.pending_identity, identity.c_str());
|
|
CopyString(cache_v3_.pending_server_host, server_host.c_str());
|
|
cache_v3_.pending_server_port = server_port;
|
|
cache_v3_.pending_self_hosted = self_hosted;
|
|
CopyString(cache_v3_.pending_request_id, request_id.c_str());
|
|
if (SaveLocked() != 0) {
|
|
cache_v3_ = previous;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string SettingsManager::PendingPasswordChangeIdentity(
|
|
bool self_hosted, const std::string &server_host, int server_port) const {
|
|
std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
if (cache_v3_.pending_identity[0] == '\0' ||
|
|
cache_v3_.pending_self_hosted != self_hosted ||
|
|
cache_v3_.pending_server_port != server_port ||
|
|
server_host != cache_v3_.pending_server_host) {
|
|
return {};
|
|
}
|
|
return cache_v3_.pending_identity;
|
|
}
|
|
|
|
bool SettingsManager::PromotePendingPasswordChange() {
|
|
std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
if (cache_v3_.pending_identity[0] == '\0') {
|
|
return true;
|
|
}
|
|
|
|
const CacheV3 previous = cache_v3_;
|
|
const std::string identity = cache_v3_.pending_identity;
|
|
const bool self_hosted = cache_v3_.pending_self_hosted;
|
|
ActivateIdentity(identity.c_str(), self_hosted);
|
|
std::memset(cache_v3_.pending_identity, 0,
|
|
sizeof(cache_v3_.pending_identity));
|
|
std::memset(cache_v3_.pending_server_host, 0,
|
|
sizeof(cache_v3_.pending_server_host));
|
|
cache_v3_.pending_server_port = 0;
|
|
cache_v3_.pending_self_hosted = false;
|
|
std::memset(cache_v3_.pending_request_id, 0,
|
|
sizeof(cache_v3_.pending_request_id));
|
|
if (SaveLocked() != 0) {
|
|
// The already-durable pending credential remains the recovery source on
|
|
// disk. Keep it in memory as well so a reconnect in this process retries
|
|
// the credential that the server has accepted.
|
|
cache_v3_ = previous;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool SettingsManager::ClearPendingPasswordChange() {
|
|
std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
if (cache_v3_.pending_identity[0] == '\0') {
|
|
return true;
|
|
}
|
|
|
|
const CacheV3 previous = cache_v3_;
|
|
std::memset(cache_v3_.pending_identity, 0,
|
|
sizeof(cache_v3_.pending_identity));
|
|
std::memset(cache_v3_.pending_server_host, 0,
|
|
sizeof(cache_v3_.pending_server_host));
|
|
cache_v3_.pending_server_port = 0;
|
|
cache_v3_.pending_self_hosted = false;
|
|
std::memset(cache_v3_.pending_request_id, 0,
|
|
sizeof(cache_v3_.pending_request_id));
|
|
if (SaveLocked() != 0) {
|
|
cache_v3_ = previous;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void SettingsManager::PersistSelfHostedIdentity(const char *client_id) {
|
|
if (!client_id) {
|
|
return;
|
|
}
|
|
|
|
std::lock_guard<std::mutex> lock(cache_mutex_);
|
|
CopyString(owner_.self_hosted_id_, client_id);
|
|
if (SaveLocked() != 0) {
|
|
LOG_ERROR("Failed to persist self-hosted identity atomically");
|
|
}
|
|
}
|
|
|
|
int SettingsManager::LoadRecentConnectionAliases() {
|
|
recent_connection_aliases_.clear();
|
|
|
|
std::ifstream alias_file(owner_.cache_path_ +
|
|
"/recent_connection_aliases.json");
|
|
if (!alias_file.good()) {
|
|
return 0;
|
|
}
|
|
|
|
try {
|
|
nlohmann::json alias_json;
|
|
alias_file >> alias_json;
|
|
|
|
const nlohmann::json *aliases = &alias_json;
|
|
if (alias_json.contains("aliases") && alias_json["aliases"].is_object()) {
|
|
aliases = &alias_json["aliases"];
|
|
}
|
|
if (!aliases->is_object()) {
|
|
LOG_WARN("Invalid recent connection alias file");
|
|
return -1;
|
|
}
|
|
|
|
for (auto it = aliases->begin(); it != aliases->end(); ++it) {
|
|
if (it.value().is_string()) {
|
|
const std::string remote_id = it.key();
|
|
const std::string alias = it.value().get<std::string>();
|
|
if (!remote_id.empty() && !alias.empty()) {
|
|
recent_connection_aliases_[remote_id] = alias;
|
|
}
|
|
}
|
|
}
|
|
} catch (const std::exception &e) {
|
|
LOG_WARN("Load recent connection aliases failed: {}", e.what());
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int SettingsManager::SaveRecentConnectionAliases() const {
|
|
std::error_code ec;
|
|
std::filesystem::create_directories(owner_.cache_path_, ec);
|
|
if (ec) {
|
|
LOG_WARN("Create cache directory failed while saving aliases: {}",
|
|
ec.message());
|
|
return -1;
|
|
}
|
|
|
|
nlohmann::json alias_json;
|
|
alias_json["aliases"] = nlohmann::json::object();
|
|
for (const auto &[remote_id, alias] : recent_connection_aliases_) {
|
|
if (!remote_id.empty() && !alias.empty()) {
|
|
alias_json["aliases"][remote_id] = alias;
|
|
}
|
|
}
|
|
|
|
std::ofstream alias_file(
|
|
owner_.cache_path_ + "/recent_connection_aliases.json", std::ios::trunc);
|
|
if (!alias_file.good()) {
|
|
LOG_WARN("Open recent connection alias file failed");
|
|
return -1;
|
|
}
|
|
alias_file << alias_json.dump(2);
|
|
return 0;
|
|
}
|
|
|
|
std::string SettingsManager::RecentConnectionDisplayName(
|
|
const Thumbnail::RecentConnection &connection) const {
|
|
const auto alias_it = recent_connection_aliases_.find(connection.remote_id);
|
|
if (alias_it != recent_connection_aliases_.end() &&
|
|
!alias_it->second.empty()) {
|
|
return alias_it->second;
|
|
}
|
|
if (!connection.remote_host_name.empty() &&
|
|
connection.remote_host_name != "unknown") {
|
|
return connection.remote_host_name;
|
|
}
|
|
return connection.remote_id;
|
|
}
|
|
|
|
void SettingsManager::SetRecentConnectionAlias(const std::string &remote_id,
|
|
const std::string &alias) {
|
|
if (!remote_id.empty()) {
|
|
recent_connection_aliases_[remote_id] = alias;
|
|
}
|
|
}
|
|
|
|
void SettingsManager::EraseRecentConnectionAlias(const std::string &remote_id) {
|
|
recent_connection_aliases_.erase(remote_id);
|
|
}
|
|
|
|
} // namespace crossdesk
|