2016-12-31 19:01:21 +01:00
|
|
|
// Copyright (c) 2011-2016 The Bitcoin Core developers
|
2014-12-13 05:09:33 +01:00
|
|
|
// Distributed under the MIT software license, see the accompanying
|
2013-11-04 16:20:43 +01:00
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
2015-12-09 11:53:12 +01:00
|
|
|
#if defined(HAVE_CONFIG_H)
|
|
|
|
#include "config/bitcoin-config.h"
|
|
|
|
#endif
|
|
|
|
|
2017-03-01 16:54:22 +01:00
|
|
|
#include "fs.h"
|
2013-05-19 17:36:01 +02:00
|
|
|
#include "intro.h"
|
|
|
|
#include "ui_intro.h"
|
2013-04-13 07:13:08 +02:00
|
|
|
|
2014-03-22 10:22:42 +01:00
|
|
|
#include "guiutil.h"
|
|
|
|
|
2013-05-19 17:36:01 +02:00
|
|
|
#include "util.h"
|
|
|
|
|
|
|
|
#include <QFileDialog>
|
|
|
|
#include <QSettings>
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
2015-12-29 15:38:38 +01:00
|
|
|
#include <cmath>
|
|
|
|
|
2013-04-13 07:13:08 +02:00
|
|
|
static const uint64_t GB_BYTES = 1000000000LL;
|
2015-12-29 15:38:38 +01:00
|
|
|
/* Minimum free space (in GB) needed for data directory */
|
2017-02-08 05:07:13 +01:00
|
|
|
static const uint64_t BLOCK_CHAIN_SIZE = 120;
|
2015-12-29 15:38:38 +01:00
|
|
|
/* Minimum free space (in GB) needed for data directory when pruned; Does not include prune target */
|
|
|
|
static const uint64_t CHAIN_STATE_SIZE = 2;
|
|
|
|
/* Total required space (in GB) depending on user choice (prune, not prune) */
|
|
|
|
static uint64_t requiredSpace;
|
2013-05-19 17:36:01 +02:00
|
|
|
|
|
|
|
/* Check free space asynchronously to prevent hanging the UI thread.
|
|
|
|
|
|
|
|
Up to one request to check a path is in flight to this thread; when the check()
|
|
|
|
function runs, the current path is requested from the associated Intro object.
|
|
|
|
The reply is sent back through a signal.
|
|
|
|
|
|
|
|
This ensures that no queue of checking requests is built up while the user is
|
|
|
|
still entering the path, and that always the most recently entered path is checked as
|
|
|
|
soon as the thread becomes available.
|
|
|
|
*/
|
|
|
|
class FreespaceChecker : public QObject
|
|
|
|
{
|
|
|
|
Q_OBJECT
|
2013-07-23 12:00:41 +02:00
|
|
|
|
2013-05-19 17:36:01 +02:00
|
|
|
public:
|
|
|
|
FreespaceChecker(Intro *intro);
|
|
|
|
|
|
|
|
enum Status {
|
|
|
|
ST_OK,
|
|
|
|
ST_ERROR
|
|
|
|
};
|
|
|
|
|
2015-07-14 13:59:05 +02:00
|
|
|
public Q_SLOTS:
|
2013-05-19 17:36:01 +02:00
|
|
|
void check();
|
|
|
|
|
2015-07-14 13:59:05 +02:00
|
|
|
Q_SIGNALS:
|
2013-05-19 17:36:01 +02:00
|
|
|
void reply(int status, const QString &message, quint64 available);
|
|
|
|
|
|
|
|
private:
|
|
|
|
Intro *intro;
|
|
|
|
};
|
|
|
|
|
|
|
|
#include "intro.moc"
|
|
|
|
|
2016-09-09 13:43:29 +02:00
|
|
|
FreespaceChecker::FreespaceChecker(Intro *_intro)
|
2013-05-19 17:36:01 +02:00
|
|
|
{
|
2016-09-09 13:43:29 +02:00
|
|
|
this->intro = _intro;
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void FreespaceChecker::check()
|
|
|
|
{
|
|
|
|
QString dataDirStr = intro->getPathToCheck();
|
2014-03-22 10:22:42 +01:00
|
|
|
fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr);
|
2013-04-13 07:13:08 +02:00
|
|
|
uint64_t freeBytesAvailable = 0;
|
2013-05-19 17:36:01 +02:00
|
|
|
int replyStatus = ST_OK;
|
|
|
|
QString replyMessage = tr("A new data directory will be created.");
|
|
|
|
|
|
|
|
/* Find first parent that exists, so that fs::space does not fail */
|
|
|
|
fs::path parentDir = dataDir;
|
2013-07-23 12:00:41 +02:00
|
|
|
fs::path parentDirOld = fs::path();
|
2013-05-19 17:36:01 +02:00
|
|
|
while(parentDir.has_parent_path() && !fs::exists(parentDir))
|
|
|
|
{
|
|
|
|
parentDir = parentDir.parent_path();
|
2013-07-23 12:00:41 +02:00
|
|
|
|
|
|
|
/* Check if we make any progress, break if not to prevent an infinite loop here */
|
|
|
|
if (parentDirOld == parentDir)
|
|
|
|
break;
|
|
|
|
|
|
|
|
parentDirOld = parentDir;
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
freeBytesAvailable = fs::space(parentDir).available;
|
|
|
|
if(fs::exists(dataDir))
|
|
|
|
{
|
|
|
|
if(fs::is_directory(dataDir))
|
|
|
|
{
|
2013-07-29 16:09:15 +02:00
|
|
|
QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
|
2013-05-19 17:36:01 +02:00
|
|
|
replyStatus = ST_OK;
|
2013-07-29 16:09:15 +02:00
|
|
|
replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
|
2013-05-19 17:36:01 +02:00
|
|
|
} else {
|
|
|
|
replyStatus = ST_ERROR;
|
|
|
|
replyMessage = tr("Path already exists, and is not a directory.");
|
|
|
|
}
|
|
|
|
}
|
2014-12-07 13:29:06 +01:00
|
|
|
} catch (const fs::filesystem_error&)
|
2013-05-19 17:36:01 +02:00
|
|
|
{
|
|
|
|
/* Parent directory does not exist or is not accessible */
|
|
|
|
replyStatus = ST_ERROR;
|
|
|
|
replyMessage = tr("Cannot create data directory here.");
|
|
|
|
}
|
2015-07-14 13:59:05 +02:00
|
|
|
Q_EMIT reply(replyStatus, replyMessage, freeBytesAvailable);
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Intro::Intro(QWidget *parent) :
|
|
|
|
QDialog(parent),
|
|
|
|
ui(new Ui::Intro),
|
|
|
|
thread(0),
|
|
|
|
signalled(false)
|
|
|
|
{
|
|
|
|
ui->setupUi(this);
|
2015-12-09 11:53:12 +01:00
|
|
|
ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(tr(PACKAGE_NAME)));
|
|
|
|
ui->storageLabel->setText(ui->storageLabel->text().arg(tr(PACKAGE_NAME)));
|
2017-02-08 05:21:04 +01:00
|
|
|
|
|
|
|
ui->lblExplanation1->setText(ui->lblExplanation1->text()
|
|
|
|
.arg(tr(PACKAGE_NAME))
|
|
|
|
.arg(BLOCK_CHAIN_SIZE)
|
|
|
|
.arg(2009)
|
|
|
|
.arg(tr("Bitcoin"))
|
|
|
|
);
|
|
|
|
ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(tr(PACKAGE_NAME)));
|
|
|
|
|
2015-12-29 15:38:38 +01:00
|
|
|
uint64_t pruneTarget = std::max<int64_t>(0, GetArg("-prune", 0));
|
|
|
|
requiredSpace = BLOCK_CHAIN_SIZE;
|
2017-02-08 05:05:55 +01:00
|
|
|
QString storageRequiresMsg = tr("At least %1 GB of data will be stored in this directory, and it will grow over time.");
|
2017-02-08 05:00:16 +01:00
|
|
|
if (pruneTarget) {
|
2017-02-08 20:16:00 +01:00
|
|
|
uint64_t prunedGBs = std::ceil(pruneTarget * 1024 * 1024.0 / GB_BYTES);
|
|
|
|
if (prunedGBs <= requiredSpace) {
|
|
|
|
requiredSpace = prunedGBs;
|
2017-02-08 05:05:55 +01:00
|
|
|
storageRequiresMsg = tr("Approximately %1 GB of data will be stored in this directory.");
|
2017-02-08 20:16:00 +01:00
|
|
|
}
|
2017-02-08 05:21:04 +01:00
|
|
|
ui->lblExplanation3->setVisible(true);
|
|
|
|
} else {
|
|
|
|
ui->lblExplanation3->setVisible(false);
|
2017-02-08 05:00:16 +01:00
|
|
|
}
|
|
|
|
requiredSpace += CHAIN_STATE_SIZE;
|
2017-02-08 04:56:52 +01:00
|
|
|
ui->sizeWarningLabel->setText(
|
|
|
|
tr("%1 will download and store a copy of the Bitcoin block chain.").arg(tr(PACKAGE_NAME)) + " " +
|
2017-02-08 05:05:55 +01:00
|
|
|
storageRequiresMsg.arg(requiredSpace) + " " +
|
2017-02-08 04:56:52 +01:00
|
|
|
tr("The wallet will also be stored in this directory.")
|
|
|
|
);
|
2013-05-19 17:36:01 +02:00
|
|
|
startThread();
|
|
|
|
}
|
|
|
|
|
|
|
|
Intro::~Intro()
|
|
|
|
{
|
|
|
|
delete ui;
|
|
|
|
/* Ensure thread is finished before it is deleted */
|
2015-07-14 13:59:05 +02:00
|
|
|
Q_EMIT stopThread();
|
2013-05-19 17:36:01 +02:00
|
|
|
thread->wait();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Intro::getDataDirectory()
|
|
|
|
{
|
|
|
|
return ui->dataDirectory->text();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Intro::setDataDirectory(const QString &dataDir)
|
|
|
|
{
|
|
|
|
ui->dataDirectory->setText(dataDir);
|
|
|
|
if(dataDir == getDefaultDataDirectory())
|
|
|
|
{
|
|
|
|
ui->dataDirDefault->setChecked(true);
|
|
|
|
ui->dataDirectory->setEnabled(false);
|
|
|
|
ui->ellipsisButton->setEnabled(false);
|
|
|
|
} else {
|
|
|
|
ui->dataDirCustom->setChecked(true);
|
|
|
|
ui->dataDirectory->setEnabled(true);
|
|
|
|
ui->ellipsisButton->setEnabled(true);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Intro::getDefaultDataDirectory()
|
|
|
|
{
|
2014-03-22 10:22:42 +01:00
|
|
|
return GUIUtil::boostPathToQString(GetDefaultDataDir());
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
|
|
|
|
2016-08-18 16:58:04 +02:00
|
|
|
bool Intro::pickDataDirectory()
|
2013-05-19 17:36:01 +02:00
|
|
|
{
|
|
|
|
QSettings settings;
|
|
|
|
/* If data directory provided on command line, no need to look at settings
|
|
|
|
or show a picking dialog */
|
|
|
|
if(!GetArg("-datadir", "").empty())
|
2016-08-18 16:58:04 +02:00
|
|
|
return true;
|
2013-05-19 17:36:01 +02:00
|
|
|
/* 1) Default data directory for operating system */
|
|
|
|
QString dataDir = getDefaultDataDirectory();
|
|
|
|
/* 2) Allow QSettings to override default dir */
|
|
|
|
dataDir = settings.value("strDataDir", dataDir).toString();
|
|
|
|
|
2016-08-10 21:35:22 +02:00
|
|
|
if(!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR) || settings.value("fReset", false).toBool() || GetBoolArg("-resetguisettings", false))
|
2013-05-19 17:36:01 +02:00
|
|
|
{
|
|
|
|
/* If current default data directory does not exist, let the user choose one */
|
|
|
|
Intro intro;
|
|
|
|
intro.setDataDirectory(dataDir);
|
2015-07-28 15:20:14 +02:00
|
|
|
intro.setWindowIcon(QIcon(":icons/bitcoin"));
|
2013-08-24 15:20:37 +02:00
|
|
|
|
2013-05-19 17:36:01 +02:00
|
|
|
while(true)
|
|
|
|
{
|
|
|
|
if(!intro.exec())
|
|
|
|
{
|
|
|
|
/* Cancel clicked */
|
2016-08-18 16:58:04 +02:00
|
|
|
return false;
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
|
|
|
dataDir = intro.getDataDirectory();
|
|
|
|
try {
|
2014-03-24 02:14:43 +01:00
|
|
|
TryCreateDirectory(GUIUtil::qstringToBoostPath(dataDir));
|
2013-05-19 17:36:01 +02:00
|
|
|
break;
|
2014-12-07 13:29:06 +01:00
|
|
|
} catch (const fs::filesystem_error&) {
|
2015-12-09 11:53:12 +01:00
|
|
|
QMessageBox::critical(0, tr(PACKAGE_NAME),
|
2014-07-31 16:56:14 +02:00
|
|
|
tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
|
2013-05-19 17:36:01 +02:00
|
|
|
/* fall through, back to choosing screen */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
settings.setValue("strDataDir", dataDir);
|
2016-08-10 21:35:22 +02:00
|
|
|
settings.setValue("fReset", false);
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
2014-03-25 09:26:11 +01:00
|
|
|
/* Only override -datadir if different from the default, to make it possible to
|
|
|
|
* override -datadir in the bitcoin.conf file in the default data directory
|
|
|
|
* (to be consistent with bitcoind behavior)
|
|
|
|
*/
|
|
|
|
if(dataDir != getDefaultDataDirectory())
|
|
|
|
SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); // use OS locale for path setting
|
2016-08-18 16:58:04 +02:00
|
|
|
return true;
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void Intro::setStatus(int status, const QString &message, quint64 bytesAvailable)
|
|
|
|
{
|
|
|
|
switch(status)
|
|
|
|
{
|
|
|
|
case FreespaceChecker::ST_OK:
|
|
|
|
ui->errorMessage->setText(message);
|
|
|
|
ui->errorMessage->setStyleSheet("");
|
|
|
|
break;
|
|
|
|
case FreespaceChecker::ST_ERROR:
|
|
|
|
ui->errorMessage->setText(tr("Error") + ": " + message);
|
|
|
|
ui->errorMessage->setStyleSheet("QLabel { color: #800000 }");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
/* Indicate number of bytes available */
|
|
|
|
if(status == FreespaceChecker::ST_ERROR)
|
|
|
|
{
|
|
|
|
ui->freeSpace->setText("");
|
|
|
|
} else {
|
2014-10-21 15:40:00 +02:00
|
|
|
QString freeString = tr("%n GB of free space available", "", bytesAvailable/GB_BYTES);
|
2015-12-29 15:38:38 +01:00
|
|
|
if(bytesAvailable < requiredSpace * GB_BYTES)
|
2013-05-19 17:36:01 +02:00
|
|
|
{
|
2015-12-29 15:38:38 +01:00
|
|
|
freeString += " " + tr("(of %n GB needed)", "", requiredSpace);
|
2013-05-19 17:36:01 +02:00
|
|
|
ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
|
|
|
|
} else {
|
|
|
|
ui->freeSpace->setStyleSheet("");
|
|
|
|
}
|
2013-07-23 12:00:41 +02:00
|
|
|
ui->freeSpace->setText(freeString + ".");
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
|
|
|
/* Don't allow confirm in ERROR state */
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(status != FreespaceChecker::ST_ERROR);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Intro::on_dataDirectory_textChanged(const QString &dataDirStr)
|
|
|
|
{
|
|
|
|
/* Disable OK button until check result comes in */
|
|
|
|
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
|
|
|
checkPath(dataDirStr);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Intro::on_ellipsisButton_clicked()
|
|
|
|
{
|
2014-01-10 11:43:29 +01:00
|
|
|
QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
|
2013-05-19 17:36:01 +02:00
|
|
|
if(!dir.isEmpty())
|
|
|
|
ui->dataDirectory->setText(dir);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Intro::on_dataDirDefault_clicked()
|
|
|
|
{
|
|
|
|
setDataDirectory(getDefaultDataDirectory());
|
|
|
|
}
|
|
|
|
|
|
|
|
void Intro::on_dataDirCustom_clicked()
|
|
|
|
{
|
|
|
|
ui->dataDirectory->setEnabled(true);
|
|
|
|
ui->ellipsisButton->setEnabled(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Intro::startThread()
|
|
|
|
{
|
|
|
|
thread = new QThread(this);
|
|
|
|
FreespaceChecker *executor = new FreespaceChecker(this);
|
|
|
|
executor->moveToThread(thread);
|
|
|
|
|
|
|
|
connect(executor, SIGNAL(reply(int,QString,quint64)), this, SLOT(setStatus(int,QString,quint64)));
|
|
|
|
connect(this, SIGNAL(requestCheck()), executor, SLOT(check()));
|
|
|
|
/* make sure executor object is deleted in its own thread */
|
|
|
|
connect(this, SIGNAL(stopThread()), executor, SLOT(deleteLater()));
|
|
|
|
connect(this, SIGNAL(stopThread()), thread, SLOT(quit()));
|
|
|
|
|
|
|
|
thread->start();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Intro::checkPath(const QString &dataDir)
|
|
|
|
{
|
|
|
|
mutex.lock();
|
|
|
|
pathToCheck = dataDir;
|
|
|
|
if(!signalled)
|
|
|
|
{
|
|
|
|
signalled = true;
|
2015-07-14 13:59:05 +02:00
|
|
|
Q_EMIT requestCheck();
|
2013-05-19 17:36:01 +02:00
|
|
|
}
|
|
|
|
mutex.unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Intro::getPathToCheck()
|
|
|
|
{
|
|
|
|
QString retval;
|
|
|
|
mutex.lock();
|
|
|
|
retval = pathToCheck;
|
|
|
|
signalled = false; /* new request can be queued now */
|
|
|
|
mutex.unlock();
|
|
|
|
return retval;
|
|
|
|
}
|