выбор папки для базы блоков из GUI
[novacoin.git] / src / qt / intro.cpp
diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp
new file mode 100644 (file)
index 0000000..fe31dd0
--- /dev/null
@@ -0,0 +1,292 @@
+// Copyright (c) 2011-2014 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "intro.h"
+#include "ui_intro.h"
+
+#include "guiutil.h"
+
+#include "util.h"
+
+#include <boost/filesystem.hpp>
+
+#include <QFileDialog>
+#include <QSettings>
+#include <QMessageBox>
+
+/* Minimum free space (in bytes) needed for data directory */
+static const uint64_t GB_BYTES = 1000000000LL;
+static const uint64_t BLOCK_CHAIN_SIZE = 1LL * GB_BYTES;
+
+/* 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
+
+public:
+    FreespaceChecker(Intro *intro);
+
+    enum Status {
+        ST_OK,
+        ST_ERROR
+    };
+
+public slots:
+    void check();
+
+signals:
+    void reply(int status, const QString &message, quint64 available);
+
+private:
+    Intro *intro;
+};
+
+#include "intro.moc"
+
+FreespaceChecker::FreespaceChecker(Intro *intro)
+{
+    this->intro = intro;
+}
+
+void FreespaceChecker::check()
+{
+    namespace fs = boost::filesystem;
+    QString dataDirStr = intro->getPathToCheck();
+    fs::path dataDir = GUIUtil::qstringToBoostPath(dataDirStr);
+    uint64_t freeBytesAvailable = 0;
+    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;
+    fs::path parentDirOld = fs::path();
+    while(parentDir.has_parent_path() && !fs::exists(parentDir))
+    {
+        parentDir = parentDir.parent_path();
+
+        /* Check if we make any progress, break if not to prevent an infinite loop here */
+        if (parentDirOld == parentDir)
+            break;
+
+        parentDirOld = parentDir;
+    }
+
+    try {
+        freeBytesAvailable = fs::space(parentDir).available;
+        if(fs::exists(dataDir))
+        {
+            if(fs::is_directory(dataDir))
+            {
+                QString separator = "<code>" + QDir::toNativeSeparators("/") + tr("name") + "</code>";
+                replyStatus = ST_OK;
+                replyMessage = tr("Directory already exists. Add %1 if you intend to create a new directory here.").arg(separator);
+            } else {
+                replyStatus = ST_ERROR;
+                replyMessage = tr("Path already exists, and is not a directory.");
+            }
+        }
+    } catch (const fs::filesystem_error&)
+    {
+        /* Parent directory does not exist or is not accessible */
+        replyStatus = ST_ERROR;
+        replyMessage = tr("Cannot create data directory here.");
+    }
+    emit reply(replyStatus, replyMessage, freeBytesAvailable);
+}
+
+
+Intro::Intro(QWidget *parent) :
+    QDialog(parent),
+    ui(new Ui::Intro),
+    thread(0),
+    signalled(false)
+{
+    ui->setupUi(this);
+    ui->sizeWarningLabel->setText(ui->sizeWarningLabel->text().arg(BLOCK_CHAIN_SIZE/GB_BYTES));
+    startThread();
+}
+
+Intro::~Intro()
+{
+    delete ui;
+    /* Ensure thread is finished before it is deleted */
+    emit stopThread();
+    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()
+{
+    return GUIUtil::boostPathToQString(GetDefaultDataDir());
+}
+
+void Intro::pickDataDirectory()
+{
+    namespace fs = boost::filesystem;
+    QSettings settings;
+    /* If data directory provided on command line, no need to look at settings
+       or show a picking dialog */
+    if(!GetArg("-datadir", "").empty())
+        return;
+    /* 1) Default data directory for operating system */
+    QString dataDir = getDefaultDataDirectory();
+    /* 2) Allow QSettings to override default dir */
+    dataDir = settings.value("strDataDir", dataDir).toString();
+
+    if(!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || GetBoolArg("-choosedatadir", false))
+    {
+        /* If current default data directory does not exist, let the user choose one */
+        Intro intro;
+        intro.setDataDirectory(dataDir);
+        intro.setWindowIcon(QIcon(":icons/bitcoin"));
+
+        while(true)
+        {
+            if(!intro.exec())
+            {
+                /* Cancel clicked */
+                exit(0);
+            }
+            dataDir = intro.getDataDirectory();
+            try {
+                boost::filesystem::create_directory(GUIUtil::qstringToBoostPath(dataDir));
+                break;
+            } catch (const fs::filesystem_error&) {
+                QMessageBox::critical(0, tr("Novacoin-qt"),
+                    tr("Error: Specified data directory \"%1\" cannot be created.").arg(dataDir));
+                /* fall through, back to choosing screen */
+            }
+        }
+
+        settings.setValue("strDataDir", dataDir);
+    }
+    /* 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
+}
+
+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 {
+        QString freeString = tr("%n GB of free space available", "", bytesAvailable/GB_BYTES);
+        if(bytesAvailable < BLOCK_CHAIN_SIZE)
+        {
+            freeString += " " + tr("(of %n GB needed)", "", BLOCK_CHAIN_SIZE/GB_BYTES);
+            ui->freeSpace->setStyleSheet("QLabel { color: #800000 }");
+        } else {
+            ui->freeSpace->setStyleSheet("");
+        }
+        ui->freeSpace->setText(freeString + ".");
+    }
+    /* 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()
+{
+    QString dir = QDir::toNativeSeparators(QFileDialog::getExistingDirectory(0, "Choose data directory", ui->dataDirectory->text()));
+    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;
+        emit requestCheck();
+    }
+    mutex.unlock();
+}
+
+QString Intro::getPathToCheck()
+{
+    QString retval;
+    mutex.lock();
+    retval = pathToCheck;
+    signalled = false; /* new request can be queued now */
+    mutex.unlock();
+    return retval;
+}