Bitcoin Core  22.99.0
P2P Digital Currency
intro.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2020 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 #if defined(HAVE_CONFIG_H)
7 #endif
8 
9 #include <chainparams.h>
10 #include <fs.h>
11 #include <qt/intro.h>
12 #include <qt/forms/ui_intro.h>
13 
14 #include <qt/guiconstants.h>
15 #include <qt/guiutil.h>
16 #include <qt/optionsmodel.h>
17 
18 #include <interfaces/node.h>
19 #include <util/system.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 */
38 class FreespaceChecker : public QObject
39 {
40  Q_OBJECT
41 
42 public:
43  explicit FreespaceChecker(Intro *intro);
44 
45  enum Status {
48  };
49 
50 public Q_SLOTS:
51  void check();
52 
53 Q_SIGNALS:
54  void reply(int status, const QString &message, quint64 available);
55 
56 private:
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::qstringToBoostPath(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 
112 namespace {
114 int GetPruneTargetGB()
115 {
116  int64_t prune_target_mib = gArgs.GetArg("-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 
122 Intro::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  thread(nullptr),
126  signalled(false),
127  m_blockchain_size_gb(blockchain_size_gb),
128  m_chain_state_size_gb(chain_state_size_gb),
129  m_prune_target_gb{GetPruneTargetGB()}
130 {
131  ui->setupUi(this);
132  ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(PACKAGE_NAME));
133  ui->storageLabel->setText(ui->storageLabel->text().arg(PACKAGE_NAME));
134 
135  ui->lblExplanation1->setText(ui->lblExplanation1->text()
136  .arg(PACKAGE_NAME)
137  .arg(m_blockchain_size_gb)
138  .arg(2009)
139  .arg(tr("Bitcoin"))
140  );
141  ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(PACKAGE_NAME));
142 
143  const int min_prune_target_GB = std::ceil(MIN_DISK_SPACE_FOR_BLOCK_FILES / 1e9);
144  ui->pruneGB->setRange(min_prune_target_GB, std::numeric_limits<int>::max());
145  if (gArgs.GetArg("-prune", 0) > 1) { // -prune=1 means enabled, above that it's a size in MiB
146  ui->prune->setChecked(true);
147  ui->prune->setEnabled(false);
148  }
149  ui->pruneGB->setValue(m_prune_target_gb);
150  ui->pruneGB->setToolTip(ui->prune->toolTip());
151  ui->lblPruneSuffix->setToolTip(ui->prune->toolTip());
152  UpdatePruneLabels(ui->prune->isChecked());
153 
154  connect(ui->prune, &QCheckBox::toggled, [this](bool prune_checked) {
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 
180 void 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 
195 int64_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 
205 bool 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::qstringToBoostPath(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(0, 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
246  }
247  break;
248  } catch (const fs::filesystem_error&) {
249  QMessageBox::critical(nullptr, PACKAGE_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", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting
267  }
268  return true;
269 }
270 
271 void 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()) {
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("%1 GB of free space available").arg(m_bytes_available / GB_BYTES);
303  freeString += " " + tr("(of %1 GB needed)").arg(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("(%1 GB needed for full chain)").arg(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 
314 void 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, "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 
353 void 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 
375 void 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(PACKAGE_NAME) + " " +
393  storageRequiresMsg.arg(m_required_space_gb) + " " +
394  tr("The wallet will also be stored in this directory.")
395  );
396  this->adjustSize();
397 }
PruneMiBtoGB
static int PruneMiBtoGB(int64_t mib)
Convert configured prune target MiB to displayed GB.
Definition: optionsmodel.h:26
Intro::on_dataDirCustom_clicked
void on_dataDirCustom_clicked()
Definition: intro.cpp:333
Intro::m_chain_state_size_gb
const int64_t m_chain_state_size_gb
Definition: intro.h:72
ArgsManager::GetBoolArg
bool GetBoolArg(const std::string &strArg, bool fDefault) const
Return boolean argument or default value.
Definition: system.cpp:600
Intro::~Intro
~Intro()
Definition: intro.cpp:167
FreespaceChecker::intro
Intro * intro
Definition: intro.cpp:57
fs.h
Intro::getDataDirectory
QString getDataDirectory()
Definition: intro.cpp:175
Intro::m_bytes_available
uint64_t m_bytes_available
Definition: intro.h:75
intro.h
ArgsManager::GetChainName
std::string GetChainName() const
Returns the appropriate chain name from the program arguments.
Definition: system.cpp:988
Intro::Intro
Intro(QWidget *parent=nullptr, int64_t blockchain_size_gb=0, int64_t chain_state_size_gb=0)
Definition: intro.cpp:122
GUIUtil
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:59
Intro::showIfNeeded
static bool showIfNeeded(bool &did_show_intro, int64_t &prune_MiB)
Determine data directory.
Definition: intro.cpp:205
Intro::on_dataDirectory_textChanged
void on_dataDirectory_textChanged(const QString &arg1)
Definition: intro.cpp:314
bitcoin-config.h
chainparams.h
Intro::setStatus
void setStatus(int status, const QString &message, quint64 bytesAvailable)
Definition: intro.cpp:271
GUIUtil::qstringToBoostPath
fs::path qstringToBoostPath(const QString &path)
Convert QString to OS specific boost path through UTF-8.
Definition: guiutil.cpp:647
GUIUtil::dialog_flags
constexpr auto dialog_flags
Definition: guiutil.h:59
FreespaceChecker::reply
void reply(int status, const QString &message, quint64 available)
Intro::on_dataDirDefault_clicked
void on_dataDirDefault_clicked()
Definition: intro.cpp:328
Intro::m_required_space_gb
int64_t m_required_space_gb
Total required space (in GB) depending on user choice (prune or not prune).
Definition: intro.h:74
Intro::UpdatePruneLabels
void UpdatePruneLabels(bool prune_checked)
Definition: intro.cpp:375
Intro::UpdateFreeSpaceLabel
void UpdateFreeSpaceLabel()
Definition: intro.cpp:299
ArgsManager::SoftSetArg
bool SoftSetArg(const std::string &strArg, const std::string &strValue)
Set an argument if it doesn't already have a value.
Definition: system.cpp:606
PACKAGE_NAME
#define PACKAGE_NAME
Definition: bitcoin-config.h:362
node.h
FreespaceChecker::FreespaceChecker
FreespaceChecker(Intro *intro)
Definition: intro.cpp:62
Intro::m_blockchain_size_gb
const int64_t m_blockchain_size_gb
Definition: intro.h:71
Intro::FreespaceChecker
friend class FreespaceChecker
Definition: intro.h:84
ArgsManager::GetArg
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: system.cpp:588
Intro::signalled
bool signalled
Definition: intro.h:69
guiutil.h
SelectParams
void SelectParams(const std::string &network)
Sets the params returned by Params() to those for the given chain name.
Definition: chainparams.cpp:578
FreespaceChecker
Definition: intro.cpp:38
Intro::ui
Ui::Intro * ui
Definition: intro.h:66
gArgs
ArgsManager gArgs
Definition: system.cpp:84
GUIUtil::getDefaultDataDirectory
QString getDefaultDataDirectory()
Determine default data directory for operating system.
Definition: guiutil.cpp:274
Intro::on_ellipsisButton_clicked
void on_ellipsisButton_clicked()
Definition: intro.cpp:321
GB_BYTES
static constexpr uint64_t GB_BYTES
Definition: guiconstants.h:53
Intro::checkPath
void checkPath(const QString &dataDir)
Definition: intro.cpp:353
system.h
Ui
Definition: addressbookpage.h:14
FreespaceChecker::Status
Status
Definition: intro.cpp:45
MIN_DISK_SPACE_FOR_BLOCK_FILES
static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES
Definition: validation.h:90
guiconstants.h
Intro::m_prune_target_gb
int64_t m_prune_target_gb
Definition: intro.h:76
Intro::getPruneMiB
int64_t getPruneMiB() const
Definition: intro.cpp:195
PruneGBtoMiB
static int64_t PruneGBtoMiB(int gb)
Convert displayed prune target GB to configured MiB.
Definition: optionsmodel.h:31
Intro::pathToCheck
QString pathToCheck
Definition: intro.h:70
Intro::mutex
QMutex mutex
Definition: intro.h:68
Params
const CChainParams & Params()
Return the currently selected parameters.
Definition: chainparams.cpp:559
Intro::thread
QThread * thread
Definition: intro.h:67
Intro::getPathToCheck
QString getPathToCheck()
Definition: intro.cpp:365
DEFAULT_PRUNE_TARGET_GB
static constexpr int DEFAULT_PRUNE_TARGET_GB
Definition: guiconstants.h:56
DEFAULT_CHOOSE_DATADIR
static const bool DEFAULT_CHOOSE_DATADIR
Definition: intro.h:12
FreespaceChecker::ST_OK
@ ST_OK
Definition: intro.cpp:46
optionsmodel.h
Intro::startThread
void startThread()
Definition: intro.cpp:339
Intro::setDataDirectory
void setDataDirectory(const QString &dataDir)
Definition: intro.cpp:180
FreespaceChecker::ST_ERROR
@ ST_ERROR
Definition: intro.cpp:47
Intro
Introduction screen (pre-GUI startup).
Definition: intro.h:28
FreespaceChecker::check
void check()
Definition: intro.cpp:67
Intro::requestCheck
void requestCheck()
TryCreateDirectories
bool TryCreateDirectories(const fs::path &p)
Ignores exceptions thrown by Boost's create_directories if the requested directory exists.
Definition: system.cpp:1080