Bitcoin Core  27.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.IsArgSet("-prune")) {
147  ui->prune->setChecked(gArgs.GetIntArg("-prune", 0) >= 1);
148  ui->prune->setEnabled(false);
149  }
150  ui->pruneGB->setValue(m_prune_target_gb);
151  ui->pruneGB->setToolTip(ui->prune->toolTip());
152  ui->lblPruneSuffix->setToolTip(ui->prune->toolTip());
153  UpdatePruneLabels(ui->prune->isChecked());
154 
155  connect(ui->prune, &QCheckBox::toggled, [this](bool prune_checked) {
156  m_prune_checkbox_is_default = false;
157  UpdatePruneLabels(prune_checked);
158  UpdateFreeSpaceLabel();
159  });
160  connect(ui->pruneGB, qOverload<int>(&QSpinBox::valueChanged), [this](int prune_GB) {
161  m_prune_target_gb = prune_GB;
162  UpdatePruneLabels(ui->prune->isChecked());
163  UpdateFreeSpaceLabel();
164  });
165 
166  startThread();
167 }
168 
170 {
171  delete ui;
172  /* Ensure thread is finished before it is deleted */
173  thread->quit();
174  thread->wait();
175 }
176 
178 {
179  return ui->dataDirectory->text();
180 }
181 
182 void Intro::setDataDirectory(const QString &dataDir)
183 {
184  ui->dataDirectory->setText(dataDir);
185  if(dataDir == GUIUtil::getDefaultDataDirectory())
186  {
187  ui->dataDirDefault->setChecked(true);
188  ui->dataDirectory->setEnabled(false);
189  ui->ellipsisButton->setEnabled(false);
190  } else {
191  ui->dataDirCustom->setChecked(true);
192  ui->dataDirectory->setEnabled(true);
193  ui->ellipsisButton->setEnabled(true);
194  }
195 }
196 
197 int64_t Intro::getPruneMiB() const
198 {
199  switch (ui->prune->checkState()) {
200  case Qt::Checked:
202  case Qt::Unchecked: default:
203  return 0;
204  }
205 }
206 
207 bool Intro::showIfNeeded(bool& did_show_intro, int64_t& prune_MiB)
208 {
209  did_show_intro = false;
210 
211  QSettings settings;
212  /* If data directory provided on command line, no need to look at settings
213  or show a picking dialog */
214  if(!gArgs.GetArg("-datadir", "").empty())
215  return true;
216  /* 1) Default data directory for operating system */
217  QString dataDir = GUIUtil::getDefaultDataDirectory();
218  /* 2) Allow QSettings to override default dir */
219  dataDir = settings.value("strDataDir", dataDir).toString();
220 
221  if(!fs::exists(GUIUtil::QStringToPath(dataDir)) || gArgs.GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR) || settings.value("fReset", false).toBool() || gArgs.GetBoolArg("-resetguisettings", false))
222  {
223  /* Use selectParams here to guarantee Params() can be used by node interface */
224  try {
226  } catch (const std::exception&) {
227  return false;
228  }
229 
230  /* If current default data directory does not exist, let the user choose one */
231  Intro intro(nullptr, Params().AssumedBlockchainSize(), Params().AssumedChainStateSize());
232  intro.setDataDirectory(dataDir);
233  intro.setWindowIcon(QIcon(":icons/bitcoin"));
234  did_show_intro = true;
235 
236  while(true)
237  {
238  if(!intro.exec())
239  {
240  /* Cancel clicked */
241  return false;
242  }
243  dataDir = intro.getDataDirectory();
244  try {
246  // If a new data directory has been created, make wallets subdirectory too
247  TryCreateDirectories(GUIUtil::QStringToPath(dataDir) / "wallets");
248  }
249  break;
250  } catch (const fs::filesystem_error&) {
251  QMessageBox::critical(nullptr, PACKAGE_NAME,
252  tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
253  /* fall through, back to choosing screen */
254  }
255  }
256 
257  // Additional preferences:
258  prune_MiB = intro.getPruneMiB();
259 
260  settings.setValue("strDataDir", dataDir);
261  settings.setValue("fReset", false);
262  }
263  /* Only override -datadir if different from the default, to make it possible to
264  * override -datadir in the bitcoin.conf file in the default data directory
265  * (to be consistent with bitcoind behavior)
266  */
267  if(dataDir != GUIUtil::getDefaultDataDirectory()) {
268  gArgs.SoftSetArg("-datadir", fs::PathToString(GUIUtil::QStringToPath(dataDir))); // use OS locale for path setting
269  }
270  return true;
271 }
272 
273 void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
274 {
275  switch(status)
276  {
278  ui->errorMessage->setText(message);
279  ui->errorMessage->setStyleSheet("");
280  break;
282  ui->errorMessage->setText(tr("Error") + ": " + message);
283  ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
284  break;
285  }
286  /* Indicate number of bytes available */
287  if(status == FreespaceChecker::ST_ERROR)
288  {
289  ui->freeSpace->setText("");
290  } else {
291  m_bytes_available = bytesAvailable;
292  if (ui->prune->isEnabled() && m_prune_checkbox_is_default) {
294  }
296  }
297  /* Don't allow confirm in ERROR state */
298  ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
299 }
300 
302 {
303  QString freeString = tr("%n GB of space available", "", m_bytes_available / GB_BYTES);
305  freeString += " " + tr("(of %n GB needed)", "", m_required_space_gb);
306  ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
307  } else if (m_bytes_available / GB_BYTES - m_required_space_gb < 10) {
308  freeString += " " + tr("(%n GB needed for full chain)", "", m_required_space_gb);
309  ui->freeSpace->setStyleSheet("QLabel { color: #999900 }");
310  } else {
311  ui->freeSpace->setStyleSheet("");
312  }
313  ui->freeSpace->setText(freeString + ".");
314 }
315 
316 void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
317 {
318  /* Disable OK button until check result comes in */
319  ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
320  checkPath(dataDirStr);
321 }
322 
324 {
325  QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(nullptr, tr("Choose data directory"), ui->dataDirectory->text()));
326  if(!dir.isEmpty())
327  ui->dataDirectory->setText(dir);
328 }
329 
331 {
333 }
334 
336 {
337  ui->dataDirectory->setEnabled(true);
338  ui->ellipsisButton->setEnabled(true);
339 }
340 
342 {
343  thread = new QThread(this);
344  FreespaceChecker *executor = new FreespaceChecker(this);
345  executor->moveToThread(thread);
346 
347  connect(executor, &FreespaceChecker::reply, this, &Intro::setStatus);
348  connect(this, &Intro::requestCheck, executor, &FreespaceChecker::check);
349  /* make sure executor object is deleted in its own thread */
350  connect(thread, &QThread::finished, executor, &QObject::deleteLater);
351 
352  thread->start();
353 }
354 
355 void Intro::checkPath(const QString &dataDir)
356 {
357  mutex.lock();
358  pathToCheck = dataDir;
359  if(!signalled)
360  {
361  signalled = true;
362  Q_EMIT requestCheck();
363  }
364  mutex.unlock();
365 }
366 
368 {
369  QString retval;
370  mutex.lock();
371  retval = pathToCheck;
372  signalled = false; /* new request can be queued now */
373  mutex.unlock();
374  return retval;
375 }
376 
377 void Intro::UpdatePruneLabels(bool prune_checked)
378 {
380  QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time.");
381  if (prune_checked && m_prune_target_gb <= m_blockchain_size_gb) {
383  storageRequiresMsg = tr("Approximately %1 GB of data will be stored in this directory.");
384  }
385  ui->lblExplanation3->setVisible(prune_checked);
386  ui->pruneGB->setEnabled(prune_checked);
387  static constexpr uint64_t nPowTargetSpacing = 10 * 60; // from chainparams, which we don't have at this stage
388  static constexpr uint32_t expected_block_data_size = 2250000; // includes undo data
389  const uint64_t expected_backup_days = m_prune_target_gb * 1e9 / (uint64_t(expected_block_data_size) * 86400 / nPowTargetSpacing);
390  ui->lblPruneSuffix->setText(
391  //: Explanatory text on the capability of the current prune target.
392  tr("(sufficient to restore backups %n day(s) old)", "", expected_backup_days));
393  ui->sizeWarningLabel->setText(
394  tr("%1 will download and store a copy of the Bitcoin block chain.").arg(PACKAGE_NAME) + " " +
395  storageRequiresMsg.arg(m_required_space_gb) + " " +
396  tr("The wallet will also be stored in this directory.")
397  );
398  this->adjustSize();
399 }
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:741
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:169
void setStatus(int status, const QString &message, quint64 bytesAvailable)
Definition: intro.cpp:273
void on_ellipsisButton_clicked()
Definition: intro.cpp:323
QMutex mutex
Definition: intro.h:69
void UpdatePruneLabels(bool prune_checked)
Definition: intro.cpp:377
const int64_t m_blockchain_size_gb
Definition: intro.h:72
void setDataDirectory(const QString &dataDir)
Definition: intro.cpp:182
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:316
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:301
bool signalled
Definition: intro.h:70
int64_t getPruneMiB() const
Definition: intro.cpp:197
static bool showIfNeeded(bool &did_show_intro, int64_t &prune_MiB)
Determine data directory.
Definition: intro.cpp:207
QString getPathToCheck()
Definition: intro.cpp:367
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:73
QString getDataDirectory()
Definition: intro.cpp:177
void checkPath(const QString &dataDir)
Definition: intro.cpp:355
void startThread()
Definition: intro.cpp:341
void on_dataDirDefault_clicked()
Definition: intro.cpp:330
QThread * thread
Definition: intro.h:68
void on_dataDirCustom_clicked()
Definition: intro.cpp:335
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:262
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:300
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:673
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:77