- Replace all hardcoded English strings with TR() translation keys across every tab, dialog, and component (~20 UI files) - Expand all 8 language files (de, es, fr, ja, ko, pt, ru, zh) with complete translations (~37k lines added) - Improve i18n loader with exe-relative path fallback and English base fallback for missing keys - Add pool-side hashrate polling via pool stats API in xmrig_manager - Introduce Layout::beginFrame() per-frame caching and refresh balance layout config only on schema generation change - Offload daemon output parsing to worker thread - Add CJK subset fallback font for Chinese/Japanese/Korean glyphs
189 lines
6.5 KiB
C++
189 lines
6.5 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#include "backup_wallet_dialog.h"
|
|
#include "../../app.h"
|
|
#include "../../rpc/rpc_client.h"
|
|
#include "../../rpc/rpc_worker.h"
|
|
#include "../../util/i18n.h"
|
|
#include "../../util/platform.h"
|
|
#include "../notifications.h"
|
|
#include "../schema/ui_schema.h"
|
|
#include "../material/draw_helpers.h"
|
|
#include "../theme.h"
|
|
#include "imgui.h"
|
|
|
|
#include <string>
|
|
#include <ctime>
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
|
|
namespace dragonx {
|
|
namespace ui {
|
|
|
|
namespace fs = std::filesystem;
|
|
using json = nlohmann::json;
|
|
|
|
// Static state
|
|
static bool s_open = false;
|
|
static char s_destination[512] = "";
|
|
static std::string s_status;
|
|
static bool s_backing_up = false;
|
|
|
|
void BackupWalletDialog::show()
|
|
{
|
|
s_open = true;
|
|
s_status.clear();
|
|
s_backing_up = false;
|
|
|
|
// Generate default destination with timestamp
|
|
std::time_t now = std::time(nullptr);
|
|
char timebuf[32];
|
|
std::strftime(timebuf, sizeof(timebuf), "%Y%m%d_%H%M%S", std::localtime(&now));
|
|
|
|
// Default to home directory
|
|
std::string home = util::Platform::getHomeDir();
|
|
snprintf(s_destination, sizeof(s_destination), "%s/wallet_backup_%s.dat", home.c_str(), timebuf);
|
|
}
|
|
|
|
bool BackupWalletDialog::isOpen()
|
|
{
|
|
return s_open;
|
|
}
|
|
|
|
void BackupWalletDialog::render(App* app)
|
|
{
|
|
if (!s_open) return;
|
|
|
|
auto& S = schema::UI();
|
|
auto win = S.window("dialogs.backup-wallet");
|
|
auto backupBtn = S.button("dialogs.backup-wallet", "backup-button");
|
|
auto closeBtn = S.button("dialogs.backup-wallet", "close-button");
|
|
|
|
if (material::BeginOverlayDialog(TR("backup_title"), &s_open, win.width, 0.94f)) {
|
|
ImGui::TextWrapped("%s", TR("backup_description"));
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
if (s_backing_up) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
|
|
// Destination path
|
|
ImGui::Text("%s", TR("backup_destination"));
|
|
ImGui::SetNextItemWidth(-1);
|
|
ImGui::InputText("##Destination", s_destination, sizeof(s_destination));
|
|
|
|
ImGui::Spacing();
|
|
|
|
// Show wallet.dat location
|
|
std::string walletPath = util::Platform::getDataDir() + "/wallet.dat";
|
|
ImGui::TextDisabled(TR("backup_source"), walletPath.c_str());
|
|
|
|
// Check if source exists
|
|
bool sourceExists = fs::exists(walletPath);
|
|
if (!sourceExists) {
|
|
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1.0f, 0.4f, 0.4f, 1.0f));
|
|
ImGui::Text("%s", TR("backup_wallet_not_found"));
|
|
ImGui::PopStyleColor();
|
|
}
|
|
|
|
if (s_backing_up) {
|
|
ImGui::EndDisabled();
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
// Backup button - use RPC backupwallet
|
|
if (s_backing_up) {
|
|
ImGui::BeginDisabled();
|
|
}
|
|
|
|
if (material::StyledButton(TR("backup_create"), ImVec2(backupBtn.width, 0), S.resolveFont(backupBtn.font))) {
|
|
if (strlen(s_destination) == 0) {
|
|
Notifications::instance().warning("Please enter a destination path");
|
|
} else if (!app->rpc() || !app->rpc()->isConnected()) {
|
|
Notifications::instance().error("Not connected to daemon");
|
|
} else {
|
|
s_backing_up = true;
|
|
s_status = "Creating backup...";
|
|
|
|
// Run backup on worker thread to avoid freezing UI
|
|
std::string dest(s_destination);
|
|
if (app->worker()) {
|
|
app->worker()->post([rpc = app->rpc(), dest]() -> rpc::RPCWorker::MainCb {
|
|
bool success = false;
|
|
std::string statusMsg;
|
|
try {
|
|
rpc->call("backupwallet", json::array({dest}));
|
|
// Check if file was created
|
|
if (fs::exists(dest)) {
|
|
auto size = fs::file_size(dest);
|
|
char sizebuf[32];
|
|
if (size > 1024 * 1024) {
|
|
snprintf(sizebuf, sizeof(sizebuf), "%.2f MB", size / (1024.0 * 1024.0));
|
|
} else if (size > 1024) {
|
|
snprintf(sizebuf, sizeof(sizebuf), "%.2f KB", size / 1024.0);
|
|
} else {
|
|
snprintf(sizebuf, sizeof(sizebuf), "%zu bytes", size);
|
|
}
|
|
statusMsg = std::string("Backup created successfully (") + sizebuf + ")";
|
|
success = true;
|
|
} else {
|
|
statusMsg = "Backup may have failed - file not found";
|
|
}
|
|
} catch (const std::exception& e) {
|
|
statusMsg = std::string("Backup failed: ") + e.what();
|
|
}
|
|
return [success, statusMsg]() {
|
|
s_status = statusMsg;
|
|
s_backing_up = false;
|
|
if (success) {
|
|
Notifications::instance().success(TR("backup_created"));
|
|
} else {
|
|
Notifications::instance().warning(statusMsg);
|
|
}
|
|
};
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (s_backing_up) {
|
|
ImGui::EndDisabled();
|
|
ImGui::SameLine();
|
|
ImGui::TextDisabled("%s", TR("backup_backing_up"));
|
|
}
|
|
|
|
ImGui::SameLine();
|
|
if (material::StyledButton(TR("close"), ImVec2(closeBtn.width, 0), S.resolveFont(closeBtn.font))) {
|
|
s_open = false;
|
|
}
|
|
|
|
// Status
|
|
if (!s_status.empty()) {
|
|
ImGui::Spacing();
|
|
ImGui::TextWrapped("%s", s_status.c_str());
|
|
}
|
|
|
|
ImGui::Spacing();
|
|
ImGui::Separator();
|
|
ImGui::Spacing();
|
|
|
|
// Tips
|
|
ImGui::TextDisabled("%s", TR("backup_tips"));
|
|
ImGui::BulletText("%s", TR("backup_tip_external"));
|
|
ImGui::BulletText("%s", TR("backup_tip_multiple"));
|
|
ImGui::BulletText("%s", TR("backup_tip_test"));
|
|
material::EndOverlayDialog();
|
|
}
|
|
}
|
|
|
|
} // namespace ui
|
|
} // namespace dragonx
|