Skip to content

Commit d44eb7f

Browse files
committed
qml: Introduce Generate Snapshot page and wiring
This commit adds the Snapshot Gen files to the Connection Settings page. It wires the generate snapshot functionality through the node model and expands the snapshot qml worker class.
1 parent feb787f commit d44eb7f

10 files changed

+750
-6
lines changed

src/Makefile.qt.include

+2
Original file line numberDiff line numberDiff line change
@@ -382,6 +382,7 @@ QML_RES_QML = \
382382
qml/components/NetworkIndicator.qml \
383383
qml/components/ProxySettings.qml \
384384
qml/components/Separator.qml \
385+
qml/components/SnapshotGenSettings.qml \
385386
qml/components/SnapshotSettings.qml \
386387
qml/components/StorageLocations.qml \
387388
qml/components/StorageOptions.qml \
@@ -439,6 +440,7 @@ QML_RES_QML = \
439440
qml/pages/settings/SettingsDisplay.qml \
440441
qml/pages/settings/SettingsProxy.qml \
441442
qml/pages/settings/SettingsSnapshot.qml \
443+
qml/pages/settings/SettingsSnapshotGen.qml \
442444
qml/pages/settings/SettingsStorage.qml \
443445
qml/pages/settings/SettingsTheme.qml \
444446
qml/pages/wallet/CreateBackup.qml \

src/qml/bitcoin_qml.qrc

+2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
<file>components/ProxySettings.qml</file>
1616
<file>components/StorageLocations.qml</file>
1717
<file>components/Separator.qml</file>
18+
<file>components/SnapshotGenSettings.qml</file>
1819
<file>components/SnapshotSettings.qml</file>
1920
<file>components/StorageOptions.qml</file>
2021
<file>components/StorageSettings.qml</file>
@@ -70,6 +71,7 @@
7071
<file>pages/settings/SettingsDeveloper.qml</file>
7172
<file>pages/settings/SettingsDisplay.qml</file>
7273
<file>pages/settings/SettingsProxy.qml</file>
74+
<file>pages/settings/SettingsSnapshotGen.qml</file>
7375
<file>pages/settings/SettingsSnapshot.qml</file>
7476
<file>pages/settings/SettingsStorage.qml</file>
7577
<file>pages/settings/SettingsTheme.qml</file>

src/qml/components/ConnectionSettings.qml

