Bitcoin Core  26.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 #if defined(HAVE_CONFIG_H)
7 #endif
8 
9 #include <chainparams.h>
10 #include <qt/intro.h>
11 #include <qt/forms/ui_intro.h>
12 #include <util/chaintype.h>
13 #include <util/fs.h>
14 
15 #include <qt/guiconstants.h>
16 #include <qt/guiutil.h>
17 #include <qt/optionsmodel.h>
18 
19 #include <common/args.h>
20 #include <interfaces/node.h>
21 #include <util/fs_helpers.h>
22 #include <validation.h>
23 
24 #include <QFileDialog>
25 #include <QSettings>
26 #include <QMessageBox>
27 
28 #include <cmath>
29 
30 /* Check free space asynchronously to prevent hanging the UI thread.
31 
32  Up to one request to check a path is in flight to this thread; when the check()
33  function runs, the current path is requested from the associated Intro object.
34  The reply is sent back through a signal.
35 
36  This ensures that no queue of checking requests is built up while the user is
37  still entering the path, and that always the most recently entered path is checked as
38  soon as the thread becomes available.
39 */
40 class FreespaceChecker : public QObject
41 {
42  Q_OBJECT
43 
44 public:
45  explicit FreespaceChecker(Intro *intro);
46 
47  enum Status {
49  ST_ERROR
50  };
51 
52 public Q_SLOTS:
53  void check();
54 
55 Q_SIGNALS:
56  void reply(int status, const QString &message, quint64 available);
57 
58 private:
60 };
61 
62 #include <qt/intro.moc>
63 
65 {
66  this->intro = _intro;
67 }
68 
70 {
71  QString dataDirStr = intro->getPathToCheck();
72  fs::path dataDir = GUIUtil::QStringToPath(dataDirStr);
73  uint64_t freeBytesAvailable = 0;
74  int replyStatus = ST_OK;
75  QString replyMessage = tr("A new data directory will be created.");
76 
77  /* Find first parent that exists, so that fs::space does not fail */
78  fs::path parentDir = dataDir;
79  fs::path parentDirOld = fs::path();
80  while(parentDir.has_parent_path() && !fs::exists(parentDir))
81  {
82  parentDir = parentDir.parent_path();
83 
84  /* Check if we make any progress, break if not to prevent an infinite loop here */
85  if (parentDirOld == parentDir)
86  break;
87 
88  parentDirOld = parentDir;
89  }
90 
91  try {
92  freeBytesAvailable = fs::space(parentDir).available;
93  if(fs::exists(dataDir))
94  {
95  if(fs::is_directory(dataDir))
96  {
97  QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
98  replyStatus = ST_OK;
99  replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
100  } else {
101  replyStatus = ST_ERROR;
102  replyMessage = tr("Path already exists, and is not a directory.");
103  }
104  }
105  } catch (const fs::filesystem_error&)
106  {
107  /* Parent directory does not exist or is not accessible */
108  replyStatus = ST_ERROR;
109  replyMessage = tr("Cannot create data directory here.");
110  }
111  Q_EMIT reply(replyStatus, replyMessage, freeBytesAvailable);
112 }
113 
114 namespace {
116 int GetPruneTargetGB()
117 {
118  int64_t prune_target_mib = gArgs.GetIntArg("-prune", 0);
119  // >1 means automatic pruning is enabled by config, 1 means manual pruning, 0 means no pruning.
120  return prune_target_mib > 1 ? PruneMiBtoGB(prune_target_mib) : DEFAULT_PRUNE_TARGET_GB;
121 }
122 } // namespace
123 
124 Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, int64_t chain_state_size_gb) :
125  QDialog(parent, GUIUtil::dialog_flags),
126  ui(new Ui::Intro),
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)
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.GetIntArg("-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::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, 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", fs::PathToString(GUIUtil::QStringToPath(dataDir))); // 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() && !(gArgs.IsArgSet("-prune") && gArgs.GetIntArg("-prune", 0) == 0)) {
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 
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, 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 
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 }
ArgsManager gArgs
Definition: args.cpp:41
#define PACKAGE_NAME
const CChainParams & Params()
Return the currently selected parameters.
void SelectParams(const ChainType chain)
Sets the params returned by Params() to those for the given chain type.
ChainType GetChainType() const
Returns the appropriate chain type from the program arguments.
Definition: args.cpp:729
bool SoftSetArg(const std::string &strArg, const std::string &strValue)
Set an argument if it doesn't already have a value.
Definition: args.cpp:528
bool IsArgSet(const std::string &strArg) const
Return true if the given argument has been manually set.
Definition: args.cpp:369
int64_t GetIntArg(const std::string &strArg, int64_t nDefault) const
Return integer argument or default value.
Definition: args.cpp:480
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: args.cpp:455
bool GetBoolArg(const std::string &strArg, bool fDefault) const
Return boolean argument or default value.
Definition: args.cpp:505
FreespaceChecker(Intro *intro)
Definition: intro.cpp:64
Intro * intro
Definition: intro.cpp:59
void reply(int status, const QString &message, quint64 available)
void check()
Definition: intro.cpp:69
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:68
void UpdatePruneLabels(bool prune_checked)
Definition: intro.cpp:375
const int64_t m_blockchain_size_gb
Definition: intro.h:71
void setDataDirectory(const QString &dataDir)
Definition: intro.cpp:180
int64_t m_prune_target_gb
Definition: intro.h:76
uint64_t m_bytes_available
Definition: intro.h:75
QString pathToCheck
Definition: intro.h:70
friend class FreespaceChecker
Definition: intro.h:84
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:74
void UpdateFreeSpaceLabel()
Definition: intro.cpp:299
bool signalled
Definition: intro.h:69
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:124
const int64_t m_chain_state_size_gb
Definition: intro.h:72
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:67
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:31
bool TryCreateDirectories(const fs::path &p)
Ignores exceptions thrown by create_directories if the requested directory exists.
Definition: fs_helpers.cpp:283
static constexpr int DEFAULT_PRUNE_TARGET_GB
Definition: guiconstants.h:60
static constexpr uint64_t GB_BYTES
Definition: guiconstants.h:57
static const bool DEFAULT_CHOOSE_DATADIR
Definition: intro.h:12
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:60
QString getDefaultDataDirectory()
Determine default data directory for operating system.
Definition: guiutil.cpp:292
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:665
static bool exists(const path &p)
Definition: fs.h:88
static std::string PathToString(const path &path)
Convert path object to a byte string.
Definition: fs.h:150
static int PruneMiBtoGB(int64_t mib)
Convert configured prune target MiB to displayed GB.
Definition: optionsmodel.h:27
static int64_t PruneGBtoMiB(int gb)
Convert displayed prune target GB to configured MiB.
Definition: optionsmodel.h:32
static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES
Definition: validation.h:81