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