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