Add an encrypted SQLite transaction history cache with cached tip metadata and per-address shielded scan progress so startup and full refreshes avoid re-scanning every z-address while still invalidating on wallet/address/rescan changes. Improve wallet history loading by paging transparent transactions, preserving cached shielded and sent rows, keeping recent/unconfirmed activity visible, and classifying mining-address receives. Show z_sendmany opid sends immediately in History and Overview, pin pending rows through refreshes, and apply optimistic address/balance debits until opids resolve. Add timestamped RPC console tracing by source/method without logging params or results, reduce redundant refresh/RPC calls, and cache Explorer recent block summaries in SQLite. Expand focused tests for transaction cache encryption, scan-progress persistence/invalidation, history preservation, operation-status parsing, pending send visibility, and Explorer/RPC refresh behavior.
190 lines
6.6 KiB
C++
190 lines
6.6 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::RPCClient::TraceScope trace("Settings / Backup wallet");
|
|
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"), 5.0f);
|
|
} 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
|