+30
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,40 @@ ColumnLayout {
1111
id: root
1212
signal next
1313
signal gotoSnapshot
14+
signal gotoGenerateSnapshot
1415
property bool snapshotImportCompleted: chainModel.isSnapshotActive
1516
property bool onboarding: false
17+
property bool generateSnapshot: false
18+
property bool isSnapshotGenerated: nodeModel.isSnapshotGenerated
1619

1720
spacing: 4
21+
Setting {
22+
id: gotoGenerateSnapshot
23+
visible: !root.onboarding
24+
Layout.fillWidth: true
25+
header: qsTr("Generate snapshot")
26+
description: qsTr("Speed up the setup of other nodes")
27+
actionItem: Item {
28+
width: 26
29+
height: 26
30+
CaretRightIcon {
31+
anchors.centerIn: parent
32+
color: gotoGenerateSnapshot.stateColor
33+
}
34+
}
35+
onClicked: {
36+
if (!nodeModel.isSnapshotFileExists()) {
37+
root.generateSnapshot = true
38+
root.gotoGenerateSnapshot()
39+
} else {
40+
root.gotoGenerateSnapshot()
41+
}
42+
}
43+
}
44+
Separator {
45+
visible: !root.onboarding
46+
Layout.fillWidth: true
47+
}
1848
Setting {
1949
id: gotoSnapshot
2050
visible: !root.onboarding
+208
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
// Copyright (c) 2023-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+
import QtQuick 2.15
6+
import QtQuick.Controls 2.15
7+
import QtQuick.Layouts 1.15
8+
import QtQuick.Dialogs 1.3
9+
10+
import "../controls"
11+
import "../controls/utils.js" as Utils
12+
13+
14+
ColumnLayout {
15+
id: columnLayout
16+
signal back
17+
property bool onboarding: false
18+
property bool generateSnapshot: false
19+
property string selectedFile: ""
20+
property bool snapshotGenerating: nodeModel.snapshotGenerating
21+
property bool isPruned: optionsModel.prune
22+
property bool isIBDCompleted: nodeModel.isIBDCompleted
23+
property bool isSnapshotGenerated: nodeModel.isSnapshotGenerated
24+
property var snapshotInfo: isSnapshotGenerated ? chainModel.getSnapshotInfo() : ({})
25+
property bool isRewinding: nodeModel.isRewinding
26+
27+
28+
width: Math.min(parent.width, 450)
29+
anchors.horizontalCenter: parent.horizontalCenter
30+
31+
StackLayout {
32+
id: genSettingsStack
33+
currentIndex: snapshotGenerating ? 1 : isSnapshotGenerated ? 2 : generateSnapshot ? 0 : onboarding ? 0 : 0
34+
35+
ColumnLayout {
36+
// index: 0
37+
Layout.alignment: Qt.AlignHCenter
38+
Layout.preferredWidth: Math.min(parent.width, 450)
39+
40+
Image {
41+
Layout.alignment: Qt.AlignCenter
42+
source: "image://images/circle-file"
43+
sourceSize.width: 200
44+
sourceSize.height: 200
45+
}
46+
47+
Header {
48+
Layout.fillWidth: true
49+
Layout.topMargin: 20
50+
headerBold: true
51+
header: qsTr("Generate snapshot")
52+
descriptionBold: false
53+
descriptionColor: Theme.color.neutral6
54+
descriptionSize: 17
55+
descriptionLineHeight: 1.1
56+
description: isPruned ? qsTr("A snapshot captures the current state of bitcoin transactions on the network. It can be imported into other bitcoin nodes to speed up the initial setup.\n\nCannot generate snapshot when pruning is enabled") : isIBDCompleted ? qsTr("A snapshot captures the current state of bitcoin transactions on the network. It can be imported into other bitcoin nodes to speed up the initial setup.\n\nYou can generate a snapshot of the current chain state.") : qsTr("A snapshot captures the current state of bitcoin transactions on the network. It can be imported into other bitcoin nodes to speed up the initial setup.\n\nSnapshot generation is available once the initial block download is complete.")
57+
}
58+
59+
ContinueButton {
60+
Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin)
61+
Layout.topMargin: 40
62+
Layout.alignment: Qt.AlignCenter
63+
text: qsTr("Generate")
64+
enabled: !isPruned && isIBDCompleted
65+
onClicked: {
66+
nodeModel.generateSnapshotThread()
67+
console.log("UI Is Snapshot generated", isSnapshotGenerated)
68+
}
69+
}
70+
}
71+
72+
ColumnLayout {
73+
// index: 1
74+
Layout.alignment: Qt.AlignHCenter
75+
Layout.preferredWidth: Math.min(parent.width, 450)
76+
77+
Image {
78+
Layout.alignment: Qt.AlignCenter
79+
source: "image://images/circle-file"
80+
sourceSize.width: 200
81+
sourceSize.height: 200
82+
}
83+
84+
Header {
85+
Layout.fillWidth: true
86+
Layout.topMargin: 20
87+
headerBold: true
88+
header: qsTr("Generating Snapshot")
89+
description: isRewinding ? qsTr("Rewinding...\nThis might take a while...") : qsTr("Restoring...\nThis might take a while...")
90+
}
91+
92+
ProgressIndicator {
93+
id: generatingProgressIndicator
94+
Layout.topMargin: 20
95+
width: 200
96+
height: 20
97+
progress: nodeModel.snapshotGenerating ? nodeModel.rewindProgress : 0
98+
Layout.alignment: Qt.AlignCenter
99+
progressColor: Theme.color.blue
100+
}
101+
}
102+
103+
ColumnLayout {
104+
// index: 2
105+
id: snapshotGeneratedColumn
106+
Layout.alignment: Qt.AlignHCenter
107+
Layout.preferredWidth: Math.min(parent.width, 450)
108+
109+
Image {
110+
Layout.alignment: Qt.AlignCenter
111+
source: "image://images/circle-green-check"
112+
sourceSize.width: 60
113+
sourceSize.height: 60
114+
}
115+
116+
Header {
117+
Layout.fillWidth: true
118+
Layout.topMargin: 20
119+
headerBold: true
120+
header: qsTr("Snapshot Generated")
121+
descriptionBold: false
122+
descriptionColor: Theme.color.neutral6
123+
descriptionSize: 17
124+
descriptionLineHeight: 1.1
125+
description: snapshotInfo && snapshotInfo["date"] ?
126+
qsTr("It contains transactions up to %1." +
127+
" You can use this snapshot to quickstart other nodes.").arg(snapshotInfo["date"])
128+
: qsTr("It contains transactions up to DEBUG. You can use this snapshot to quickstart other nodes.")
129+
}
130+
131+
TextButton {
132+
Layout.alignment: Qt.AlignCenter
133+
text: qsTr("Generate new snapshot")
134+
onClicked: {
135+
nodeModel.generateSnapshotThread()
136+
}
137+
}
138+
139+
ContinueButton {
140+
Layout.preferredWidth: Math.min(300, columnLayout.width - 2 * Layout.leftMargin)
141+
Layout.topMargin: 20
142+
Layout.alignment: Qt.AlignCenter
143+
text: qsTr("View file")
144+
borderColor: Theme.color.neutral6
145+
backgroundColor: "transparent"
146+
onClicked: viewSnapshotFileDialog.open()
147+
}
148+
149+
FileDialog {
150+
id: viewSnapshotFileDialog
151+
folder: nodeModel.getSnapshotDirectory()
152+
selectMultiple: false
153+
selectExisting: true
154+
nameFilters: ["Snapshot files (*.dat)", "All files (*)"]
155+
}
156+
157+
Setting {
158+
id: snapshotGeneratedViewDetails
159+
Layout.alignment: Qt.AlignCenter
160+
header: qsTr("View details")
161+
actionItem: CaretRightIcon {
162+
id: snapshotGeneratedCaretIcon
163+
color: snapshotGeneratedViewDetails.stateColor
164+
rotation: snapshotGeneratedViewDetails.expanded ? 90 : 0
165+
Behavior on rotation { NumberAnimation { duration: 200 } }
166+
}
167+
168+
property bool expanded: false
169+
170+
onClicked: {
171+
expanded = !expanded
172+
}
173+
}
174+
175+
ColumnLayout {
176+
id: snapshotGeneratedDetailsContent
177+
visible: snapshotGeneratedViewDetails.expanded
178+
Layout.preferredWidth: Math.min(300, parent.width - 2 * Layout.leftMargin)
179+
Layout.alignment: Qt.AlignCenter
180+
Layout.leftMargin: 80
181+
Layout.rightMargin: 80
182+
Layout.topMargin: 10
183+
spacing: 10
184+
// TODO: make sure the block height number aligns right
185+
RowLayout {
186+
CoreText {
187+
text: qsTr("Block Height:")
188+
Layout.alignment: Qt.AlignLeft
189+
font.pixelSize: 14
190+
}
191+
CoreText {
192+
text: snapshotInfo && snapshotInfo["height"] ?
193+
snapshotInfo["height"] : qsTr("DEBUG")
194+
Layout.alignment: Qt.AlignRight
195+
font.pixelSize: 14
196+
}
197+
}
198+
Separator { Layout.fillWidth: true }
199+
CoreText {
200+
text: snapshotInfo && snapshotInfo["hashSerialized"] ?
201+
qsTr("Hash: %1").arg(snapshotInfo["hashSerialized"].substring(0, 13) + "...") :
202+
qsTr("Hash: DEBUG")
203+
font.pixelSize: 14
204+
}
205+
}
206+
}
207+
}
208+
}

src/qml/models/nodemodel.cpp

+86-1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ NodeModel::NodeModel(interfaces::Node& node)
2828
ConnectToBlockTipSignal();
2929
ConnectToNumConnectionsChangedSignal();
3030
ConnectToSnapshotLoadProgressSignal();
31+
ConnectToRewindProgressSignal();
3132
}
3233

