v1.1.0: explorer tab, bootstrap fixes, full theme overlay merge
Explorer tab:
- New block explorer tab with search, chain stats, mempool info,
recent blocks table, block detail modal with tx expansion
- Sidebar nav entry, i18n strings, ui.toml layout values
Bootstrap fixes:
- Move wizard Done handler into render() — was dead code, preventing
startEmbeddedDaemon() and tryConnect() from firing post-wizard
- Stop deleting BDB database/ dir during cleanup — caused LSN mismatch
that salvaged wallet.dat into wallet.{timestamp}.bak
- Add banlist.dat, db.log, .lock to cleanup file list
- Fatal extraction failure for blocks/ and chainstate/ files
- Verification progress: split SHA-256 (0-50%) and MD5 (50-100%)
Theme system:
- Expand overlay merge to apply ALL sections (tabs, dialogs, components,
screens, flat sections), not just theme+backdrop+effects
- Add screens and security section parsing to UISchema
- Build-time theme expansion via expand_themes.py (CMake + build.sh)
Other:
- Version bump to 1.1.0
- WalletState::clear() resets all fields (sync, daemon info, etc.)
- Sidebar item-height 42 → 36
This commit is contained in:
@@ -405,7 +405,13 @@ bool Bootstrap::extract(const std::string& zipPath, const std::string& dataDir)
|
||||
|
||||
if (!mz_zip_reader_extract_to_file(&zip, i, destPath.c_str(), 0)) {
|
||||
DEBUG_LOGF("[Bootstrap] Failed to extract: %s\n", filename.c_str());
|
||||
// Non-fatal: continue with remaining files
|
||||
// Critical chain data must extract successfully
|
||||
if (filename.rfind("blocks/", 0) == 0 || filename.rfind("chainstate/", 0) == 0) {
|
||||
setProgress(State::Failed, "Failed to extract critical file: " + filename);
|
||||
mz_zip_reader_end(&zip);
|
||||
return false;
|
||||
}
|
||||
// Non-fatal for other files: continue with remaining
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +437,13 @@ bool Bootstrap::extract(const std::string& zipPath, const std::string& dataDir)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
void Bootstrap::cleanChainData(const std::string& dataDir) {
|
||||
// Directories to remove completely
|
||||
// Directories to remove completely.
|
||||
// NOTE: "database" is intentionally NOT removed here. It contains BDB
|
||||
// environment/log files for wallet.dat. Deleting it while keeping wallet.dat
|
||||
// creates a BDB LSN mismatch that causes the daemon to "salvage" the wallet
|
||||
// (renaming it to wallet.{timestamp}.bak and creating an empty replacement).
|
||||
// The daemon's DB_RECOVER flag in CDBEnv::Open() handles stale environment
|
||||
// files gracefully when the environment dir still exists.
|
||||
for (const char* subdir : {"blocks", "chainstate", "notarizations"}) {
|
||||
fs::path p = fs::path(dataDir) / subdir;
|
||||
std::error_code ec;
|
||||
@@ -441,14 +453,14 @@ void Bootstrap::cleanChainData(const std::string& dataDir) {
|
||||
}
|
||||
}
|
||||
// Individual files to remove (will be replaced by bootstrap)
|
||||
for (const char* file : {"fee_estimates.dat", "peers.dat"}) {
|
||||
for (const char* file : {"fee_estimates.dat", "peers.dat", "banlist.dat", "db.log", ".lock"}) {
|
||||
fs::path p = fs::path(dataDir) / file;
|
||||
std::error_code ec;
|
||||
if (fs::exists(p, ec)) {
|
||||
fs::remove(p, ec);
|
||||
}
|
||||
}
|
||||
// NEVER remove: wallet.dat, debug.log, .lock, *.conf
|
||||
// NEVER remove: wallet.dat, debug.log, *.conf
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -505,7 +517,7 @@ std::string Bootstrap::parseChecksumFile(const std::string& content) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string Bootstrap::computeSHA256(const std::string& filePath) {
|
||||
std::string Bootstrap::computeSHA256(const std::string& filePath, float pctBase, float pctRange) {
|
||||
FILE* fp = fopen(filePath.c_str(), "rb");
|
||||
if (!fp) return {};
|
||||
|
||||
@@ -530,12 +542,18 @@ std::string Bootstrap::computeSHA256(const std::string& filePath) {
|
||||
|
||||
// Update progress every ~4MB
|
||||
if (fileSize > 0 && (processed % (4 * 1024 * 1024)) < (long long)sizeof(buf)) {
|
||||
float pct = (float)(100.0 * processed / fileSize);
|
||||
float filePct = (float)(100.0 * processed / fileSize);
|
||||
float overallPct = pctBase + pctRange * (float)processed / (float)fileSize;
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg), "Verifying SHA-256... %.0f%% (%s / %s)",
|
||||
pct, formatSize((double)processed).c_str(),
|
||||
filePct, formatSize((double)processed).c_str(),
|
||||
formatSize((double)fileSize).c_str());
|
||||
setProgress(State::Verifying, msg, (double)processed, (double)fileSize);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mutex_);
|
||||
progress_.state = State::Verifying;
|
||||
progress_.status_text = msg;
|
||||
progress_.percent = overallPct;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
@@ -636,7 +654,7 @@ static void md5_final(MD5Context* ctx, uint8_t digest[16]) {
|
||||
|
||||
} // anon namespace
|
||||
|
||||
std::string Bootstrap::computeMD5(const std::string& filePath) {
|
||||
std::string Bootstrap::computeMD5(const std::string& filePath, float pctBase, float pctRange) {
|
||||
FILE* fp = fopen(filePath.c_str(), "rb");
|
||||
if (!fp) return {};
|
||||
|
||||
@@ -661,12 +679,18 @@ std::string Bootstrap::computeMD5(const std::string& filePath) {
|
||||
|
||||
// Update progress every ~4MB
|
||||
if (fileSize > 0 && (processed % (4 * 1024 * 1024)) < (long long)sizeof(buf)) {
|
||||
float pct = (float)(100.0 * processed / fileSize);
|
||||
float filePct = (float)(100.0 * processed / fileSize);
|
||||
float overallPct = pctBase + pctRange * (float)processed / (float)fileSize;
|
||||
char msg[128];
|
||||
snprintf(msg, sizeof(msg), "Verifying MD5... %.0f%% (%s / %s)",
|
||||
pct, formatSize((double)processed).c_str(),
|
||||
filePct, formatSize((double)processed).c_str(),
|
||||
formatSize((double)fileSize).c_str());
|
||||
setProgress(State::Verifying, msg, (double)processed, (double)fileSize);
|
||||
{
|
||||
std::lock_guard<std::mutex> lk(mutex_);
|
||||
progress_.state = State::Verifying;
|
||||
progress_.status_text = msg;
|
||||
progress_.percent = overallPct;
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
@@ -705,11 +729,23 @@ bool Bootstrap::verifyChecksums(const std::string& zipPath, const std::string& b
|
||||
return true;
|
||||
}
|
||||
|
||||
// Determine progress ranges: if both checksums exist, split 0-50% / 50-100%
|
||||
float sha256Base = 0.0f, sha256Range = 0.0f;
|
||||
float md5Base = 0.0f, md5Range = 0.0f;
|
||||
if (haveSHA256 && haveMD5) {
|
||||
sha256Base = 0.0f; sha256Range = 50.0f;
|
||||
md5Base = 50.0f; md5Range = 50.0f;
|
||||
} else if (haveSHA256) {
|
||||
sha256Base = 0.0f; sha256Range = 100.0f;
|
||||
} else {
|
||||
md5Base = 0.0f; md5Range = 100.0f;
|
||||
}
|
||||
|
||||
// --- SHA-256 ---
|
||||
if (haveSHA256) {
|
||||
setProgress(State::Verifying, "Verifying SHA-256...");
|
||||
setProgress(State::Verifying, "Verifying SHA-256...", 0, 1); // percent = 0
|
||||
std::string expected = parseChecksumFile(sha256Content);
|
||||
std::string actual = computeSHA256(zipPath);
|
||||
std::string actual = computeSHA256(zipPath, sha256Base, sha256Range);
|
||||
|
||||
if (cancel_requested_) return false;
|
||||
|
||||
@@ -735,9 +771,9 @@ bool Bootstrap::verifyChecksums(const std::string& zipPath, const std::string& b
|
||||
|
||||
// --- MD5 ---
|
||||
if (haveMD5) {
|
||||
setProgress(State::Verifying, "Verifying MD5...");
|
||||
setProgress(State::Verifying, "Verifying MD5...", (double)md5Base, 100.0);
|
||||
std::string expected = parseChecksumFile(md5Content);
|
||||
std::string actual = computeMD5(zipPath);
|
||||
std::string actual = computeMD5(zipPath, md5Base, md5Range);
|
||||
|
||||
if (cancel_requested_) return false;
|
||||
|
||||
@@ -761,7 +797,7 @@ bool Bootstrap::verifyChecksums(const std::string& zipPath, const std::string& b
|
||||
DEBUG_LOGF("[Bootstrap] MD5 verified: %s\n", actual.c_str());
|
||||
}
|
||||
|
||||
setProgress(State::Verifying, "Checksums verified \xe2\x9c\x93");
|
||||
setProgress(State::Verifying, "Checksums verified \xe2\x9c\x93", 100.0, 100.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -88,12 +88,12 @@ private:
|
||||
std::string downloadSmallFile(const std::string& url);
|
||||
|
||||
/// Compute SHA-256 of a file, return lowercase hex digest.
|
||||
/// Updates progress with "Verifying SHA-256..." status during computation.
|
||||
std::string computeSHA256(const std::string& filePath);
|
||||
/// pctBase/pctRange map file progress onto a portion of the overall percent.
|
||||
std::string computeSHA256(const std::string& filePath, float pctBase = 0.0f, float pctRange = 100.0f);
|
||||
|
||||
/// Compute MD5 of a file, return lowercase hex digest.
|
||||
/// Updates progress with "Verifying MD5..." status during computation.
|
||||
std::string computeMD5(const std::string& filePath);
|
||||
/// pctBase/pctRange map file progress onto a portion of the overall percent.
|
||||
std::string computeMD5(const std::string& filePath, float pctBase = 0.0f, float pctRange = 100.0f);
|
||||
|
||||
/// Parse the first hex token from a checksum file (handles "<hash> <filename>" format).
|
||||
static std::string parseChecksumFile(const std::string& content);
|
||||
|
||||
@@ -1068,6 +1068,25 @@ void I18n::loadBuiltinEnglish()
|
||||
strings_["import_key_progress"] = "Importing %d/%d...";
|
||||
strings_["click_to_copy"] = "Click to copy";
|
||||
strings_["block_hash_copied"] = "Block hash copied";
|
||||
|
||||
// Explorer tab
|
||||
strings_["explorer"] = "Explorer";
|
||||
strings_["explorer_search"] = "Search";
|
||||
strings_["explorer_chain_stats"] = "Chain";
|
||||
strings_["explorer_mempool"] = "Mempool";
|
||||
strings_["explorer_mempool_txs"] = "Transactions";
|
||||
strings_["explorer_mempool_size"] = "Size";
|
||||
strings_["explorer_recent_blocks"] = "Recent Blocks";
|
||||
strings_["explorer_block_detail"] = "Block";
|
||||
strings_["explorer_block_height"] = "Height";
|
||||
strings_["explorer_block_hash"] = "Hash";
|
||||
strings_["explorer_block_txs"] = "Transactions";
|
||||
strings_["explorer_block_size"] = "Size";
|
||||
strings_["explorer_block_time"] = "Time";
|
||||
strings_["explorer_block_merkle"] = "Merkle Root";
|
||||
strings_["explorer_tx_outputs"] = "Outputs";
|
||||
strings_["explorer_tx_size"] = "Size";
|
||||
strings_["explorer_invalid_query"] = "Enter a block height or 64-character hash";
|
||||
}
|
||||
|
||||
const char* I18n::translate(const char* key) const
|
||||
|
||||
Reference in New Issue
Block a user