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