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