PIVX Core  5.6.99
P2P Digital Currency
intro.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2014 The Bitcoin developers
2 // Copyright (c) 2014-2015 The Dash developers
3 // Copyright (c) 2015-2021 The PIVX Core developers
4 // Distributed under the MIT/X11 software license, see the accompanying
5 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
6 
7 #if defined(HAVE_CONFIG_H)
8 #include "config/pivx-config.h"
9 #endif
10 
11 #include "intro.h"
12 #include "ui_intro.h"
13 
14 #include "fs.h"
15 #include "guiutil.h"
16 
17 #include "qtutils.h"
18 #include "util/system.h"
19 
20 #include <QFileDialog>
21 #include <QMessageBox>
22 #include <QSettings>
23 
24 static const uint64_t GB_BYTES = 1000000000LL;
25 /* Minimum free space (in GB) needed for mainnet data directory */
26 static const uint64_t BLOCK_CHAIN_SIZE = 25;
27 /* Minimum free space (in GB) needed for testnet data directory */
28 static const uint64_t TESTNET_BLOCK_CHAIN_SIZE = 1;
29 /* Total required space (in GB) depending on network */
30 static uint64_t requiredSpace;
31 
32 /* Check free space asynchronously to prevent hanging the UI thread.
33 
34  Up to one request to check a path is in flight to this thread; when the check()
35  function runs, the current path is requested from the associated Intro object.
36  The reply is sent back through a signal.
37 
38  This ensures that no queue of checking requests is built up while the user is
39  still entering the path, and that always the most recently entered path is checked as
40  soon as the thread becomes available.
41 */
42 class FreespaceChecker : public QObject
43 {
44  Q_OBJECT
45 
46 public:
47  explicit FreespaceChecker(Intro* intro);
48 
49  enum Status {
51  ST_ERROR
52  };
53 
54 public Q_SLOTS:
55  void check();
56 
57 Q_SIGNALS:
58  void reply(int status, const QString& message, quint64 available);
59 
60 private:
62 };
63 
64 #include "intro.moc"
65 
67 {
68  this->intro = intro;
69 }
70 
72 {
73  QString dataDirStr = intro->getPathToCheck();
74  fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr);
75  uint64_t freeBytesAvailable = 0;
76  int replyStatus = ST_OK;
77  QString replyMessage = tr("A new data directory will be created.");
78 
79  /* Find first parent that exists, so that fs::space does not fail */
80  fs::path parentDir = dataDir;
81  fs::path parentDirOld = fs::path();
82  while (parentDir.has_parent_path() && !fs::exists(parentDir)) {
83  parentDir = parentDir.parent_path();
84 
85  /* Check if we make any progress, break if not to prevent an infinite loop here */
86  if (parentDirOld == parentDir)
87  break;
88 
89  parentDirOld = parentDir;
90  }
91 
92  try {
93  freeBytesAvailable = fs::space(parentDir).available;
94  if (fs::exists(dataDir)) {
95  if (fs::is_directory(dataDir)) {
96  QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
97  replyStatus = ST_OK;
98  replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
99  } else {
100  replyStatus = ST_ERROR;
101  replyMessage = tr("Path already exists, and is not a directory.");
102  }
103  }
104  } catch (const fs::filesystem_error& e) {
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 
113 Intro::Intro(QWidget* parent) : QDialog(parent, Qt::WindowSystemMenuHint | Qt::WindowTitleHint | Qt::WindowCloseButtonHint),
114  ui(new Ui::Intro),
115  thread(0),
116  signalled(false)
117 {
118  ui->setupUi(this);
119  this->setStyleSheet(GUIUtil::loadStyleSheet());
120 
121  setCssProperty(ui->frame, "container-welcome-step2");
122  setCssProperty(ui->container, "container-welcome-stack");
123  setCssProperty(ui->frame_2, "container-welcome");
124  setCssProperty(ui->welcomeLabel, "text-title-welcome");
125  setCssProperty(ui->storageLabel, "text-intro-white");
126  setCssProperty(ui->sizeWarningLabel, "text-intro-white");
127  setCssProperty(ui->freeSpace, "text-intro-white");
128  setCssProperty(ui->errorMessage, "text-intro-white");
129 
130  setCssProperty({ui->dataDirDefault, ui->dataDirCustom}, "radio-welcome");
131  setCssProperty(ui->dataDirectory, "edit-primary-welcome");
132  ui->dataDirectory->setAttribute(Qt::WA_MacShowFocusRect, 0);
133  setCssProperty(ui->ellipsisButton, "btn-dots-welcome");
134  setCssBtnPrimary(ui->pushButtonOk);
135  setCssBtnSecondary(ui->pushButtonCancel);
136 
137  connect(ui->pushButtonOk, &QPushButton::clicked, this, &Intro::accept);
138  connect(ui->pushButtonCancel, &QPushButton::clicked, this, &Intro::close);
139 
140  ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(PACKAGE_NAME));
141  ui->storageLabel->setText(ui->storageLabel->text().arg(PACKAGE_NAME));
142  ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(PACKAGE_NAME).arg(requiredSpace));
143  startThread();
144 }
145 
147 {
148  delete ui;
149  /* Ensure thread is finished before it is deleted */
150  Q_EMIT stopThread();
151  thread->wait();
152 }
153 
155 {
156  return ui->dataDirectory->text();
157 }
158 
159 void Intro::setDataDirectory(const QString& dataDir)
160 {
161  ui->dataDirectory->setText(dataDir);
162  if (dataDir == getDefaultDataDirectory()) {
163  ui->dataDirDefault->setChecked(true);
164  ui->dataDirectory->setEnabled(false);
165  ui->ellipsisButton->setEnabled(false);
166  updateDataDirStatus(false);
167  } else {
168  ui->dataDirCustom->setChecked(true);
169  ui->dataDirectory->setEnabled(true);
170  ui->ellipsisButton->setEnabled(true);
171  updateDataDirStatus(true);
172  }
173 }
174 
176 {
178 }
179 
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  /* 1) Default data directory for operating system */
188  QString dataDir = getDefaultDataDirectory();
189  /* 2) Allow QSettings to override default dir */
190  dataDir = settings.value("strDataDir", dataDir).toString();
191 
192 
193  if (!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || gArgs.GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR)) {
194  // If current default data directory does not exist, let the user choose one
195  if (gArgs.GetBoolArg("-testnet", false)) {
196  requiredSpace = TESTNET_BLOCK_CHAIN_SIZE;
197  } else if (gArgs.GetBoolArg("-regtest", false)) {
198  requiredSpace = 0;
199  } else {
200  requiredSpace = BLOCK_CHAIN_SIZE;
201  }
202  Intro intro;
203  intro.setDataDirectory(dataDir);
204  intro.setWindowIcon(QIcon(":icons/bitcoin"));
205 
206  while (true) {
207  if (!intro.exec()) {
208  // Cancel clicked
209  return false;
210  }
211  dataDir = intro.getDataDirectory();
212  try {
214  // If a new data directory has been created, make wallets subdirectory too
216  }
217  break;
218  } catch (const fs::filesystem_error& e) {
219  QMessageBox::critical(nullptr, PACKAGE_NAME,
220  tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
221  // fall through, back to choosing screen
222  }
223  }
224 
225  settings.setValue("strDataDir", dataDir);
226  }
227 
228  /* Only override -datadir if different from the default, to make it possible to
229  * override -datadir in the pivx.conf file in the default data directory
230  * (to be consistent with pivxd behavior)
231  */
232 
233  if (dataDir != getDefaultDataDirectory())
234  gArgs.SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting
235  return true;
236 }
237 
238 void Intro::setStatus(int status, const QString& message, quint64 bytesAvailable)
239 {
240  switch (status) {
242  ui->errorMessage->setText(message);
243  ui->errorMessage->setStyleSheet("");
244  break;
246  ui->errorMessage->setText(tr("Error") + ": " + message);
247  ui->errorMessage->setStyleSheet("QLabel { color: #f84444 }");
248  break;
249  }
250  /* Indicate number of bytes available */
251  if (status == FreespaceChecker::ST_ERROR) {
252  ui->freeSpace->setText("");
253  } else {
254  QString freeString = tr("%1 GB of free space available").arg(bytesAvailable / GB_BYTES);
255  if (bytesAvailable < requiredSpace * GB_BYTES) {
256  freeString += " " + tr("(of %1 GB needed)").arg(requiredSpace);
257  ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
258  } else {
259  ui->freeSpace->setStyleSheet("");
260  }
261  ui->freeSpace->setText(freeString + ".");
262  }
263  /* Don't allow confirm in ERROR state */
264  ui->pushButtonOk->setEnabled(status != FreespaceChecker::ST_ERROR);
265 }
266 
267 void Intro::updateDataDirStatus(bool enabled){
268  if(enabled){
269  setCssProperty(ui->dataDirectory, "edit-primary-welcome", true);
270  } else {
271  setCssProperty(ui->dataDirectory, "edit-primary-welcome-disabled", true);
272 
273  }
274 }
275 
276 void Intro::on_dataDirectory_textChanged(const QString& dataDirStr)
277 {
278  /* Disable OK button until check result comes in */
279  ui->pushButtonOk->setEnabled(false);
280  checkPath(dataDirStr);
281 }
282 
284 {
285  QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
286  if (!dir.isEmpty())
287  ui->dataDirectory->setText(dir);
288 }
289 
291 {
293  updateDataDirStatus(false);
294 }
295 
297 {
298  ui->dataDirectory->setEnabled(true);
299  ui->ellipsisButton->setEnabled(true);
300  updateDataDirStatus(true);
301 }
302 
304 {
305  thread = new QThread(this);
306  FreespaceChecker* executor = new FreespaceChecker(this);
307  executor->moveToThread(thread);
308 
309  connect(executor, &FreespaceChecker::reply, this, &Intro::setStatus);
310  connect(this, &Intro::requestCheck, executor, &FreespaceChecker::check);
311  /* make sure executor object is deleted in its own thread */
312  connect(this, &Intro::stopThread, executor, &QObject::deleteLater);
313  connect(this, &Intro::stopThread, thread, &QThread::quit);
314 
315  thread->start();
316 }
317 
318 void Intro::checkPath(const QString& dataDir)
319 {
320  mutex.lock();
321  pathToCheck = dataDir;
322  if (!signalled) {
323  signalled = true;
324  Q_EMIT requestCheck();
325  }
326  mutex.unlock();
327 }
328 
330 {
331  QString retval;
332  mutex.lock();
333  retval = pathToCheck;
334  signalled = false; /* new request can be queued now */
335  mutex.unlock();
336  return retval;
337 }
false
Definition: bls_dkg.cpp:151
bool SoftSetArg(const std::string &strArg, const std::string &strValue)
Set an argument if it doesn't already have a value.
Definition: system.cpp:473
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: system.cpp:449
bool GetBoolArg(const std::string &strArg, bool fDefault) const
Return boolean argument or default value.
Definition: system.cpp:465
FreespaceChecker(Intro *intro)
Definition: intro.cpp:66
Intro * intro
Definition: intro.cpp:61
void reply(int status, const QString &message, quint64 available)
void check()
Definition: intro.cpp:71
Introduction screen (pre-GUI startup).
Definition: intro.h:27
~Intro()
Definition: intro.cpp:146
void stopThread()
void setStatus(int status, const QString &message, quint64 bytesAvailable)
Definition: intro.cpp:238
void on_ellipsisButton_clicked()
Definition: intro.cpp:283
QMutex mutex
Definition: intro.h:69
void setDataDirectory(const QString &dataDir)
Definition: intro.cpp:159
static bool pickDataDirectory()
Determine data directory.
Definition: intro.cpp:180
QString pathToCheck
Definition: intro.h:71
friend class FreespaceChecker
Definition: intro.h:78
void on_dataDirectory_textChanged(const QString &arg1)
Definition: intro.cpp:276
Intro(QWidget *parent=0)
Definition: intro.cpp:113
void updateDataDirStatus(bool enabled)
Definition: intro.cpp:267
bool signalled
Definition: intro.h:70
static QString getDefaultDataDirectory()
Determine default data directory for operating system.
Definition: intro.cpp:175
QString getPathToCheck()
Definition: intro.cpp:329
Ui::Intro * ui
Definition: intro.h:67
void requestCheck()
QString getDataDirectory()
Definition: intro.cpp:154
void checkPath(const QString &dataDir)
Definition: intro.cpp:318
void startThread()
Definition: intro.cpp:303
void on_dataDirDefault_clicked()
Definition: intro.cpp:290
QThread * thread
Definition: intro.h:68
void on_dataDirCustom_clicked()
Definition: intro.cpp:296
fs::path qstringToBoostPath(const QString &path)
Definition: guiutil.cpp:676
QString loadStyleSheet()
Load global CSS theme.
Definition: guiutil.cpp:639
QString boostPathToQString(const fs::path &path)
Definition: guiutil.cpp:681
#define PACKAGE_NAME
Definition: pivx-config.h:366
QSettings * settings
Definition: qtutils.cpp:197
void setCssProperty(std::initializer_list< QWidget * > args, const QString &value)
Definition: qtutils.cpp:334
void setCssBtnPrimary(QPushButton *btn, bool forceUpdate)
Definition: qtutils.cpp:302
void setCssBtnSecondary(QPushButton *btn, bool forceUpdate)
Definition: qtutils.cpp:307
fs::path GetDefaultDataDir()
Definition: system.cpp:533
bool TryCreateDirectories(const fs::path &p)
Ignores exceptions thrown by Boost's create_directories if the requested directory exists.
Definition: system.cpp:891
ArgsManager gArgs
Definition: system.cpp:89