Create walletdir if datadir doesn't exist and fix tests
[bitcoinplatinum.git] / src / qt / intro.cpp
blob7f8a8394e691a63476906a8bb01170e235bd92e8
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.
5 #if defined(HAVE_CONFIG_H)
6 #include <config/bitcoin-config.h>
7 #endif
9 #include <fs.h>
10 #include <qt/intro.h>
11 #include <qt/forms/ui_intro.h>
13 #include <qt/guiutil.h>
15 #include <util.h>
17 #include <QFileDialog>
18 #include <QSettings>
19 #include <QMessageBox>
21 #include <cmath>
23 static const uint64_t GB_BYTES = 1000000000LL;
24 /* Minimum free space (in GB) needed for data directory */
25 static const uint64_t BLOCK_CHAIN_SIZE = 150;
26 /* Minimum free space (in GB) needed for data directory when pruned; Does not include prune target */
27 static const uint64_t CHAIN_STATE_SIZE = 3;
28 /* Total required space (in GB) depending on user choice (prune, not prune) */
29 static uint64_t requiredSpace;
31 /* Check free space asynchronously to prevent hanging the UI thread.
33 Up to one request to check a path is in flight to this thread; when the check()
34 function runs, the current path is requested from the associated Intro object.
35 The reply is sent back through a signal.
37 This ensures that no queue of checking requests is built up while the user is
38 still entering the path, and that always the most recently entered path is checked as
39 soon as the thread becomes available.
41 class FreespaceChecker : public QObject
43 Q_OBJECT
45 public:
46 explicit FreespaceChecker(Intro *intro);
48 enum Status {
49 ST_OK,
50 ST_ERROR
53 public Q_SLOTS:
54 void check();
56 Q_SIGNALS:
57 void reply(int status, const QString &message, quint64 available);
59 private:
60 Intro *intro;
63 #include <qt/intro.moc>
65 FreespaceChecker::FreespaceChecker(Intro *_intro)
67 this->intro = _intro;
70 void FreespaceChecker::check()
72 QString dataDirStr = intro->getPathToCheck();
73 fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr);
74 uint64_t freeBytesAvailable = 0;
75 int replyStatus = ST_OK;
76 QString replyMessage = tr("A new data directory will be created.");
78 /* Find first parent that exists, so that fs::space does not fail */
79 fs::path parentDir = dataDir;
80 fs::path parentDirOld = fs::path();
81 while(parentDir.has_parent_path() && !fs::exists(parentDir))
83 parentDir = parentDir.parent_path();
85 /* Check if we make any progress, break if not to prevent an infinite loop here */
86 if (parentDirOld == parentDir)
87 break;
89 parentDirOld = parentDir;
92 try {
93 freeBytesAvailable = fs::space(parentDir).available;
94 if(fs::exists(dataDir))
96 if(fs::is_directory(dataDir))
98 QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
99 replyStatus = ST_OK;
100 replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
101 } else {
102 replyStatus = ST_ERROR;
103 replyMessage = tr("Path already exists, and is not a directory.");
106 } catch (const fs::filesystem_error&)
108 /* Parent directory does not exist or is not accessible */
109 replyStatus = ST_ERROR;
110 replyMessage = tr("Cannot create data directory here.");
112 Q_EMIT reply(replyStatus, replyMessage, freeBytesAvailable);
116 Intro::Intro(QWidget *parent) :
117 QDialog(parent),
118 ui(new Ui::Intro),
119 thread(0),
120 signalled(false)
122 ui->setupUi(this);
123 ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(tr(PACKAGE_NAME)));
124 ui->storageLabel->setText(ui->storageLabel->text().arg(tr(PACKAGE_NAME)));
126 ui->lblExplanation1->setText(ui->lblExplanation1->text()
127 .arg(tr(PACKAGE_NAME))
128 .arg(BLOCK_CHAIN_SIZE)
129 .arg(2009)
130 .arg(tr("Bitcoin"))
132 ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(tr(PACKAGE_NAME)));
134 uint64_t pruneTarget = std::max<int64_t>(0, gArgs.GetArg("-prune", 0));
135 requiredSpace = BLOCK_CHAIN_SIZE;
136 QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time.");
137 if (pruneTarget) {
138 uint64_t prunedGBs = std::ceil(pruneTarget * 1024 * 1024.0 / GB_BYTES);
139 if (prunedGBs <= requiredSpace) {
140 requiredSpace = prunedGBs;
141 storageRequiresMsg = tr("Approximately %1 GB of data will be stored in this directory.");
143 ui->lblExplanation3->setVisible(true);
144 } else {
145 ui->lblExplanation3->setVisible(false);
147 requiredSpace += CHAIN_STATE_SIZE;
148 ui->sizeWarningLabel->setText(
149 tr("%1 will download and store a copy of the Bitcoin block chain.").arg(tr(PACKAGE_NAME)) + " " +
150 storageRequiresMsg.arg(requiredSpace) + " " +
151 tr("The wallet will also be stored in this directory.")
153 startThread();
156 Intro::~Intro()
158 delete ui;
159 /* Ensure thread is finished before it is deleted */
160 Q_EMIT stopThread();
161 thread->wait();
164 QString Intro::getDataDirectory()
166 return ui->dataDirectory->text();
169 void Intro::setDataDirectory(const QString &dataDir)
171 ui->dataDirectory->setText(dataDir);
172 if(dataDir == getDefaultDataDirectory())
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);
184 QString Intro::getDefaultDataDirectory()
186 return GUIUtil::boostPathToQString(GetDefaultDataDir());
189 bool Intro::pickDataDirectory()
191 QSettings settings;
192 /* If data directory provided on command line, no need to look at settings
193 or show a picking dialog */
194 if(!gArgs.GetArg("-datadir", "").empty())
195 return true;
196 /* 1) Default data directory for operating system */
197 QString dataDir = getDefaultDataDirectory();
198 /* 2) Allow QSettings to override default dir */
199 dataDir = settings.value("strDataDir", dataDir).toString();
201 if(!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || gArgs.GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR) || settings.value("fReset", false).toBool() || gArgs.GetBoolArg("-resetguisettings", false))
203 /* If current default data directory does not exist, let the user choose one */
204 Intro intro;
205 intro.setDataDirectory(dataDir);
206 intro.setWindowIcon(QIcon(":icons/bitcoin"));
208 while(true)
210 if(!intro.exec())
212 /* Cancel clicked */
213 return false;
215 dataDir = intro.getDataDirectory();
216 try {
217 if (TryCreateDirectories(GUIUtil::qstringToBoostPath(dataDir))) {
218 // If a new data directory has been created, make wallets subdirectory too
219 TryCreateDirectories(GUIUtil::qstringToBoostPath(dataDir) / "wallets");
221 break;
222 } catch (const fs::filesystem_error&) {
223 QMessageBox::critical(0, tr(PACKAGE_NAME),
224 tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
225 /* fall through, back to choosing screen */
229 settings.setValue("strDataDir", dataDir);
230 settings.setValue("fReset", false);
232 /* Only override -datadir if different from the default, to make it possible to
233 * override -datadir in the bitcoin.conf file in the default data directory
234 * (to be consistent with bitcoind behavior)
236 if(dataDir != getDefaultDataDirectory())
237 gArgs.SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting
238 return true;
241 void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
243 switch(status)
245 case FreespaceChecker::ST_OK:
246 ui->errorMessage->setText(message);
247 ui->errorMessage->setStyleSheet("");
248 break;
249 case FreespaceChecker::ST_ERROR:
250 ui->errorMessage->setText(tr("Error") + ": " + message);
251 ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
252 break;
254 /* Indicate number of bytes available */
255 if(status == FreespaceChecker::ST_ERROR)
257 ui->freeSpace->setText("");
258 } else {
259 QString freeString = tr("%n GB of free space available", "", bytesAvailable/GB_BYTES);
260 if(bytesAvailable < requiredSpace * GB_BYTES)
262 freeString += " " + tr("(of %n GB needed)", "", requiredSpace);
263 ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
264 } else {
265 ui->freeSpace->setStyleSheet("");
267 ui->freeSpace->setText(freeString + ".");
269 /* Don't allow confirm in ERROR state */
270 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
273 void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
275 /* Disable OK button until check result comes in */
276 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
277 checkPath(dataDirStr);
280 void Intro::on_ellipsisButton_clicked()
282 QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
283 if(!dir.isEmpty())
284 ui->dataDirectory->setText(dir);
287 void Intro::on_dataDirDefault_clicked()
289 setDataDirectory(getDefaultDataDirectory());
292 void Intro::on_dataDirCustom_clicked()
294 ui->dataDirectory->setEnabled(true);
295 ui->ellipsisButton->setEnabled(true);
298 void Intro::startThread()
300 thread = new QThread(this);
301 FreespaceChecker *executor = new FreespaceChecker(this);
302 executor->moveToThread(thread);
304 connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64)));
305 connect(this, SIGNAL(requestCheck()), executor, SLOT(check()));
306 /* make sure executor object is deleted in its own thread */
307 connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater()));
308 connect(this, SIGNAL(stopThread()), thread, SLOT(quit()));
310 thread->start();
313 void Intro::checkPath(const QString &dataDir)
315 mutex.lock();
316 pathToCheck = dataDir;
317 if(!signalled)
319 signalled = true;
320 Q_EMIT requestCheck();
322 mutex.unlock();
325 QString Intro::getPathToCheck()
327 QString retval;
328 mutex.lock();
329 retval = pathToCheck;
330 signalled = false; /* new request can be queued now */
331 mutex.unlock();
332 return retval;