3334
void NodeModel::setBlockTipHeight(int new_height)
@@ -157,7 +158,9 @@ void NodeModel::ConnectToBlockTipSignal()
157158
QMetaObject::invokeMethod(this, [=] {
158159
setBlockTipHeight(tip.block_height);
159160
setVerificationProgress(verification_progress);
160-
161+
if (verification_progress >= 0.99) {
162+
setIBDCompleted(true);
163+
}
161164
Q_EMIT setTimeRatioList(tip.block_time);
162165
});
163166
});
@@ -193,6 +196,20 @@ void NodeModel::ConnectToSnapshotLoadProgressSignal()
193196
});
194197
}
195198

199+
void NodeModel::ConnectToRewindProgressSignal()
200+
{
201+
assert(!m_handler_rewind_progress);
202+
203+
m_handler_rewind_progress = m_node.handleRewindProgress(
204+
[this](double progress) {
205+
if (isRewinding()) {
206+
setRewindProgress(1.0 - progress);
207+
} else {
208+
setRewindProgress(progress);
209+
}
210+
});
211+
}
212+
196213
void NodeModel::snapshotLoadThread(QString path_file) {
197214
m_snapshot_loading = true;
198215
Q_EMIT snapshotLoadingChanged();
@@ -223,3 +240,71 @@ void NodeModel::setSnapshotProgress(double new_progress) {
223240
Q_EMIT snapshotProgressChanged();
224241
}
225242
}
243+
244+
void NodeModel::cancelSnapshotGeneration() {
245+
m_snapshot_cancel = true;
246+
m_snapshot_generating = false;
247+
Q_EMIT snapshotGeneratingChanged();
248+
}
249+
250+
void NodeModel::generateSnapshotThread() {
251+
QString path_file = "";
252+
m_snapshot_generating = true;
253+
Q_EMIT snapshotGeneratingChanged();
254+
255+
QThread* generate_snapshot_thread = QThread::create([this, path_file]() {
256+
SnapshotQml generator(m_node, path_file);
257+
generator.setSnapshotCancel(&m_snapshot_cancel);
258+
connect(&generator, &SnapshotQml::isRewindingChanged, this, [this, &generator]() {
259+
setIsRewinding(generator.isRewinding());
260+
});
261+
generator.SnapshotGen();
262+
m_snapshot_generating = false;
263+
Q_EMIT snapshotGeneratingChanged();
264+
setIsSnapshotGenerated(true);
265+
});
266+
267+
connect(generate_snapshot_thread, &QThread::finished, generate_snapshot_thread, &QThread::deleteLater);
268+
269+
generate_snapshot_thread->start();
270+
}
271+
272+
QUrl NodeModel::getSnapshotDirectory() {
273+
return QUrl::fromLocalFile(SnapshotQml(m_node, "").getSnapshotDirectory());
274+
}
275+
276+
bool NodeModel::isSnapshotFileExists() {
277+
bool result = SnapshotQml(m_node, "").isSnapshotFileExists();
278+
if (result) {
279+
setIsSnapshotGenerated(true);
280+
}
281+
return result;
282+
}
283+
284+
void NodeModel::setIBDCompleted(bool completed) {
285+
if (completed != m_is_ibd_completed) {
286+
m_is_ibd_completed = completed;
287+
Q_EMIT isIBDCompletedChanged();
288+
}
289+
}
290+
291+
void NodeModel::setIsRewinding(bool is_rewinding) {
292+
if (is_rewinding != m_is_rewinding) {
293+
m_is_rewinding = is_rewinding;
294+
Q_EMIT isRewindingChanged();
295+
}
296+
}
297+
298+
void NodeModel::setRewindProgress(double progress) {
299+
if (progress != m_rewind_progress) {
300+
m_rewind_progress = progress;
301+
Q_EMIT rewindProgressChanged();
302+
}
303+
}
304+
305+
void NodeModel::setIsSnapshotGenerated(bool generated) {
306+
if (generated != m_is_snapshot_generated) {
307+
m_is_snapshot_generated = generated;
308+
Q_EMIT isSnapshotGeneratedChanged();
309+
}
310+
}

0 commit comments

Comments
 (0)