Refactor app services and stabilize refresh/UI flows
- Add refresh scheduler and network refresh service boundaries for typed refresh results, ordered RPC collectors, applicators, and price parsing. - Add daemon lifecycle and wallet security workflow helpers while preserving App-owned command RPC, decrypt, cancellation, and UI handoff behavior. - Split balance, console, mining, amount formatting, and async task logic into focused modules with expanded Phase 4 test coverage. - Fix market price loading by triggering price refresh immediately, avoiding queue-pressure drops, tracking loading/error state, and adding translations. - Polish send, explorer, peers, settings, theme/schema, and related tab UI. - Replace checked-in generated language headers with build-generated resources. - Document the cleanup audit, UI static-state guidance, and architecture updates.
This commit is contained in:
117
src/daemon/daemon_controller.cpp
Normal file
117
src/daemon/daemon_controller.cpp
Normal file
@@ -0,0 +1,117 @@
|
||||
#include "daemon_controller.h"
|
||||
#include "../config/settings.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace dragonx {
|
||||
namespace daemon {
|
||||
|
||||
DaemonController::DaemonController()
|
||||
: daemon_(std::make_unique<EmbeddedDaemon>())
|
||||
{
|
||||
}
|
||||
|
||||
DaemonController::~DaemonController() = default;
|
||||
|
||||
void DaemonController::setStateCallback(StateCallback callback)
|
||||
{
|
||||
daemon_->setStateCallback(std::move(callback));
|
||||
}
|
||||
|
||||
void DaemonController::syncSettings(const config::Settings* settings)
|
||||
{
|
||||
if (!settings) return;
|
||||
daemon_->setDebugCategories(settings->getDebugCategories());
|
||||
daemon_->setMaxConnections(settings->getMaxConnections());
|
||||
}
|
||||
|
||||
bool DaemonController::start(const config::Settings* settings)
|
||||
{
|
||||
syncSettings(settings);
|
||||
return daemon_->start();
|
||||
}
|
||||
|
||||
void DaemonController::stop(int waitMs)
|
||||
{
|
||||
daemon_->stop(waitMs);
|
||||
}
|
||||
|
||||
bool DaemonController::isRunning() const
|
||||
{
|
||||
return daemon_->isRunning();
|
||||
}
|
||||
|
||||
bool DaemonController::externalDaemonDetected() const
|
||||
{
|
||||
return daemon_->externalDaemonDetected();
|
||||
}
|
||||
|
||||
DaemonController::State DaemonController::state() const
|
||||
{
|
||||
return daemon_->getState();
|
||||
}
|
||||
|
||||
const std::string& DaemonController::lastError() const
|
||||
{
|
||||
return daemon_->getLastError();
|
||||
}
|
||||
|
||||
int DaemonController::crashCount() const
|
||||
{
|
||||
return daemon_->getCrashCount();
|
||||
}
|
||||
|
||||
int DaemonController::lastBlockHeight() const
|
||||
{
|
||||
return daemon_ ? daemon_->getLastBlockHeight() : 0;
|
||||
}
|
||||
|
||||
double DaemonController::memoryUsageMB() const
|
||||
{
|
||||
return daemon_ ? daemon_->getMemoryUsageMB() : 0.0;
|
||||
}
|
||||
|
||||
std::vector<std::string> DaemonController::recentLines(std::size_t count) const
|
||||
{
|
||||
return daemon_ ? daemon_->getRecentLines(count) : std::vector<std::string>{};
|
||||
}
|
||||
|
||||
std::string DaemonController::outputSince(std::size_t& offset) const
|
||||
{
|
||||
return daemon_ ? daemon_->getOutputSince(offset) : std::string{};
|
||||
}
|
||||
|
||||
void DaemonController::resetCrashCount()
|
||||
{
|
||||
daemon_->resetCrashCount();
|
||||
}
|
||||
|
||||
void DaemonController::setRescanOnNextStart(bool enabled)
|
||||
{
|
||||
daemon_->setRescanOnNextStart(enabled);
|
||||
}
|
||||
|
||||
bool DaemonController::rescanOnNextStart() const
|
||||
{
|
||||
return daemon_->rescanOnNextStart();
|
||||
}
|
||||
|
||||
void DaemonController::prepareLifecycleOperation(const LifecycleDecision& decision,
|
||||
const config::Settings* settings)
|
||||
{
|
||||
if (settings) syncSettings(settings);
|
||||
if (decision.resetCrashCount) resetCrashCount();
|
||||
if (decision.setRescanOnNextStart) setRescanOnNextStart(true);
|
||||
}
|
||||
|
||||
DaemonController::ShutdownDecision DaemonController::shutdownDecision(
|
||||
bool keepDaemonRunning, bool stopExternalDaemon) const
|
||||
{
|
||||
return evaluateShutdownPolicy(static_cast<bool>(daemon_),
|
||||
daemon_ && daemon_->externalDaemonDetected(),
|
||||
keepDaemonRunning,
|
||||
stopExternalDaemon);
|
||||
}
|
||||
|
||||
} // namespace daemon
|
||||
} // namespace dragonx
|
||||
225
src/daemon/daemon_controller.h
Normal file
225
src/daemon/daemon_controller.h
Normal file
@@ -0,0 +1,225 @@
|
||||
#pragma once
|
||||
|
||||
#include "embedded_daemon.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace dragonx {
|
||||
namespace config { class Settings; }
|
||||
namespace daemon {
|
||||
|
||||
class DaemonController {
|
||||
public:
|
||||
using State = EmbeddedDaemon::State;
|
||||
using StateCallback = EmbeddedDaemon::StateCallback;
|
||||
|
||||
enum class ShutdownAction {
|
||||
DisconnectOnly,
|
||||
StopDaemon
|
||||
};
|
||||
|
||||
struct ShutdownDecision {
|
||||
ShutdownAction action = ShutdownAction::DisconnectOnly;
|
||||
const char* logReason = "no embedded daemon";
|
||||
const char* status = "Disconnecting...";
|
||||
};
|
||||
|
||||
enum class LifecycleOperation {
|
||||
ManualRestart,
|
||||
Rescan,
|
||||
DeleteBlockchainData,
|
||||
BootstrapStop
|
||||
};
|
||||
|
||||
struct LifecycleDecision {
|
||||
LifecycleOperation operation = LifecycleOperation::ManualRestart;
|
||||
bool allowed = false;
|
||||
bool wasRunning = false;
|
||||
const char* taskName = "";
|
||||
const char* status = "";
|
||||
const char* warning = "";
|
||||
bool resetCrashCount = false;
|
||||
bool setRescanOnNextStart = false;
|
||||
bool disconnectRpc = false;
|
||||
int restartDelayMs = 0;
|
||||
};
|
||||
|
||||
class LifecycleTaskContext {
|
||||
public:
|
||||
virtual ~LifecycleTaskContext() = default;
|
||||
virtual bool cancelled() const = 0;
|
||||
virtual bool shuttingDown() const = 0;
|
||||
virtual void sleepForMs(int milliseconds) = 0;
|
||||
};
|
||||
|
||||
class LifecycleRuntime {
|
||||
public:
|
||||
virtual ~LifecycleRuntime() = default;
|
||||
virtual void stopDaemonWithPolicy() = 0;
|
||||
virtual bool startDaemon() = 0;
|
||||
virtual int deleteBlockchainData() = 0;
|
||||
virtual void resetOutputOffset() = 0;
|
||||
virtual void requestRpcStopAndDisconnect(const char* context, const char* reason) = 0;
|
||||
};
|
||||
|
||||
struct LifecycleExecutionResult {
|
||||
bool completed = false;
|
||||
bool cancelled = false;
|
||||
bool stopped = false;
|
||||
bool started = false;
|
||||
int deletedItems = 0;
|
||||
};
|
||||
|
||||
DaemonController();
|
||||
~DaemonController();
|
||||
|
||||
DaemonController(const DaemonController&) = delete;
|
||||
DaemonController& operator=(const DaemonController&) = delete;
|
||||
|
||||
EmbeddedDaemon* daemon() { return daemon_.get(); }
|
||||
const EmbeddedDaemon* daemon() const { return daemon_.get(); }
|
||||
|
||||
void setStateCallback(StateCallback callback);
|
||||
void syncSettings(const config::Settings* settings);
|
||||
|
||||
bool start(const config::Settings* settings);
|
||||
void stop(int waitMs);
|
||||
|
||||
bool isRunning() const;
|
||||
bool externalDaemonDetected() const;
|
||||
State state() const;
|
||||
const std::string& lastError() const;
|
||||
int crashCount() const;
|
||||
int lastBlockHeight() const;
|
||||
double memoryUsageMB() const;
|
||||
std::vector<std::string> recentLines(std::size_t count) const;
|
||||
std::string outputSince(std::size_t& offset) const;
|
||||
|
||||
void resetCrashCount();
|
||||
void setRescanOnNextStart(bool enabled);
|
||||
bool rescanOnNextStart() const;
|
||||
|
||||
static ShutdownDecision evaluateShutdownPolicy(bool hasDaemon,
|
||||
bool externalDaemonDetected,
|
||||
bool keepDaemonRunning,
|
||||
bool stopExternalDaemon) {
|
||||
if (!hasDaemon) {
|
||||
return {};
|
||||
}
|
||||
if (keepDaemonRunning) {
|
||||
return {ShutdownAction::DisconnectOnly,
|
||||
"keep_daemon_running enabled",
|
||||
"Disconnecting (daemon stays running)..."};
|
||||
}
|
||||
if (externalDaemonDetected && !stopExternalDaemon) {
|
||||
return {ShutdownAction::DisconnectOnly,
|
||||
"external daemon (not ours to stop)",
|
||||
"Disconnecting (daemon stays running)..."};
|
||||
}
|
||||
return {ShutdownAction::StopDaemon,
|
||||
"stopping managed daemon",
|
||||
"Sending stop command to daemon..."};
|
||||
}
|
||||
static LifecycleDecision evaluateLifecycleOperation(LifecycleOperation operation,
|
||||
bool usingEmbeddedDaemon,
|
||||
bool hasDaemon,
|
||||
bool daemonRunning,
|
||||
bool restartInProgress = false) {
|
||||
switch (operation) {
|
||||
case LifecycleOperation::ManualRestart:
|
||||
if (!usingEmbeddedDaemon || restartInProgress) return {};
|
||||
return {operation, true, daemonRunning, "daemon-restart", "Restarting daemon...", "",
|
||||
true, false, true, 500};
|
||||
case LifecycleOperation::Rescan:
|
||||
if (!usingEmbeddedDaemon || !hasDaemon) {
|
||||
return {operation, false, daemonRunning, "", "",
|
||||
"Rescan requires embedded daemon. Restart your daemon with -rescan manually."};
|
||||
}
|
||||
return {operation, true, daemonRunning, "rescan-blockchain", "Starting rescan...", "",
|
||||
false, true, false, 3000};
|
||||
case LifecycleOperation::DeleteBlockchainData:
|
||||
if (!usingEmbeddedDaemon || !hasDaemon) {
|
||||
return {operation, false, daemonRunning, "", "",
|
||||
"Delete blockchain requires embedded daemon. Stop your daemon manually and delete the data directory."};
|
||||
}
|
||||
return {operation, true, daemonRunning, "delete-blockchain-data", "Deleting blockchain data...", "",
|
||||
false, false, false, 3000};
|
||||
case LifecycleOperation::BootstrapStop:
|
||||
return {operation, true, daemonRunning, "bootstrap-stop-daemon", "Stopping daemon for bootstrap...", "",
|
||||
false, false, true, 0};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
void prepareLifecycleOperation(const LifecycleDecision& decision,
|
||||
const config::Settings* settings = nullptr);
|
||||
static inline LifecycleExecutionResult executeLifecycleOperation(const LifecycleDecision& decision,
|
||||
LifecycleRuntime& runtime,
|
||||
LifecycleTaskContext& task)
|
||||
{
|
||||
LifecycleExecutionResult result;
|
||||
if (!decision.allowed) return result;
|
||||
|
||||
auto waitForDelay = [&]() {
|
||||
int waitTicks = std::max(0, decision.restartDelayMs / 100);
|
||||
for (int i = 0; i < waitTicks && !task.cancelled() && !task.shuttingDown(); ++i) {
|
||||
task.sleepForMs(100);
|
||||
}
|
||||
};
|
||||
|
||||
auto cancelled = [&]() {
|
||||
result.cancelled = task.cancelled() || task.shuttingDown();
|
||||
return result.cancelled;
|
||||
};
|
||||
|
||||
switch (decision.operation) {
|
||||
case LifecycleOperation::BootstrapStop:
|
||||
if (decision.wasRunning) {
|
||||
runtime.requestRpcStopAndDisconnect("Bootstrap daemon stop", "Bootstrap");
|
||||
result.stopped = true;
|
||||
}
|
||||
result.completed = true;
|
||||
return result;
|
||||
case LifecycleOperation::ManualRestart:
|
||||
if (decision.wasRunning) {
|
||||
runtime.stopDaemonWithPolicy();
|
||||
result.stopped = true;
|
||||
}
|
||||
break;
|
||||
case LifecycleOperation::Rescan:
|
||||
case LifecycleOperation::DeleteBlockchainData:
|
||||
runtime.stopDaemonWithPolicy();
|
||||
result.stopped = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (cancelled()) return result;
|
||||
waitForDelay();
|
||||
if (cancelled()) return result;
|
||||
|
||||
if (decision.operation == LifecycleOperation::DeleteBlockchainData) {
|
||||
result.deletedItems = runtime.deleteBlockchainData();
|
||||
if (cancelled()) return result;
|
||||
}
|
||||
|
||||
if (decision.operation == LifecycleOperation::Rescan ||
|
||||
decision.operation == LifecycleOperation::DeleteBlockchainData) {
|
||||
runtime.resetOutputOffset();
|
||||
}
|
||||
|
||||
result.started = runtime.startDaemon();
|
||||
result.completed = !cancelled();
|
||||
return result;
|
||||
}
|
||||
ShutdownDecision shutdownDecision(bool keepDaemonRunning,
|
||||
bool stopExternalDaemon) const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<EmbeddedDaemon> daemon_;
|
||||
};
|
||||
|
||||
} // namespace daemon
|
||||
} // namespace dragonx
|
||||
93
src/daemon/lifecycle_adapters.cpp
Normal file
93
src/daemon/lifecycle_adapters.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "lifecycle_adapters.h"
|
||||
#include "../util/logger.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
namespace dragonx {
|
||||
namespace daemon {
|
||||
|
||||
AsyncLifecycleTaskContext::AsyncLifecycleTaskContext(
|
||||
const util::AsyncTaskManager::Token& token,
|
||||
const std::atomic<bool>& shuttingDown)
|
||||
: token_(token), shuttingDown_(shuttingDown)
|
||||
{
|
||||
}
|
||||
|
||||
bool AsyncLifecycleTaskContext::cancelled() const
|
||||
{
|
||||
return token_.cancelled();
|
||||
}
|
||||
|
||||
bool AsyncLifecycleTaskContext::shuttingDown() const
|
||||
{
|
||||
return shuttingDown_.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void AsyncLifecycleTaskContext::sleepForMs(int milliseconds)
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(std::max(0, milliseconds)));
|
||||
}
|
||||
|
||||
bool ImmediateLifecycleTaskContext::cancelled() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ImmediateLifecycleTaskContext::shuttingDown() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void ImmediateLifecycleTaskContext::sleepForMs(int)
|
||||
{
|
||||
}
|
||||
|
||||
int BlockchainDataCleaner::removeBlockchainData(const std::filesystem::path& dataDir)
|
||||
{
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
static constexpr std::array<const char*, 4> directories = {
|
||||
"blocks", "chainstate", "database", "notarizations"
|
||||
};
|
||||
static constexpr std::array<const char*, 5> files = {
|
||||
"peers.dat", "fee_estimates.dat", "banlist.dat", "db.log", ".lock"
|
||||
};
|
||||
|
||||
int removed = 0;
|
||||
for (const char* directoryName : directories) {
|
||||
fs::path path = dataDir / directoryName;
|
||||
std::error_code existsError;
|
||||
if (!fs::exists(path, existsError)) continue;
|
||||
|
||||
std::error_code removeError;
|
||||
auto count = fs::remove_all(path, removeError);
|
||||
if (!removeError) {
|
||||
removed += static_cast<int>(count);
|
||||
DEBUG_LOGF("[DaemonLifecycle] Removed %s (%d entries)\n",
|
||||
directoryName, static_cast<int>(count));
|
||||
} else {
|
||||
DEBUG_LOGF("[DaemonLifecycle] Failed to remove %s: %s\n",
|
||||
directoryName, removeError.message().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
for (const char* fileName : files) {
|
||||
fs::path path = dataDir / fileName;
|
||||
std::error_code removeError;
|
||||
if (fs::remove(path, removeError)) {
|
||||
++removed;
|
||||
DEBUG_LOGF("[DaemonLifecycle] Removed %s\n", fileName);
|
||||
} else if (removeError) {
|
||||
DEBUG_LOGF("[DaemonLifecycle] Failed to remove %s: %s\n",
|
||||
fileName, removeError.message().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
} // namespace daemon
|
||||
} // namespace dragonx
|
||||
39
src/daemon/lifecycle_adapters.h
Normal file
39
src/daemon/lifecycle_adapters.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include "daemon_controller.h"
|
||||
#include "../util/async_task_manager.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <filesystem>
|
||||
|
||||
namespace dragonx {
|
||||
namespace daemon {
|
||||
|
||||
class AsyncLifecycleTaskContext final : public DaemonController::LifecycleTaskContext {
|
||||
public:
|
||||
AsyncLifecycleTaskContext(const util::AsyncTaskManager::Token& token,
|
||||
const std::atomic<bool>& shuttingDown);
|
||||
|
||||
bool cancelled() const override;
|
||||
bool shuttingDown() const override;
|
||||
void sleepForMs(int milliseconds) override;
|
||||
|
||||
private:
|
||||
const util::AsyncTaskManager::Token& token_;
|
||||
const std::atomic<bool>& shuttingDown_;
|
||||
};
|
||||
|
||||
class ImmediateLifecycleTaskContext final : public DaemonController::LifecycleTaskContext {
|
||||
public:
|
||||
bool cancelled() const override;
|
||||
bool shuttingDown() const override;
|
||||
void sleepForMs(int milliseconds) override;
|
||||
};
|
||||
|
||||
class BlockchainDataCleaner final {
|
||||
public:
|
||||
static int removeBlockchainData(const std::filesystem::path& dataDir);
|
||||
};
|
||||
|
||||
} // namespace daemon
|
||||
} // namespace dragonx
|
||||
Reference in New Issue
Block a user