Files
ObsidianDragon/src/ui/windows/backup_wallet_dialog.cpp
DanS 975743f754 feat(wallet): persist history and surface pending sends
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.
2026-05-05 03:22:14 -05:00

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