Skip to content

Commit a18e63e

Browse files
hebastojohnny9promag
committed
qml: Add node lifecycle model
Initialize and shut down the node on a worker thread. Expose lifecycle state and block progress through NodeModel, and stop cleanly when the window closes. Co-authored-by: johnny9 <johnny9dev@pm.me> Co-authored-by: João Barbosa <joao.paulo.barbosa@gmail.com>
1 parent ff1e0b9 commit a18e63e

11 files changed

Lines changed: 435 additions & 20 deletions

File tree

src/init/bitcoin-qt.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@
1616

1717
namespace init {
1818
namespace {
19-
const char* EXE_NAME = "bitcoin-qt";
19+
#ifndef BITCOIN_GUI_EXECUTABLE_NAME
20+
#define BITCOIN_GUI_EXECUTABLE_NAME "bitcoin-qt"
21+
#endif
22+
const char* EXE_NAME = BITCOIN_GUI_EXECUTABLE_NAME;
2023

2124
class BitcoinQtInit : public interfaces::Init
2225
{

src/qml/CMakeLists.txt

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,17 @@ unset(warn_flags)
1717
add_library(bitcoinqml STATIC
1818
bitcoin.cpp
1919
bitcoin.h
20+
initexecutor.cpp
21+
initexecutor.h
22+
models/nodemodel.cpp
23+
models/nodemodel.h
2024
${BITCOIN_QML_QRC}
2125
)
2226

2327
target_compile_definitions(bitcoinqml
2428
PUBLIC
2529
QT_NO_KEYWORDS
30+
QT_NO_CONTEXTLESS_CONNECT
2631
QT_USE_QSTRINGBUILDER
2732
)
2833

@@ -37,19 +42,31 @@ if(ENABLE_TEST_AUTOMATION)
3742
endif()
3843

3944
target_link_libraries(bitcoinqml
45+
PRIVATE
46+
bitcoin_node
4047
PUBLIC
4148
Qt6::Qml
4249
Qt6::Quick
4350
Qt6::QuickControls2
4451
)
4552

46-
add_executable(bitcoin-qml WIN32 main.cpp)
53+
add_executable(bitcoin-qml WIN32
54+
main.cpp
55+
../init/bitcoin-qt.cpp
56+
)
4757
add_windows_application_manifest(bitcoin-qml)
4858

59+
target_compile_definitions(bitcoin-qml
60+
PRIVATE
61+
BITCOIN_GUI_EXECUTABLE_NAME="bitcoin-qml"
62+
)
63+
4964
target_link_libraries(bitcoin-qml
5065
PRIVATE
5166
core_interface
5267
bitcoinqml
68+
bitcoin_node
69+
$<TARGET_NAME_IF_EXISTS:bitcoin_wallet>
5370
)
5471

5572
qt6_import_qml_plugins(bitcoin-qml)

src/qml/bitcoin.cpp

Lines changed: 93 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,56 +4,131 @@
44

55
#include <qml/bitcoin.h>
66

7+
#include <clientversion.h>
8+
#include <common/args.h>
9+
#include <common/init.h>
10+
#include <common/system.h>
11+
#include <init.h>
12+
#include <interfaces/init.h>
13+
#include <interfaces/node.h>
14+
#include <node/interface_ui.h>
15+
#include <noui.h>
16+
#include <qml/initexecutor.h>
17+
#include <qml/models/nodemodel.h>
718
#ifdef ENABLE_TEST_AUTOMATION
819
#include <qml/test/testbridge.h>
920
#endif
21+
#include <util/threadnames.h>
22+
#include <util/translation.h>
1023

1124
#include <QGuiApplication>
1225
#include <QQmlApplicationEngine>
26+
#include <QQmlContext>
1327
#include <QQuickStyle>
1428
#include <QStringLiteral>
29+
#include <QTimer>
1530
#include <QUrl>
1631

1732
#include <cstdlib>
33+
#include <iostream>
1834
#include <memory>
19-
20-
#ifdef ENABLE_TEST_AUTOMATION
21-
namespace {
22-
QString TestAutomationSocketPath(int argc, char* argv[])
23-
{
24-
const QString prefix{QStringLiteral("-test-automation=")};
25-
for (int i = 1; i < argc; ++i) {
26-
const QString argument{QString::fromLocal8Bit(argv[i])};
27-
if (argument.startsWith(prefix)) {
28-
return argument.sliced(prefix.size());
29-
}
30-
}
31-
return {};
32-
}
33-
} // namespace
34-
#endif
35+
#include <string>
36+
#include <tuple>
3537

3638
int QmlGuiMain(int argc, char* argv[])
3739
{
3840
Q_INIT_RESOURCE(bitcoin_qml);
3941

42+
#ifdef WIN32
43+
common::WinCmdLineArgs win_args;
44+
std::tie(argc, argv) = win_args.get();
45+
#endif
46+
4047
QQuickStyle::setStyle(QStringLiteral("Basic"));
4148

4249
QGuiApplication app(argc, argv);
4350
QGuiApplication::setApplicationDisplayName(QGuiApplication::translate("bitcoin-core", "Bitcoin Core"));
51+
QGuiApplication::setQuitOnLastWindowClosed(false);
52+
53+
SetupEnvironment();
54+
util::ThreadSetInternalName("main");
55+
noui_connect();
56+
57+
std::unique_ptr<interfaces::Init> init{interfaces::MakeGuiInit(argc, argv)};
58+
59+
SetupServerArgs(gArgs, init->canListenIpc());
60+
#ifdef ENABLE_TEST_AUTOMATION
61+
gArgs.AddArg("-test-automation=<path>", "Enable test automation bridge on the given local socket path", ArgsManager::ALLOW_ANY, OptionsCategory::GUI);
62+
#endif
63+
std::string error;
64+
if (!gArgs.ParseParameters(argc, argv, error)) {
65+
InitError(Untranslated(strprintf("Error parsing command line arguments: %s", error)));
66+
return EXIT_FAILURE;
67+
}
68+
69+
if (HelpRequested(gArgs) || gArgs.GetBoolArg("-version", false)) {
70+
std::cout << init->exeName() << " " << FormatFullVersion() << "\n";
71+
if (!gArgs.GetBoolArg("-version", false)) {
72+
std::cout << "\n" << gArgs.GetHelpMessage();
73+
}
74+
return EXIT_SUCCESS;
75+
}
76+
77+
for (int i = 1; i < argc; ++i) {
78+
if (!IsSwitchChar(argv[i][0])) {
79+
InitError(Untranslated(strprintf("Command line contains unexpected token '%s', see bitcoin-qml -h for a list of options.", argv[i])));
80+
return EXIT_FAILURE;
81+
}
82+
}
83+
84+
if (auto config_error{common::InitConfig(gArgs)}) {
85+
InitError(config_error->message, config_error->details);
86+
return EXIT_FAILURE;
87+
}
88+
89+
gArgs.SoftSetBoolArg("-printtoconsole", false);
90+
InitLogging(gArgs);
91+
InitParameterInteraction(gArgs);
92+
93+
std::unique_ptr<interfaces::Node> node{init->makeNode()};
94+
if (!node->baseInitialize()) {
95+
return EXIT_FAILURE;
96+
}
97+
98+
qRegisterMetaType<interfaces::BlockAndHeaderTipInfo>("interfaces::BlockAndHeaderTipInfo");
99+
100+
NodeModel node_model{*node};
101+
QmlInitExecutor init_executor{*node};
102+
103+
QObject::connect(&node_model, &NodeModel::requestedInitialize, &init_executor, &QmlInitExecutor::initialize);
104+
QObject::connect(&node_model, &NodeModel::requestedShutdown, &init_executor, &QmlInitExecutor::shutdown);
105+
QObject::connect(&init_executor, &QmlInitExecutor::initializeResult, &node_model, &NodeModel::initializeResult);
106+
QObject::connect(&init_executor, &QmlInitExecutor::shutdownResult, &node_model, &NodeModel::shutdownResult);
107+
QObject::connect(&init_executor, &QmlInitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException);
108+
QObject::connect(&node_model, &NodeModel::shutdownComplete, &app, [&app, &node] {
109+
app.exit(node->getExitStatus());
110+
});
111+
QObject::connect(&app, &QGuiApplication::lastWindowClosed, &node_model, &NodeModel::requestShutdown);
44112

45113
QQmlApplicationEngine engine;
114+
engine.rootContext()->setContextProperty(QStringLiteral("nodeModel"), &node_model);
46115
engine.load(QUrl{QStringLiteral("qrc:///qml/pages/MainWindow.qml")});
47116
if (engine.rootObjects().isEmpty()) {
117+
node->startShutdown();
118+
node->appShutdown();
48119
return EXIT_FAILURE;
49120
}
50121

51122
#ifdef ENABLE_TEST_AUTOMATION
52123
std::unique_ptr<TestBridge> test_bridge;
53-
if (const QString socket_path{TestAutomationSocketPath(argc, argv)}; !socket_path.isEmpty()) {
54-
test_bridge = std::make_unique<TestBridge>(engine, socket_path);
124+
if (gArgs.IsArgSet("-test-automation")) {
125+
const QString socket_path{QString::fromStdString(gArgs.GetArg("-test-automation", ""))};
126+
if (!socket_path.isEmpty()) {
127+
test_bridge = std::make_unique<TestBridge>(engine, socket_path);
128+
}
55129
}
56130
#endif
57131

132+
QTimer::singleShot(0, &node_model, &NodeModel::start);
58133
return app.exec();
59134
}

src/qml/initexecutor.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright (c) 2014-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <qml/initexecutor.h>
6+
7+
#include <interfaces/node.h>
8+
#include <util/exception.h>
9+
#include <util/threadnames.h>
10+
11+
#include <QMetaObject>
12+
#include <QString>
13+
14+
QmlInitExecutor::QmlInitExecutor(interfaces::Node& node)
15+
: m_node{node}
16+
{
17+
m_context.moveToThread(&m_thread);
18+
m_thread.start();
19+
}
20+
21+
QmlInitExecutor::~QmlInitExecutor()
22+
{
23+
m_thread.quit();
24+
m_thread.wait();
25+
}
26+
27+
void QmlInitExecutor::handleRunawayException(const std::exception* exception)
28+
{
29+
PrintExceptionContinue(exception, "Runaway exception");
30+
Q_EMIT runawayException(exception ? QString::fromUtf8(exception->what()) : tr("Unknown exception"));
31+
}
32+
33+
void QmlInitExecutor::initialize()
34+
{
35+
QMetaObject::invokeMethod(&m_context, [this] {
36+
try {
37+
util::ThreadRename("qml-init");
38+
interfaces::BlockAndHeaderTipInfo tip_info;
39+
const bool success{m_node.appInitMain(&tip_info)};
40+
Q_EMIT initializeResult(success, tip_info);
41+
} catch (const std::exception& exception) {
42+
handleRunawayException(&exception);
43+
} catch (...) {
44+
handleRunawayException(nullptr);
45+
}
46+
});
47+
}
48+
49+
void QmlInitExecutor::shutdown()
50+
{
51+
QMetaObject::invokeMethod(&m_context, [this] {
52+
try {
53+
m_node.appShutdown();
54+
Q_EMIT shutdownResult();
55+
} catch (const std::exception& exception) {
56+
handleRunawayException(&exception);
57+
} catch (...) {
58+
handleRunawayException(nullptr);
59+
}
60+
});
61+
}

src/qml/initexecutor.h

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) 2014-present The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_QML_INITEXECUTOR_H
6+
#define BITCOIN_QML_INITEXECUTOR_H
7+
8+
#include <interfaces/node.h>
9+
10+
#include <exception>
11+
12+
#include <QObject>
13+
#include <QThread>
14+
15+
QT_BEGIN_NAMESPACE
16+
class QString;
17+
QT_END_NAMESPACE
18+
19+
/** Run node initialization and shutdown without blocking the GUI thread. */
20+
class QmlInitExecutor : public QObject
21+
{
22+
Q_OBJECT
23+
24+
public:
25+
explicit QmlInitExecutor(interfaces::Node& node);
26+
~QmlInitExecutor() override;
27+
28+
public Q_SLOTS:
29+
void initialize();
30+
void shutdown();
31+
32+
Q_SIGNALS:
33+
void initializeResult(bool success, interfaces::BlockAndHeaderTipInfo tip_info);
34+
void shutdownResult();
35+
void runawayException(const QString& message);
36+
37+
private:
38+
void handleRunawayException(const std::exception* exception);
39+
40+
interfaces::Node& m_node;
41+
QObject m_context;
42+
QThread m_thread;
43+
};
44+
45+
#endif // BITCOIN_QML_INITEXECUTOR_H

0 commit comments

Comments
 (0)