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