The installed-daemon lookup built the binary path from the Windows
resource key "dragonxd.exe", which never exists on Linux or macOS, so
the installed full node was never found there. The version scan also
read the whole binary, now around 200 MB, into memory on the UI thread.
- A new DAEMON_BINARY_NAME gives the daemon's file name on this platform
("dragonxd" on POSIX, "dragonxd.exe" on Windows); the old resource
constant stays only as the embedded-table key. The installed daemon is
now found on POSIX.
- The version-stamp scan streams the file in 4 MiB chunks with bounded
memory, carrying bytes across each boundary so a stamp straddling it is
neither cut short nor missed, and stops at the first match. The result
is cached by path, size and mtime, so only a changed binary is
rescanned.
- Settings stats the binary on the UI thread but runs the streamed scan
on a worker, guarded so a new scan is not started while one is still
running (the future blocks in its destructor), and the version fills
in when it lands.
Tests: the POSIX path and binary name, a version stamp in a small file,
a stamp straddling the 4 MiB chunk boundary, and a file with no stamp.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
176 lines
8.7 KiB
C++
176 lines
8.7 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "provision_state.h"
|
|
|
|
namespace dragonx {
|
|
namespace resources {
|
|
|
|
// Embedded resource data (generated at build time)
|
|
struct EmbeddedResource {
|
|
const uint8_t* data;
|
|
size_t size;
|
|
const char* filename;
|
|
};
|
|
|
|
// Check if embedded resources are available
|
|
bool hasEmbeddedResources();
|
|
|
|
// Get embedded resource by name
|
|
// Returns nullptr if not found or not embedded
|
|
const EmbeddedResource* getEmbeddedResource(const std::string& name);
|
|
|
|
using CancelFn = std::function<bool()>; // polled between 4 MB chunks; true = stop now
|
|
|
|
// Suffix of our own temp files in the daemon dir ("<name>.partial"). NOT ".tmp": the in-app miner/node
|
|
// updaters stage "<name>.tmp" in the same directory and must never have it deleted under them.
|
|
inline constexpr const char* kPartialFileSuffix = ".partial";
|
|
|
|
// Extract all embedded resources to the daemon directory. Serialised with every other writer of that
|
|
// directory by an internal mutex; atomic per file (temp + flush + rename), so a destination is either the old
|
|
// complete file or the new one. `progress` (optional) receives bytes written; `cancelled` (optional) aborts
|
|
// between chunks leaving no partial destination. Returns true if every needed file was written (false also on
|
|
// cancel). Safe on any thread.
|
|
bool extractEmbeddedResources(ProvisionProgress* progress = nullptr, const CancelFn& cancelled = {});
|
|
|
|
// Does `destPath` need (re)writing? Missing -> yes. Present: only when `refreshOnSizeMismatch` and the size
|
|
// differs. embeddedSize == 0 -> never. (Pure apart from the stat; exposed for tests.)
|
|
bool resourceNeedsWrite(std::uint64_t embeddedSize, bool refreshOnSizeMismatch, const std::string& destPath);
|
|
// The bundled miner is placed only when MISSING: a present xmrig is never replaced by the bundled copy, even when
|
|
// its size differs, so an "Update miner..." install survives a wallet restart or upgrade.
|
|
inline constexpr bool kXmrigRefreshOnSizeMismatch = false; // the in-app updater owns the miner once placed
|
|
// Write `data` to `destPath` via `destPath + kPartialFileSuffix`, flush it to disk, then move it into place
|
|
// (POSIX rename; Windows MoveFileEx with retry and move-aside of a running .exe to ".old"). POSIX: chmod 0755
|
|
// first when `executable`. Returns false (temp removed, original kept) on any failure or on cancel.
|
|
// Takes no lock itself (extractEmbeddedResources / copyBundledFiles / the on-demand extractors do).
|
|
bool writeFileAtomic(const std::string& destPath, const std::uint8_t* data, std::size_t size, bool executable,
|
|
ProvisionProgress* progress = nullptr, const CancelFn& cancelled = {});
|
|
// Same contract, streaming from a source file in 4 MB chunks; keeps the source's permissions.
|
|
bool copyFileAtomic(const std::string& srcPath, const std::string& destPath,
|
|
ProvisionProgress* progress = nullptr, const CancelFn& cancelled = {});
|
|
|
|
// Node files shipped NEXT TO the executable (zip / AppImage / .app; no embedded_data.h).
|
|
struct BundleCopyItem { std::string name, src, dst; };
|
|
std::vector<std::string> bundleSearchDirs(); // macOS: .app Contents/Resources first; then exe dir
|
|
const std::vector<std::string>& saplingParamFileNames(); // sapling-spend.params, sapling-output.params
|
|
const std::vector<std::string>& nodeBundleFileNames(); // params + asmap.dat + dragonxd + dragonx-cli + dragonx-tx
|
|
// First search dir holding a name wins; names with no source are skipped (optional); names whose destination
|
|
// exists are skipped unless replaceExisting. Stat-only.
|
|
std::vector<BundleCopyItem> planBundleCopy(const std::vector<std::string>& searchDirs, const std::string& destDir,
|
|
const std::vector<std::string>& names, bool replaceExisting);
|
|
struct BundleCopyResult { bool ok = true; int copied = 0; std::string firstFailure; };
|
|
// Executes planBundleCopy under the provisioning lock (checks the plan lock-free first: the common
|
|
// "nothing to copy" call never touches the lock).
|
|
BundleCopyResult copyBundledFiles(const std::vector<std::string>& searchDirs, const std::string& destDir,
|
|
const std::vector<std::string>& names, bool replaceExisting,
|
|
ProvisionProgress* progress = nullptr, const CancelFn& cancelled = {});
|
|
|
|
// Check if params need to be extracted (not already present)
|
|
bool needsParamsExtraction();
|
|
|
|
// Get the params directory path
|
|
std::string getParamsDirectory();
|
|
|
|
// --- Daemon binary management (Settings → daemon binary panel) ------------------------------
|
|
// Info about the dragonxd binary currently installed in the dragonx/ extraction directory.
|
|
struct DaemonBinaryInfo {
|
|
bool exists = false;
|
|
std::string path;
|
|
std::uintmax_t size = 0;
|
|
std::string version; // scanned from the binary ("vX.Y.Z-<commit>"), empty if not found
|
|
std::int64_t modifiedEpoch = 0; // last-write time as unix epoch seconds, 0 if unknown
|
|
};
|
|
|
|
// Info about the dragonxd binary bundled inside this wallet build.
|
|
struct BundledDaemonInfo {
|
|
bool available = false; // a daemon resource is embedded in this build
|
|
std::uintmax_t size = 0;
|
|
std::string version;
|
|
};
|
|
|
|
// Read + scan the installed dragonxd (reads the file; call off the UI thread or cache the result).
|
|
// scanVersion=false: stat only (exists / size / modifiedEpoch), no 16 MB read.
|
|
DaemonBinaryInfo getInstalledDaemonInfo(bool scanVersion = true);
|
|
// Info about the bundled daemon (scans the embedded bytes once, cached).
|
|
BundledDaemonInfo getBundledDaemonInfo();
|
|
// Force-overwrite the installed dragonx binaries (dragonxd/cli/tx) with the bundled ones. The
|
|
// caller should stop the daemon first. Returns true if all present resources were written.
|
|
bool reextractBundledDaemon();
|
|
|
|
// Ensure the embedded offline wallet-rebuild recovery helper is extracted to the daemon dir, and
|
|
// return its path ("" if not embedded in this build or extraction failed). Idempotent — extracts only
|
|
// when missing. Unlike the first-run extractEmbeddedResources() (gated on needsParamsExtraction()),
|
|
// this runs on demand so recovery works from a self-contained exe on ANY run, not just the first.
|
|
std::string ensureWalletRebuildHelperExtracted();
|
|
|
|
// Resource names
|
|
constexpr const char* RESOURCE_SAPLING_SPEND = "sapling-spend.params";
|
|
constexpr const char* RESOURCE_SAPLING_OUTPUT = "sapling-output.params";
|
|
constexpr const char* RESOURCE_ASMAP = "asmap.dat";
|
|
constexpr const char* RESOURCE_DRAGONXD = "dragonxd.exe"; // embedded-resource table key (Windows builds only)
|
|
// The daemon binary's file name on THIS platform. Use it to build paths; RESOURCE_DRAGONXD is only the table key.
|
|
#ifdef _WIN32
|
|
constexpr const char* DAEMON_BINARY_NAME = "dragonxd.exe";
|
|
#else
|
|
constexpr const char* DAEMON_BINARY_NAME = "dragonxd";
|
|
#endif
|
|
constexpr const char* RESOURCE_DRAGONX_CLI = "dragonx-cli.exe";
|
|
constexpr const char* RESOURCE_DRAGONX_TX = "dragonx-tx.exe";
|
|
constexpr const char* RESOURCE_DRAGONX_WALLET_REBUILD = "dragonx-wallet-rebuild.exe";
|
|
constexpr const char* RESOURCE_XMRIG = "xmrig.exe";
|
|
constexpr const char* RESOURCE_DARK_GRADIENT = "dark_gradient.png";
|
|
constexpr const char* RESOURCE_LOGO = "logo_ObsidianDragon_dark.png";
|
|
|
|
// Embedded theme overlay info (for iterating bundled themes)
|
|
struct EmbeddedTheme {
|
|
const uint8_t* data;
|
|
size_t size;
|
|
const char* filename; // e.g. "dark.toml"
|
|
};
|
|
|
|
// Get array of embedded overlay themes (nullptr-terminated)
|
|
const EmbeddedTheme* getEmbeddedThemes();
|
|
|
|
// Extract bundled overlay themes to the given directory
|
|
// Returns number of themes extracted
|
|
int extractBundledThemes(const std::string& destDir);
|
|
|
|
// Write `size` bytes to `path` unless the file already holds exactly those bytes.
|
|
// Returns 1 = written, 0 = already identical (untouched), -1 = write failed.
|
|
int writeFileIfDifferent(const std::string& path, const uint8_t* data, size_t size);
|
|
|
|
// Update stale bundled theme files in the given directory.
|
|
// Compares on-disk file size to embedded size; overwrites on mismatch.
|
|
// Returns number of themes updated.
|
|
int updateBundledThemes(const std::string& dir);
|
|
|
|
// Check if daemon needs to be extracted
|
|
bool needsDaemonExtraction();
|
|
|
|
// Get daemon binary directory (<exe_dir>/dragonx/)
|
|
std::string getDaemonDirectory();
|
|
|
|
// Check if daemon is available (embedded or in app directory)
|
|
bool hasDaemonAvailable();
|
|
|
|
// Get daemon executable path (extracts if needed)
|
|
std::string getDaemonPath();
|
|
|
|
// Check if xmrig is available (embedded or in app directory)
|
|
bool hasXmrigAvailable();
|
|
|
|
// Force re-extract xmrig binary from embedded resources
|
|
// (bypasses exists-check; use when Defender deletes the file)
|
|
bool forceExtractXmrig();
|
|
|
|
// Get xmrig executable path (extracts if needed)
|
|
std::string getXmrigPath();
|
|
|
|
} // namespace resources
|
|
} // namespace dragonx
|