Bitcoin Core 28.99.0
P2P Digital Currency
intro.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2022 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 <bitcoin-build-config.h> // IWYU pragma: keep
6
7#include <chainparams.h>
8#include <qt/intro.h>
9#include <qt/forms/ui_intro.h>
10#include <util/chaintype.h>
11#include <util/fs.h>
12
13#include <qt/guiconstants.h>
14#include <qt/guiutil.h>
15#include <qt/optionsmodel.h>
16
17#include <common/args.h>
18#include <interfaces/node.h>
19#include <util/fs_helpers.h>
20#include <validation.h>
21
22#include <QFileDialog>
23#include <QSettings>
24#include <QMessageBox>
25
26#include <cmath>
27
28/* Check free space asynchronously to prevent hanging the UI thread.
29
30 Up to one request to check a path is in flight to this thread; when the check()
31 function runs, the current path is requested from the associated Intro object.
32 The reply is sent back through a signal.
33
34 This ensures that no queue of checking requests is built up while the user is
35 still entering the path, and that always the most recently entered path is checked as
36 soon as the thread becomes available.
37*/
38class FreespaceChecker : public QObject
39{
40 Q_OBJECT
41
42public:
43 explicit FreespaceChecker(Intro *intro);
44
45 enum Status {
48 };
49
50public Q_SLOTS:
51 void check();
52
53Q_SIGNALS:
54 void reply(int status, const QString &message, quint64 available);
55
56private:
58};
59
60#include <qt/intro.moc>
61
63{
64 this->intro = _intro;
65}
66
68{
69 QString dataDirStr = intro->getPathToCheck();
70 fs::path dataDir = GUIUtil::QStringToPath(dataDirStr);
71 uint64_t freeBytesAvailable = 0;
72 int replyStatus = ST_OK;
73 QString replyMessage = tr("A new data directory will be created.");
74
75 /* Find first parent that exists, so that fs::space does not fail */
76 fs::path parentDir = dataDir;
77 fs::path parentDirOld = fs::path();
78 while(parentDir.has_parent_path() && !fs::exists(parentDir))
79 {
80 parentDir = parentDir.parent_path();
81
82 /* Check if we make any progress, break if not to prevent an infinite loop here */
83 if (parentDirOld == parentDir)
84 break;
85
86 parentDirOld = parentDir;
87 }
88
89 try {
90 freeBytesAvailable = fs::space(parentDir).available;
91 if(fs::exists(dataDir))
92 {
93 if(fs::is_directory(dataDir))
94 {
95 QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
96 replyStatus = ST_OK;
97 replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
98 } else {
99 replyStatus = ST_ERROR;
100 replyMessage = tr("Path already exists, and is not a directory.");
101 }
102 }
103 } catch (const fs::filesystem_error&)
104 {
105 /* Parent directory does not exist or is not accessible */
106 replyStatus = ST_ERROR;
107 replyMessage = tr("Cannot create data directory here.");
108 }
109 Q_EMIT reply(replyStatus, replyMessage, freeBytesAvailable);
110}
111
112namespace {
114int GetPruneTargetGB()
115{
116 int64_t prune_target_mib = gArgs.GetIntArg("-prune", 0);
117 // >1 means automatic pruning is enabled by config, 1 means manual pruning, 0 means no pruning.
118 return prune_target_mib > 1 ? PruneMiBtoGB(prune_target_mib) : DEFAULT_PRUNE_TARGET_GB;
119}
120} // namespace
121
122Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, int64_t chain_state_size_gb) :
123 QDialog(parent, GUIUtil::dialog_flags),
124 ui(new Ui::Intro),
125 m_blockchain_size_gb(blockchain_size_gb),
126 m_chain_state_size_gb(chain_state_size_gb),
127 m_prune_target_gb{GetPruneTargetGB()}
128{
129 ui->setupUi(this);
130 ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(CLIENT_NAME));
131 ui->storageLabel->setText(ui->storageLabel->text().arg(CLIENT_NAME));
132
133 ui->lblExplanation1->setText(ui->lblExplanation1->text()
134 .arg(CLIENT_NAME)
136 .arg(2009)
137 .arg(tr("Bitcoin"))
138 );
139 ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(CLIENT_NAME));
140
141 const int min_prune_target_GB = std::ceil(MIN_DISK_SPACE_FOR_BLOCK_FILES / 1e9);
142 ui->pruneGB->setRange(min_prune_target_GB, std::numeric_limits<int>::max());
143 if (gArgs.IsArgSet("-prune")) {
145 ui->prune->setChecked(gArgs.GetIntArg("-prune", 0) >= 1);
146 ui->prune->setEnabled(false);
147 }
148 ui->pruneGB->setValue(m_prune_target_gb);
149 ui->pruneGB->setToolTip(ui->prune->toolTip());
150 ui->lblPruneSuffix->setToolTip(ui->prune->toolTip());
151 UpdatePruneLabels(ui->prune->isChecked());
152
153 connect(ui->prune, &QCheckBox::toggled, [this](bool prune_checked) {
154 m_prune_checkbox_is_default = false;
155 UpdatePruneLabels(prune_checked);
156 UpdateFreeSpaceLabel();
157 });
158 connect(ui->pruneGB, qOverload<int>(&QSpinBox::valueChanged), [this](int prune_GB) {
159 m_prune_target_gb = prune_GB;
160 UpdatePruneLabels(ui->prune->isChecked());
161 UpdateFreeSpaceLabel();
162 });
163
164 startThread();
165}
166
168{
169 delete ui;
170 /* Ensure thread is finished before it is deleted */
171 thread->quit();
172 thread->wait();
173}
174
176{
177 return ui->dataDirectory->text();
178}
179
180void Intro::setDataDirectory(const QString &dataDir)
181{
182 ui->dataDirectory->setText(dataDir);
183 if(dataDir == GUIUtil::getDefaultDataDirectory())
184 {
185 ui->dataDirDefault->setChecked(true);
186 ui->dataDirectory->setEnabled(false);
187 ui->ellipsisButton->setEnabled(false);
188 } else {
189 ui->dataDirCustom->setChecked(true);
190 ui->dataDirectory->setEnabled(true);
191 ui->ellipsisButton->setEnabled(true);
192 }
193}
194
195int64_t Intro::getPruneMiB() const
196{
197 switch (ui->prune->checkState()) {
198 case Qt::Checked:
200 case Qt::Unchecked: default:
201 return 0;
202 }
203}
204
205bool Intro::showIfNeeded(bool& did_show_intro, int64_t& prune_MiB)
206{
207 did_show_intro = false;
208
209 QSettings settings;
210 /* If data directory provided on command line, no need to look at settings
211 or show a picking dialog */
212 if(!gArgs.GetArg("-datadir", "").empty())
213 return true;
214 /* 1) Default data directory for operating system */
215 QString dataDir = GUIUtil::getDefaultDataDirectory();
216 /* 2) Allow QSettings to override default dir */
217 dataDir = settings.value("strDataDir", dataDir).toString();
218
219 if(!fs::exists(GUIUtil::QStringToPath(dataDir)) || gArgs.GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR) || settings.value("fReset", false).toBool() || gArgs.GetBoolArg("-resetguisettings", false))
220 {
221 /* Use selectParams here to guarantee Params() can be used by node interface */
222 try {
224 } catch (const std::exception&) {
225 return false;
226 }
227
228 /* If current default data directory does not exist, let the user choose one */
229 Intro intro(nullptr, Params().AssumedBlockchainSize(), Params().AssumedChainStateSize());
230 intro.setDataDirectory(dataDir);
231 intro.setWindowIcon(QIcon(":icons/bitcoin"));
232 did_show_intro = true;
233
234 while(true)
235 {
236 if(!intro.exec())
237 {
238 /* Cancel clicked */
239 return false;
240 }
241 dataDir = intro.getDataDirectory();
242 try {
244 // If a new data directory has been created, make wallets subdirectory too
245 TryCreateDirectories(GUIUtil::QStringToPath(dataDir) / "wallets");
246 }
247 break;
248 } catch (const fs::filesystem_error&) {
249 QMessageBox::critical(nullptr, CLIENT_NAME,
250 tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
251 /* fall through, back to choosing screen */
252 }
253 }
254
255 // Additional preferences:
256 prune_MiB = intro.getPruneMiB();
257
258 settings.setValue("strDataDir", dataDir);
259 settings.setValue("fReset", false);
260 }
261 /* Only override -datadir if different from the default, to make it possible to
262 * override -datadir in the bitcoin.conf file in the default data directory
263 * (to be consistent with bitcoind behavior)
264 */
265 if(dataDir != GUIUtil::getDefaultDataDirectory()) {
266 gArgs.SoftSetArg("-datadir", fs::PathToString(GUIUtil::QStringToPath(dataDir))); // use OS locale for path setting
267 }
268 return true;
269}
270
271void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
272{
273 switch(status)
274 {
276 ui->errorMessage->setText(message);
277 ui->errorMessage->setStyleSheet("");
278 break;
280 ui->errorMessage->setText(tr("Error") + ": " + message);
281 ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
282 break;
283 }
284 /* Indicate number of bytes available */
285 if(status == FreespaceChecker::ST_ERROR)
286 {
287 ui->freeSpace->setText("");
288 } else {
289 m_bytes_available = bytesAvailable;
290 if (ui->prune->isEnabled() && m_prune_checkbox_is_default) {
292 }
294 }
295 /* Don't allow confirm in ERROR state */
296 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
297}
298
300{
301 QString freeString = tr("%n GB of space available", "", m_bytes_available / GB_BYTES);
303 freeString += " " + tr("(of %n GB needed)", "", m_required_space_gb);
304 ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
305 } else if (m_bytes_available / GB_BYTES - m_required_space_gb < 10) {
306 freeString += " " + tr("(%n GB needed for full chain)", "", m_required_space_gb);
307 ui->freeSpace->setStyleSheet("QLabel { color: #999900 }");
308 } else {
309 ui->freeSpace->setStyleSheet("");
310 }
311 ui->freeSpace->setText(freeString + ".");
312}
313
314void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
315{
316 /* Disable OK button until check result comes in */
317 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
318 checkPath(dataDirStr);
319}
320
322{
323 QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(nullptr, tr("Choose data directory"), ui->dataDirectory->text()));
324 if(!dir.isEmpty())
325 ui->dataDirectory->setText(dir);
326}
327
329{
331}
332
334{
335 ui->dataDirectory->setEnabled(true);
336 ui->ellipsisButton->setEnabled(true);
337}
338
340{
341 thread = new QThread(this);
342 FreespaceChecker *executor = new FreespaceChecker(this);
343 executor->moveToThread(thread);
344
345 connect(executor, &FreespaceChecker::reply, this, &Intro::setStatus);
346 connect(this, &Intro::requestCheck, executor, &FreespaceChecker::check);
347 /* make sure executor object is deleted in its own thread */
348 connect(thread, &QThread::finished, executor, &QObject::deleteLater);
349
350 thread->start();
351}
352
353void Intro::checkPath(const QString &dataDir)
354{
355 mutex.lock();
356 pathToCheck = dataDir;
357 if(!signalled)
358 {
359 signalled = true;
360 Q_EMIT requestCheck();
361 }
362 mutex.unlock();
363}
364
366{
367 QString retval;
368 mutex.lock();
369 retval = pathToCheck;
370 signalled = false; /* new request can be queued now */
371 mutex.unlock();
372 return retval;
373}
374
375void Intro::UpdatePruneLabels(bool prune_checked)
376{
378 QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time.");
379 if (prune_checked && m_prune_target_gb <= m_blockchain_size_gb) {
381 storageRequiresMsg = tr("Approximately %1 GB of data will be stored in this directory.");
382 }
383 ui->lblExplanation3->setVisible(prune_checked);
384 ui->pruneGB->setEnabled(prune_checked);
385 static constexpr uint64_t nPowTargetSpacing = 10 * 60; // from chainparams, which we don't have at this stage
386 static constexpr uint32_t expected_block_data_size = 2250000; // includes undo data
387 const uint64_t expected_backup_days = m_prune_target_gb * 1e9 / (uint64_t(expected_block_data_size) * 86400 / nPowTargetSpacing);
388 ui->lblPruneSuffix->setText(
389 //: Explanatory text on the capability of the current prune target.
390 tr("(sufficient to restore backups %n day(s) old)", "", expected_backup_days));
391 ui->sizeWarningLabel->setText(
392 tr("%1 will download and store a copy of the Bitcoin block chain.").arg(CLIENT_NAME) + " " +
393 storageRequiresMsg.arg(m_required_space_gb) + " " +
394 tr("The wallet will also be stored in this directory.")
395 );
396 this->adjustSize();
397}
ArgsManager gArgs
Definition: args.cpp:42
void SelectParams(const ChainType chain)
Sets the params returned by Params() to those for the given chain type.
const CChainParams & Params()
Return the currently selected parameters.
ChainType GetChainType() const
Returns the appropriate chain type from the program arguments.
Definition: args.cpp:774
bool SoftSetArg(const std::string &strArg, const std::string &strValue)
Set an argument if it doesn't already have a value.
Definition: args.cpp:530
bool IsArgSet(const std::string &strArg) const
Return true if the given argument has been manually set.
Definition: args.cpp:371
int64_t GetIntArg(const std::string &strArg, int64_t nDefault) const
Return integer argument or default value.
Definition: args.cpp:482
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: args.cpp:457
bool GetBoolArg(const std::string &strArg, bool fDefault) const
Return boolean argument or default value.
Definition: args.cpp:507
FreespaceChecker(Intro *intro)
Definition: intro.cpp:62
Intro * intro
Definition: intro.cpp:57
void reply(int status, const QString &message, quint64 available)
void check()
Definition: intro.cpp:67
Introduction screen (pre-GUI startup).
Definition: intro.h:29
~Intro()
Definition: intro.cpp:167
void setStatus(int status, const QString &message, quint64 bytesAvailable)
Definition: intro.cpp:271
void on_ellipsisButton_clicked()
Definition: intro.cpp:321
QMutex mutex
Definition: intro.h:69
void UpdatePruneLabels(bool prune_checked)
Definition: intro.cpp:375
const int64_t m_blockchain_size_gb
Definition: intro.h:72
void setDataDirectory(const QString &dataDir)
Definition: intro.cpp:180
int64_t m_prune_target_gb
Definition: intro.h:77
bool m_prune_checkbox_is_default
Definition: intro.h:67
uint64_t m_bytes_available
Definition: intro.h:76
QString pathToCheck
Definition: intro.h:71
friend class FreespaceChecker
Definition: intro.h:85
void on_dataDirectory_textChanged(const QString &arg1)
Definition: intro.cpp:314
int64_t m_required_space_gb
Total required space (in GB) depending on user choice (prune or not prune).
Definition: intro.h:75
void UpdateFreeSpaceLabel()
Definition: intro.cpp:299
bool signalled
Definition: intro.h:70
int64_t getPruneMiB() const
Definition: intro.cpp:195
static bool showIfNeeded(bool &did_show_intro, int64_t &prune_MiB)
Determine data directory.
Definition: intro.cpp:205
QString getPathToCheck()
Definition: intro.cpp:365
Ui::Intro * ui
Definition: intro.h:66
void requestCheck()
Intro(QWidget *parent=nullptr, int64_t blockchain_size_gb=0, int64_t chain_state_size_gb=0)
Definition: intro.cpp:122
const int64_t m_chain_state_size_gb
Definition: intro.h:73
QString getDataDirectory()
Definition: intro.cpp:175
void checkPath(const QString &dataDir)
Definition: intro.cpp:353
void startThread()
Definition: intro.cpp:339
void on_dataDirDefault_clicked()
Definition: intro.cpp:328
QThread * thread
Definition: intro.h:68
void on_dataDirCustom_clicked()
Definition: intro.cpp:333
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:33
bool TryCreateDirectories(const fs::path &p)
Ignores exceptions thrown by create_directories if the requested directory exists.
Definition: fs_helpers.cpp:261
static constexpr int DEFAULT_PRUNE_TARGET_GB
Definition: guiconstants.h:61
static constexpr uint64_t GB_BYTES
Definition: guiconstants.h:58
static const bool DEFAULT_CHOOSE_DATADIR
Definition: intro.h:12
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:58
QString getDefaultDataDirectory()
Determine default data directory for operating system.
Definition: guiutil.cpp:297
constexpr auto dialog_flags
Definition: guiutil.h:60
fs::path QStringToPath(const QString &path)
Convert QString to OS specific boost path through UTF-8.
Definition: guiutil.cpp:677
static bool exists(const path &p)
Definition: fs.h:89
static std::string PathToString(const path &path)
Convert path object to a byte string.
Definition: fs.h:151
static int PruneMiBtoGB(int64_t mib)
Convert configured prune target MiB to displayed GB.
Definition: optionsmodel.h:29
static int64_t PruneGBtoMiB(int gb)
Convert displayed prune target GB to configured MiB.
Definition: optionsmodel.h:34
static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES
Definition: validation.h:79