daemon version check, idle mining control, bootstrap mirror, import key paste, and cleanup
- Add startup binary version checking for dragonxd/xmrig - Display daemon version in UI - Add idle mining thread count adjustment - Add bootstrap mirror option (bootstrap2.dragonx.is) in setup wizard - Add paste button to import private key dialog with address validation - Add z-address generation UI feedback (loading indicator) - Add option to delete blockchain data while preserving wallet.dat - Add font scale slider hotkey tooltip (Ctrl+Plus/Ctrl+Minus) - Fix Windows RPC auth: trim \r from config values, add .cookie fallback - Fix connection status message during block index loading - Improve application shutdown to prevent lingering background process
This commit is contained in:
127
src/app.cpp
127
src/app.cpp
@@ -104,6 +104,12 @@ bool App::init()
|
||||
if (!settings_->load()) {
|
||||
DEBUG_LOGF("Warning: Could not load settings, using defaults\n");
|
||||
}
|
||||
// On upgrade (version mismatch), re-save to persist new defaults + current version
|
||||
if (settings_->needsUpgradeSave()) {
|
||||
DEBUG_LOGF("[INFO] Wallet upgraded — re-saving settings with new defaults\n");
|
||||
settings_->save();
|
||||
settings_->clearUpgradeSave();
|
||||
}
|
||||
|
||||
// Apply verbose logging preference from saved settings
|
||||
util::Logger::instance().setVerbose(settings_->getVerboseLogging());
|
||||
@@ -137,6 +143,27 @@ bool App::init()
|
||||
{
|
||||
std::string schemaPath = util::Platform::getExecutableDirectory() + "/res/themes/ui.toml";
|
||||
bool loaded = false;
|
||||
|
||||
#if HAS_EMBEDDED_UI_TOML
|
||||
// If on-disk ui.toml exists but differs in size from the embedded
|
||||
// version, a newer wallet binary is running against stale theme
|
||||
// files. Overwrite the on-disk copy so layout matches the binary.
|
||||
if (std::filesystem::exists(schemaPath)) {
|
||||
std::error_code ec;
|
||||
auto diskSize = std::filesystem::file_size(schemaPath, ec);
|
||||
if (!ec && diskSize != static_cast<std::uintmax_t>(embedded::ui_toml_size)) {
|
||||
DEBUG_LOGF("[INFO] ui.toml on disk (%ju bytes) differs from embedded (%zu bytes) — updating\n",
|
||||
(uintmax_t)diskSize, embedded::ui_toml_size);
|
||||
std::ofstream ofs(schemaPath, std::ios::binary | std::ios::trunc);
|
||||
if (ofs.is_open()) {
|
||||
ofs.write(reinterpret_cast<const char*>(embedded::ui_toml_data),
|
||||
embedded::ui_toml_size);
|
||||
ofs.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (std::filesystem::exists(schemaPath)) {
|
||||
loaded = ui::schema::UISchema::instance().loadFromFile(schemaPath);
|
||||
}
|
||||
@@ -1548,6 +1575,9 @@ void App::renderImportKeyDialog()
|
||||
return it != dlg.extraFloats.end() ? it->second : fb;
|
||||
};
|
||||
|
||||
int btnFont = (int)dlgF("button-font", 1);
|
||||
float btnW = dlgF("button-width", 120.0f);
|
||||
|
||||
if (!ui::material::BeginOverlayDialog("Import Private Key", &show_import_key_, dlgF("width", 500.0f), 0.94f)) {
|
||||
return;
|
||||
}
|
||||
@@ -1561,6 +1591,50 @@ void App::renderImportKeyDialog()
|
||||
ImGui::SetNextItemWidth(-1);
|
||||
ImGui::InputText("##importkey", import_key_input_, sizeof(import_key_input_));
|
||||
|
||||
// Paste & Clear buttons
|
||||
if (ui::material::StyledButton(TR("paste"), ImVec2(0, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
const char* clipboard = ImGui::GetClipboardText();
|
||||
if (clipboard) {
|
||||
snprintf(import_key_input_, sizeof(import_key_input_), "%s", clipboard);
|
||||
// Trim whitespace
|
||||
std::string trimmed(import_key_input_);
|
||||
while (!trimmed.empty() && (trimmed.front() == ' ' || trimmed.front() == '\t' ||
|
||||
trimmed.front() == '\n' || trimmed.front() == '\r'))
|
||||
trimmed.erase(trimmed.begin());
|
||||
while (!trimmed.empty() && (trimmed.back() == ' ' || trimmed.back() == '\t' ||
|
||||
trimmed.back() == '\n' || trimmed.back() == '\r'))
|
||||
trimmed.pop_back();
|
||||
snprintf(import_key_input_, sizeof(import_key_input_), "%s", trimmed.c_str());
|
||||
}
|
||||
}
|
||||
ImGui::SameLine();
|
||||
if (ui::material::StyledButton(TR("clear"), ImVec2(0, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
memset(import_key_input_, 0, sizeof(import_key_input_));
|
||||
}
|
||||
|
||||
// Key validation indicator
|
||||
if (import_key_input_[0] != '\0') {
|
||||
std::string k(import_key_input_);
|
||||
bool isZKey = (k.substr(0, 20) == "secret-extended-key-") ||
|
||||
(k.length() >= 2 && k[0] == 'S' && k[1] == 'K');
|
||||
bool isTKey = (k.length() >= 51 && k.length() <= 52 &&
|
||||
(k[0] == '5' || k[0] == 'K' || k[0] == 'L' || k[0] == 'U'));
|
||||
if (isZKey || isTKey) {
|
||||
ImGui::PushFont(ui::material::Type().iconSmall());
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), ICON_MD_CHECK_CIRCLE);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 4.0f);
|
||||
ImGui::TextColored(ImVec4(0.3f, 0.8f, 0.3f, 1.0f), "%s",
|
||||
isZKey ? "Shielded spending key" : "Transparent private key");
|
||||
} else {
|
||||
ImGui::PushFont(ui::material::Type().iconSmall());
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), ICON_MD_HELP);
|
||||
ImGui::PopFont();
|
||||
ImGui::SameLine(0, 4.0f);
|
||||
ImGui::TextColored(ImVec4(0.8f, 0.6f, 0.0f, 1.0f), "Unrecognized key format");
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::Spacing();
|
||||
|
||||
if (!import_status_.empty()) {
|
||||
@@ -1574,8 +1648,6 @@ void App::renderImportKeyDialog()
|
||||
ImGui::Spacing();
|
||||
ImGui::Separator();
|
||||
|
||||
int btnFont = (int)dlgF("button-font", 1);
|
||||
float btnW = dlgF("button-width", 120.0f);
|
||||
if (ui::material::StyledButton("Import", ImVec2(btnW, 0), ui::material::resolveButtonFont(btnFont))) {
|
||||
std::string key(import_key_input_);
|
||||
if (!key.empty()) {
|
||||
@@ -2037,6 +2109,57 @@ void App::rescanBlockchain()
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void App::deleteBlockchainData()
|
||||
{
|
||||
if (!isUsingEmbeddedDaemon() || !embedded_daemon_) {
|
||||
ui::Notifications::instance().warning(
|
||||
"Delete blockchain requires embedded daemon. Stop your daemon manually and delete the data directory.");
|
||||
return;
|
||||
}
|
||||
|
||||
DEBUG_LOGF("[App] Deleting blockchain data - stopping daemon first\n");
|
||||
ui::Notifications::instance().info("Stopping daemon and deleting blockchain data...");
|
||||
|
||||
std::thread([this]() {
|
||||
DEBUG_LOGF("[App] Stopping daemon for blockchain deletion...\n");
|
||||
stopEmbeddedDaemon();
|
||||
if (shutting_down_) return;
|
||||
|
||||
for (int i = 0; i < 30 && !shutting_down_; ++i)
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
if (shutting_down_) return;
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
std::string dataDir = util::Platform::getDragonXDataDir();
|
||||
|
||||
// Directories to remove
|
||||
const char* dirs[] = { "blocks", "chainstate", "database", "notarizations" };
|
||||
// Files to remove
|
||||
const char* files[] = { "peers.dat", "fee_estimates.dat", "banlist.dat",
|
||||
"db.log", ".lock" };
|
||||
|
||||
int removed = 0;
|
||||
std::error_code ec;
|
||||
for (auto d : dirs) {
|
||||
fs::path p = fs::path(dataDir) / d;
|
||||
if (fs::exists(p, ec)) {
|
||||
auto n = fs::remove_all(p, ec);
|
||||
if (!ec) { removed += (int)n; DEBUG_LOGF("[App] Removed %s (%d entries)\n", d, (int)n); }
|
||||
else { DEBUG_LOGF("[App] Failed to remove %s: %s\n", d, ec.message().c_str()); }
|
||||
}
|
||||
}
|
||||
for (auto f : files) {
|
||||
fs::path p = fs::path(dataDir) / f;
|
||||
if (fs::remove(p, ec)) { removed++; DEBUG_LOGF("[App] Removed %s\n", f); }
|
||||
}
|
||||
|
||||
DEBUG_LOGF("[App] Blockchain data deleted (%d items removed), restarting daemon...\n", removed);
|
||||
|
||||
daemon_output_offset_ = 0;
|
||||
startEmbeddedDaemon();
|
||||
}).detach();
|
||||
}
|
||||
|
||||
double App::getDaemonMemoryUsageMB() const
|
||||
{
|
||||
// If we have an embedded daemon with a tracked process handle, use it
|
||||
|
||||
Reference in New Issue
Block a user