LCOV - code coverage report
Current view: top level - src/qt - intro.cpp (source / functions) Hit Total Coverage
Test: total_coverage.info Lines: 0 202 0.0 %
Date: 2020-09-26 01:30:44 Functions: 0 27 0.0 %

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

Generated by: LCOV version 1.15