diff --git a/src/qt/intro.cpp b/src/qt/intro.cpp index d64b38597..73310c877 100644 --- a/src/qt/intro.cpp +++ b/src/qt/intro.cpp @@ -1,356 +1,377 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include +#include #include #include #include #include #include -/** - * Total required space (in GB) depending on user choice (prune, not prune). - */ -static uint64_t requiredSpace; - /* 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: explicit FreespaceChecker(Intro *intro); enum Status { ST_OK, ST_ERROR }; public Q_SLOTS: void check(); Q_SIGNALS: void reply(int status, const QString &message, quint64 available); private: Intro *intro; }; #include FreespaceChecker::FreespaceChecker(Intro *_intro) { this->intro = _intro; } void FreespaceChecker::check() { 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 = "" + QDir::toNativeSeparators("/") + tr("name") + ""; 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."); } Q_EMIT reply(replyStatus, replyMessage, freeBytesAvailable); } -Intro::Intro(QWidget *parent, uint64_t blockchain_size, - uint64_t chain_state_size) +namespace { +//! Return pruning size that will be used if automatic pruning is enabled. +int GetPruneTargetGB() { + int64_t prune_target_mib = gArgs.GetArg("-prune", 0); + // >1 means automatic pruning is enabled by config, 1 means manual pruning, + // 0 means no pruning. + return prune_target_mib > 1 ? PruneMiBtoGB(prune_target_mib) + : DEFAULT_PRUNE_TARGET_GB; +} +} // namespace + +Intro::Intro(QWidget *parent, int64_t blockchain_size_gb, + int64_t chain_state_size_gb) : QDialog(parent), ui(new Ui::Intro), thread(nullptr), signalled(false), - m_blockchain_size(blockchain_size), m_chain_state_size(chain_state_size) { + m_blockchain_size_gb(blockchain_size_gb), + m_chain_state_size_gb(chain_state_size_gb), + m_prune_target_gb(GetPruneTargetGB()) { ui->setupUi(this); ui->welcomeLabel->setText(ui->welcomeLabel->text().arg(PACKAGE_NAME)); ui->storageLabel->setText(ui->storageLabel->text().arg(PACKAGE_NAME)); ui->lblExplanation1->setText(ui->lblExplanation1->text() .arg(PACKAGE_NAME) - .arg(m_blockchain_size) + .arg(m_blockchain_size_gb) .arg(2009) .arg(tr("Bitcoin"))); ui->lblExplanation2->setText(ui->lblExplanation2->text().arg(PACKAGE_NAME)); - uint64_t pruneTarget = std::max(0, gArgs.GetArg("-prune", 0)); - // -prune=1 means enabled, above that it's a size in MB - if (pruneTarget > 1) { + // -prune=1 means enabled, above that it's a size in MiB + if (gArgs.GetArg("-prune", 0) > 1) { ui->prune->setChecked(true); ui->prune->setEnabled(false); } - ui->prune->setText( - tr("Discard blocks after verification, except most " - "recent %1 GB (prune)") - .arg(pruneTarget ? pruneTarget / 1000 : DEFAULT_PRUNE_TARGET_GB)); - requiredSpace = m_blockchain_size; - QString storageRequiresMsg = - tr("At least %1 GB of data will be stored in this directory, and it " - "will grow over time."); - if (pruneTarget) { - uint64_t prunedGBs = std::ceil(pruneTarget * 1024 * 1024.0 / GB_BYTES); - if (prunedGBs <= requiredSpace) { - requiredSpace = prunedGBs; - storageRequiresMsg = tr("Approximately %1 GB of data will be " - "stored in this directory."); - } - ui->lblExplanation3->setVisible(true); - } else { - ui->lblExplanation3->setVisible(false); - } - requiredSpace += m_chain_state_size; - ui->sizeWarningLabel->setText( - tr("%1 will download and store a copy of the Bitcoin block chain.") - .arg(PACKAGE_NAME) + - " " + storageRequiresMsg.arg(requiredSpace) + " " + - tr("The wallet will also be stored in this directory.")); - this->adjustSize(); + ui->prune->setText(tr("Discard blocks after verification, except most " + "recent %1 GB (prune)") + .arg(m_prune_target_gb)); + UpdatePruneLabels(ui->prune->isChecked()); + + connect(ui->prune, &QCheckBox::toggled, [this](bool prune_checked) { + UpdatePruneLabels(prune_checked); + UpdateFreeSpaceLabel(); + }); + startThread(); } Intro::~Intro() { delete ui; /* Ensure thread is finished before it is deleted */ thread->quit(); thread->wait(); } QString Intro::getDataDirectory() { return ui->dataDirectory->text(); } void Intro::setDataDirectory(const QString &dataDir) { ui->dataDirectory->setText(dataDir); if (dataDir == GUIUtil::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); } } bool Intro::showIfNeeded(bool &did_show_intro, bool &prune) { did_show_intro = false; QSettings settings; /* If data directory provided on command line, no need to look at settings or show a picking dialog */ if (!gArgs.GetArg("-datadir", "").empty()) { return true; } /* 1) Default data directory for operating system */ QString dataDir = GUIUtil::getDefaultDataDirectory(); /* 2) Allow QSettings to override default dir */ dataDir = settings.value("strDataDir", dataDir).toString(); if (!fs::exists(GUIUtil::qstringToBoostPath(dataDir)) || gArgs.GetBoolArg("-choosedatadir", DEFAULT_CHOOSE_DATADIR) || settings.value("fReset", false).toBool() || gArgs.GetBoolArg("-resetguisettings", false)) { /** * Use selectParams here to guarantee Params() can be used by node * interface. */ try { SelectParams(gArgs.GetChainName()); } catch (const std::exception &) { return false; } /** * If current default data directory does not exist, let the user choose * one. */ const CChainParams ¶ms = GetConfig().GetChainParams(); Intro intro(nullptr, params.AssumedBlockchainSize(), params.AssumedChainStateSize()); intro.setDataDirectory(dataDir); intro.setWindowIcon(QIcon(":icons/bitcoin")); did_show_intro = true; while (true) { if (!intro.exec()) { /* Cancel clicked */ return false; } dataDir = intro.getDataDirectory(); try { if (TryCreateDirectories( GUIUtil::qstringToBoostPath(dataDir))) { // If a new data directory has been created, make wallets // subdirectory too TryCreateDirectories(GUIUtil::qstringToBoostPath(dataDir) / "wallets"); } break; } catch (const fs::filesystem_error &) { QMessageBox::critical(nullptr, PACKAGE_NAME, tr("Error: Specified data directory " "\"%1\" cannot be created.") .arg(dataDir)); /* fall through, back to choosing screen */ } } // Additional preferences: prune = intro.ui->prune->isChecked(); settings.setValue("strDataDir", dataDir); settings.setValue("fReset", false); } /* 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 != GUIUtil::getDefaultDataDirectory()) { // use OS locale for path setting gArgs.SoftSetArg("-datadir", GUIUtil::qstringToBoostPath(dataDir).string()); } return true; } 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 < requiredSpace * GB_BYTES) { - freeString += " " + tr("(of %n GB needed)", "", requiredSpace); - ui->freeSpace->setStyleSheet("QLabel { color: #800000 }"); - } else if (bytesAvailable / GB_BYTES - requiredSpace < 10) { - freeString += - " " + tr("(%n GB needed for full chain)", "", requiredSpace); - ui->freeSpace->setStyleSheet("QLabel { color: #999900 }"); - } else { - ui->freeSpace->setStyleSheet(""); + m_bytes_available = bytesAvailable; + if (ui->prune->isEnabled()) { + ui->prune->setChecked( + m_bytes_available < + (m_blockchain_size_gb + m_chain_state_size_gb + 10) * GB_BYTES); } - ui->freeSpace->setText(freeString + "."); + UpdateFreeSpaceLabel(); } /* Don't allow confirm in ERROR state */ ui->buttonBox->button(QDialogButtonBox::Ok) ->setEnabled(status != FreespaceChecker::ST_ERROR); } +void Intro::UpdateFreeSpaceLabel() { + QString freeString = + tr("%n GB of free space available", "", m_bytes_available / GB_BYTES); + if (m_bytes_available < m_required_space_gb * GB_BYTES) { + freeString += " " + tr("(of %n GB needed)", "", m_required_space_gb); + ui->freeSpace->setStyleSheet("QLabel { color: #800000 }"); + } else if (m_bytes_available / GB_BYTES - m_required_space_gb < 10) { + freeString += + " " + tr("(%n GB needed for full chain)", "", m_required_space_gb); + ui->freeSpace->setStyleSheet("QLabel { color: #999900 }"); + } else { + ui->freeSpace->setStyleSheet(""); + } + ui->freeSpace->setText(freeString + "."); +} + 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( nullptr, "Choose data directory", ui->dataDirectory->text())); if (!dir.isEmpty()) { ui->dataDirectory->setText(dir); } } void Intro::on_dataDirDefault_clicked() { setDataDirectory(GUIUtil::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, &FreespaceChecker::reply, this, &Intro::setStatus); connect(this, &Intro::requestCheck, executor, &FreespaceChecker::check); /* make sure executor object is deleted in its own thread */ connect(thread, &QThread::finished, executor, &QObject::deleteLater); thread->start(); } void Intro::checkPath(const QString &dataDir) { mutex.lock(); pathToCheck = dataDir; if (!signalled) { signalled = true; Q_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; } + +void Intro::UpdatePruneLabels(bool prune_checked) { + m_required_space_gb = m_blockchain_size_gb + m_chain_state_size_gb; + QString storageRequiresMsg = + tr("At least %1 GB of data will be stored in this directory, and it " + "will grow over time."); + if (prune_checked && m_prune_target_gb <= m_blockchain_size_gb) { + m_required_space_gb = m_prune_target_gb + m_chain_state_size_gb; + storageRequiresMsg = + tr("Approximately %1 GB of data will be stored in this directory."); + } + ui->lblExplanation3->setVisible(prune_checked); + ui->sizeWarningLabel->setText( + tr("%1 will download and store a copy of the Bitcoin block chain.") + .arg(PACKAGE_NAME) + + " " + storageRequiresMsg.arg(m_required_space_gb) + " " + + tr("The wallet will also be stored in this directory.")); + this->adjustSize(); +} diff --git a/src/qt/intro.h b/src/qt/intro.h index 4139ede7e..663051255 100644 --- a/src/qt/intro.h +++ b/src/qt/intro.h @@ -1,81 +1,88 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_INTRO_H #define BITCOIN_QT_INTRO_H #include #include #include static const bool DEFAULT_CHOOSE_DATADIR = false; class FreespaceChecker; namespace interfaces { class Node; } namespace Ui { class Intro; } /** Introduction screen (pre-GUI startup). Allows the user to choose a data directory, in which the wallet and block chain will be stored. */ class Intro : public QDialog { Q_OBJECT public: - explicit Intro(QWidget *parent = nullptr, uint64_t blockchain_size = 0, - uint64_t chain_state_size = 0); + explicit Intro(QWidget *parent = nullptr, int64_t blockchain_size_gb = 0, + int64_t chain_state_size_gb = 0); ~Intro(); QString getDataDirectory(); void setDataDirectory(const QString &dataDir); /** * Determine data directory. Let the user choose if the current one doesn't * exist. * Let the user configure additional preferences such as pruning. * * @returns true if a data directory was selected, false if the user * cancelled the selection * dialog. * * @note do NOT call global GetDataDir() before calling this function, this * will cause the wrong path to be cached. */ static bool showIfNeeded(bool &did_show_intro, bool &prune); Q_SIGNALS: void requestCheck(); public Q_SLOTS: void setStatus(int status, const QString &message, quint64 bytesAvailable); private Q_SLOTS: void on_dataDirectory_textChanged(const QString &arg1); void on_ellipsisButton_clicked(); void on_dataDirDefault_clicked(); void on_dataDirCustom_clicked(); private: Ui::Intro *ui; QThread *thread; QMutex mutex; bool signalled; QString pathToCheck; - uint64_t m_blockchain_size; - uint64_t m_chain_state_size; + const int64_t m_blockchain_size_gb; + const int64_t m_chain_state_size_gb; + //! Total required space (in GB) depending on user choice (prune or not + //! prune). + int64_t m_required_space_gb{0}; + uint64_t m_bytes_available{0}; + const int64_t m_prune_target_gb; void startThread(); void checkPath(const QString &dataDir); QString getPathToCheck(); + void UpdatePruneLabels(bool prune_checked); + void UpdateFreeSpaceLabel(); friend class FreespaceChecker; }; #endif // BITCOIN_QT_INTRO_H diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 0ec618e74..8146717fc 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -1,595 +1,594 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #if defined(HAVE_CONFIG_H) #include #endif #include #include #include #include #include #include #include #include // for -dbcache defaults #include #include // For DEFAULT_SCRIPTCHECK_THREADS #include #include #include const char *DEFAULT_GUI_PROXY_HOST = "127.0.0.1"; static const QString GetDefaultProxyAddress(); OptionsModel::OptionsModel(QObject *parent, bool resetSettings) : QAbstractListModel(parent) { Init(resetSettings); } void OptionsModel::addOverriddenOption(const std::string &option) { strOverriddenByCommandLine += QString::fromStdString(option) + "=" + QString::fromStdString(gArgs.GetArg(option, "")) + " "; } // Writes all missing QSettings with their default values void OptionsModel::Init(bool resetSettings) { if (resetSettings) { Reset(); } checkAndMigrate(); QSettings settings; // Ensure restart flag is unset on client startup setRestartRequired(false); // These are Qt-only settings: // Window if (!settings.contains("fHideTrayIcon")) { settings.setValue("fHideTrayIcon", false); } fHideTrayIcon = settings.value("fHideTrayIcon").toBool(); Q_EMIT hideTrayIconChanged(fHideTrayIcon); if (!settings.contains("fMinimizeToTray")) { settings.setValue("fMinimizeToTray", false); } fMinimizeToTray = settings.value("fMinimizeToTray").toBool() && !fHideTrayIcon; if (!settings.contains("fMinimizeOnClose")) { settings.setValue("fMinimizeOnClose", false); } fMinimizeOnClose = settings.value("fMinimizeOnClose").toBool(); // Display if (!settings.contains("nDisplayUnit")) { settings.setValue("nDisplayUnit", BitcoinUnits::BCH); } nDisplayUnit = settings.value("nDisplayUnit").toInt(); if (!settings.contains("strThirdPartyTxUrls")) { settings.setValue("strThirdPartyTxUrls", ""); } strThirdPartyTxUrls = settings.value("strThirdPartyTxUrls", "").toString(); if (!settings.contains("fCoinControlFeatures")) { settings.setValue("fCoinControlFeatures", false); } fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool(); // These are shared with the core or have a command-line parameter // and we want command-line parameters to overwrite the GUI settings. // // If setting doesn't exist create it with defaults. // // If gArgs.SoftSetArg() or gArgs.SoftSetBoolArg() return false we were // overridden // by command-line and show this in the UI. // Main if (!settings.contains("bPrune")) { settings.setValue("bPrune", false); } if (!settings.contains("nPruneSize")) { settings.setValue("nPruneSize", DEFAULT_PRUNE_TARGET_GB); } SetPruneEnabled(settings.value("bPrune").toBool()); if (!settings.contains("nDatabaseCache")) { settings.setValue("nDatabaseCache", (qint64)DEFAULT_DB_CACHE_MB); } if (!gArgs.SoftSetArg( "-dbcache", settings.value("nDatabaseCache").toString().toStdString())) { addOverriddenOption("-dbcache"); } if (!settings.contains("nThreadsScriptVerif")) { settings.setValue("nThreadsScriptVerif", DEFAULT_SCRIPTCHECK_THREADS); } if (!gArgs.SoftSetArg( "-par", settings.value("nThreadsScriptVerif").toString().toStdString())) { addOverriddenOption("-par"); } if (!settings.contains("strDataDir")) { settings.setValue("strDataDir", GUIUtil::getDefaultDataDirectory()); } // Wallet #ifdef ENABLE_WALLET if (!settings.contains("bSpendZeroConfChange")) { settings.setValue("bSpendZeroConfChange", true); } if (!gArgs.SoftSetBoolArg( "-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool())) { addOverriddenOption("-spendzeroconfchange"); } #endif // Network if (!settings.contains("fUseUPnP")) { settings.setValue("fUseUPnP", DEFAULT_UPNP); } if (!gArgs.SoftSetBoolArg("-upnp", settings.value("fUseUPnP").toBool())) { addOverriddenOption("-upnp"); } if (!settings.contains("fListen")) { settings.setValue("fListen", DEFAULT_LISTEN); } if (!gArgs.SoftSetBoolArg("-listen", settings.value("fListen").toBool())) { addOverriddenOption("-listen"); } if (!settings.contains("fUseProxy")) { settings.setValue("fUseProxy", false); } if (!settings.contains("addrProxy")) { settings.setValue("addrProxy", GetDefaultProxyAddress()); } // Only try to set -proxy, if user has enabled fUseProxy if (settings.value("fUseProxy").toBool() && !gArgs.SoftSetArg( "-proxy", settings.value("addrProxy").toString().toStdString())) { addOverriddenOption("-proxy"); } else if (!settings.value("fUseProxy").toBool() && !gArgs.GetArg("-proxy", "").empty()) { addOverriddenOption("-proxy"); } if (!settings.contains("fUseSeparateProxyTor")) { settings.setValue("fUseSeparateProxyTor", false); } if (!settings.contains("addrSeparateProxyTor")) { settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress()); } // Only try to set -onion, if user has enabled fUseSeparateProxyTor if (settings.value("fUseSeparateProxyTor").toBool() && !gArgs.SoftSetArg( "-onion", settings.value("addrSeparateProxyTor").toString().toStdString())) { addOverriddenOption("-onion"); } else if (!settings.value("fUseSeparateProxyTor").toBool() && !gArgs.GetArg("-onion", "").empty()) { addOverriddenOption("-onion"); } // Display if (!settings.contains("language")) { settings.setValue("language", ""); } if (!gArgs.SoftSetArg( "-lang", settings.value("language").toString().toStdString())) { addOverriddenOption("-lang"); } language = settings.value("language").toString(); } /** * Helper function to copy contents from one QSettings to another. * By using allKeys this also covers nested settings in a hierarchy. */ static void CopySettings(QSettings &dst, const QSettings &src) { for (const QString &key : src.allKeys()) { dst.setValue(key, src.value(key)); } } /** Back up a QSettings to an ini-formatted file. */ static void BackupSettings(const fs::path &filename, const QSettings &src) { qInfo() << "Backing up GUI settings to" << GUIUtil::boostPathToQString(filename); QSettings dst(GUIUtil::boostPathToQString(filename), QSettings::IniFormat); dst.clear(); CopySettings(dst, src); } void OptionsModel::Reset() { QSettings settings; // Backup old settings to chain-specific datadir for troubleshooting BackupSettings(GetDataDir(true) / "guisettings.ini.bak", settings); // Save the strDataDir setting QString dataDir = GUIUtil::getDefaultDataDirectory(); dataDir = settings.value("strDataDir", dataDir).toString(); // Remove all entries from our QSettings object settings.clear(); // Set strDataDir settings.setValue("strDataDir", dataDir); // Set that this was reset settings.setValue("fReset", true); // default setting for OptionsModel::StartAtStartup - disabled if (GUIUtil::GetStartOnSystemStartup()) { GUIUtil::SetStartOnSystemStartup(false); } } int OptionsModel::rowCount(const QModelIndex &parent) const { return OptionIDRowCount; } struct ProxySetting { bool is_set; QString ip; QString port; }; static ProxySetting GetProxySetting(QSettings &settings, const QString &name) { static const ProxySetting default_val = { false, DEFAULT_GUI_PROXY_HOST, QString("%1").arg(DEFAULT_GUI_PROXY_PORT)}; // Handle the case that the setting is not set at all if (!settings.contains(name)) { return default_val; } // contains IP at index 0 and port at index 1 QStringList ip_port = GUIUtil::splitSkipEmptyParts(settings.value(name).toString(), ":"); if (ip_port.size() == 2) { return {true, ip_port.at(0), ip_port.at(1)}; } else { // Invalid: return default return default_val; } } static void SetProxySetting(QSettings &settings, const QString &name, const ProxySetting &ip_port) { settings.setValue(name, ip_port.ip + ":" + ip_port.port); } static const QString GetDefaultProxyAddress() { return QString("%1:%2") .arg(DEFAULT_GUI_PROXY_HOST) .arg(DEFAULT_GUI_PROXY_PORT); } void OptionsModel::SetPruneEnabled(bool prune, bool force) { QSettings settings; settings.setValue("bPrune", prune); - // Convert prune size from GB to MiB: - const uint64_t nPruneSizeMiB = - (settings.value("nPruneSize").toInt() * GB_BYTES) >> 20; - std::string prune_val = prune ? ToString(nPruneSizeMiB) : "0"; + const int64_t prune_target_mib = + PruneGBtoMiB(settings.value("nPruneSize").toInt()); + std::string prune_val = prune ? ToString(prune_target_mib) : "0"; if (force) { gArgs.ForceSetArg("-prune", prune_val); return; } if (!gArgs.SoftSetArg("-prune", prune_val)) { addOverriddenOption("-prune"); } } void OptionsModel::SetPruneTargetGB(int prune_target_gb, bool force) { const bool prune = prune_target_gb > 0; if (prune) { QSettings settings; settings.setValue("nPruneSize", prune_target_gb); } SetPruneEnabled(prune, force); } // read QSettings values and return them QVariant OptionsModel::data(const QModelIndex &index, int role) const { if (role == Qt::EditRole) { QSettings settings; switch (index.row()) { case StartAtStartup: return GUIUtil::GetStartOnSystemStartup(); case HideTrayIcon: return fHideTrayIcon; case MinimizeToTray: return fMinimizeToTray; case MapPortUPnP: #ifdef USE_UPNP return settings.value("fUseUPnP"); #else return false; #endif case MinimizeOnClose: return fMinimizeOnClose; // default proxy case ProxyUse: return settings.value("fUseProxy", false); case ProxyIP: return GetProxySetting(settings, "addrProxy").ip; case ProxyPort: return GetProxySetting(settings, "addrProxy").port; // separate Tor proxy case ProxyUseTor: return settings.value("fUseSeparateProxyTor", false); case ProxyIPTor: return GetProxySetting(settings, "addrSeparateProxyTor").ip; case ProxyPortTor: return GetProxySetting(settings, "addrSeparateProxyTor").port; #ifdef ENABLE_WALLET case SpendZeroConfChange: return settings.value("bSpendZeroConfChange"); #endif case DisplayUnit: return nDisplayUnit; case ThirdPartyTxUrls: return strThirdPartyTxUrls; case Language: return settings.value("language"); case CoinControlFeatures: return fCoinControlFeatures; case Prune: return settings.value("bPrune"); case PruneSize: return settings.value("nPruneSize"); case DatabaseCache: return settings.value("nDatabaseCache"); case ThreadsScriptVerif: return settings.value("nThreadsScriptVerif"); case Listen: return settings.value("fListen"); default: return QVariant(); } } return QVariant(); } // write QSettings values bool OptionsModel::setData(const QModelIndex &index, const QVariant &value, int role) { bool successful = true; /* set to false on parse error */ if (role == Qt::EditRole) { QSettings settings; switch (index.row()) { case StartAtStartup: successful = GUIUtil::SetStartOnSystemStartup(value.toBool()); break; case HideTrayIcon: fHideTrayIcon = value.toBool(); settings.setValue("fHideTrayIcon", fHideTrayIcon); Q_EMIT hideTrayIconChanged(fHideTrayIcon); break; case MinimizeToTray: fMinimizeToTray = value.toBool(); settings.setValue("fMinimizeToTray", fMinimizeToTray); break; case MapPortUPnP: // core option - can be changed on-the-fly settings.setValue("fUseUPnP", value.toBool()); node().mapPort(value.toBool()); break; case MinimizeOnClose: fMinimizeOnClose = value.toBool(); settings.setValue("fMinimizeOnClose", fMinimizeOnClose); break; // default proxy case ProxyUse: if (settings.value("fUseProxy") != value) { settings.setValue("fUseProxy", value.toBool()); setRestartRequired(true); } break; case ProxyIP: { auto ip_port = GetProxySetting(settings, "addrProxy"); if (!ip_port.is_set || ip_port.ip != value.toString()) { ip_port.ip = value.toString(); SetProxySetting(settings, "addrProxy", ip_port); setRestartRequired(true); } } break; case ProxyPort: { auto ip_port = GetProxySetting(settings, "addrProxy"); if (!ip_port.is_set || ip_port.port != value.toString()) { ip_port.port = value.toString(); SetProxySetting(settings, "addrProxy", ip_port); setRestartRequired(true); } } break; // separate Tor proxy case ProxyUseTor: if (settings.value("fUseSeparateProxyTor") != value) { settings.setValue("fUseSeparateProxyTor", value.toBool()); setRestartRequired(true); } break; case ProxyIPTor: { auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); if (!ip_port.is_set || ip_port.ip != value.toString()) { ip_port.ip = value.toString(); SetProxySetting(settings, "addrSeparateProxyTor", ip_port); setRestartRequired(true); } } break; case ProxyPortTor: { auto ip_port = GetProxySetting(settings, "addrSeparateProxyTor"); if (!ip_port.is_set || ip_port.port != value.toString()) { ip_port.port = value.toString(); SetProxySetting(settings, "addrSeparateProxyTor", ip_port); setRestartRequired(true); } } break; #ifdef ENABLE_WALLET case SpendZeroConfChange: if (settings.value("bSpendZeroConfChange") != value) { settings.setValue("bSpendZeroConfChange", value); setRestartRequired(true); } break; #endif case DisplayUnit: setDisplayUnit(value); break; case ThirdPartyTxUrls: if (strThirdPartyTxUrls != value.toString()) { strThirdPartyTxUrls = value.toString(); settings.setValue("strThirdPartyTxUrls", strThirdPartyTxUrls); setRestartRequired(true); } break; case Language: if (settings.value("language") != value) { settings.setValue("language", value); setRestartRequired(true); } break; case CoinControlFeatures: fCoinControlFeatures = value.toBool(); settings.setValue("fCoinControlFeatures", fCoinControlFeatures); Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures); break; case Prune: if (settings.value("bPrune") != value) { settings.setValue("bPrune", value); setRestartRequired(true); } break; case PruneSize: if (settings.value("nPruneSize") != value) { settings.setValue("nPruneSize", value); setRestartRequired(true); } break; case DatabaseCache: if (settings.value("nDatabaseCache") != value) { settings.setValue("nDatabaseCache", value); setRestartRequired(true); } break; case ThreadsScriptVerif: if (settings.value("nThreadsScriptVerif") != value) { settings.setValue("nThreadsScriptVerif", value); setRestartRequired(true); } break; case Listen: if (settings.value("fListen") != value) { settings.setValue("fListen", value); setRestartRequired(true); } break; default: break; } } Q_EMIT dataChanged(index, index); return successful; } /** Updates current unit in memory, settings and emits * displayUnitChanged(newUnit) signal */ void OptionsModel::setDisplayUnit(const QVariant &value) { if (!value.isNull()) { QSettings settings; nDisplayUnit = value.toInt(); settings.setValue("nDisplayUnit", nDisplayUnit); Q_EMIT displayUnitChanged(nDisplayUnit); } } bool OptionsModel::getProxySettings(QNetworkProxy &proxy) const { // Directly query current base proxy, because // GUI settings can be overridden with -proxy. proxyType curProxy; if (node().getProxy(NET_IPV4, curProxy)) { proxy.setType(QNetworkProxy::Socks5Proxy); proxy.setHostName(QString::fromStdString(curProxy.proxy.ToStringIP())); proxy.setPort(curProxy.proxy.GetPort()); return true; } else { proxy.setType(QNetworkProxy::NoProxy); } return false; } void OptionsModel::setRestartRequired(bool fRequired) { QSettings settings; return settings.setValue("fRestartRequired", fRequired); } bool OptionsModel::isRestartRequired() const { QSettings settings; return settings.value("fRestartRequired", false).toBool(); } void OptionsModel::checkAndMigrate() { // Migration of default values // Check if the QSettings container was already loaded with this client // version QSettings settings; static const char strSettingsVersionKey[] = "nSettingsVersion"; int settingsVersion = settings.contains(strSettingsVersionKey) ? settings.value(strSettingsVersionKey).toInt() : 0; if (settingsVersion < CLIENT_VERSION) { // -dbcache was bumped from 100 to 300 in 0.13 // see https://github.com/bitcoin/bitcoin/pull/8273 // force people to upgrade to the new value if they are using 100MB if (settingsVersion < 130000 && settings.contains("nDatabaseCache") && settings.value("nDatabaseCache").toLongLong() == 100) { settings.setValue("nDatabaseCache", (qint64)DEFAULT_DB_CACHE_MB); } settings.setValue(strSettingsVersionKey, CLIENT_VERSION); } // Overwrite the 'addrProxy' setting in case it has been set to an illegal // default value (see issue #12623; PR #12650). if (settings.contains("addrProxy") && settings.value("addrProxy").toString().endsWith("%2")) { settings.setValue("addrProxy", GetDefaultProxyAddress()); } // Overwrite the 'addrSeparateProxyTor' setting in case it has been set to // an illegal default value (see issue #12623; PR #12650). if (settings.contains("addrSeparateProxyTor") && settings.value("addrSeparateProxyTor").toString().endsWith("%2")) { settings.setValue("addrSeparateProxyTor", GetDefaultProxyAddress()); } } diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index dc8caec73..fa7b99c99 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -1,129 +1,146 @@ // Copyright (c) 2011-2016 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #ifndef BITCOIN_QT_OPTIONSMODEL_H #define BITCOIN_QT_OPTIONSMODEL_H #include +#include #include #include namespace interfaces { class Node; } QT_BEGIN_NAMESPACE class QNetworkProxy; QT_END_NAMESPACE extern const char *DEFAULT_GUI_PROXY_HOST; static constexpr unsigned short DEFAULT_GUI_PROXY_PORT = 9050; +/** + * Convert configured prune target MiB to displayed GB. Round up to avoid + * underestimating max disk usage. + */ +static inline int PruneMiBtoGB(int64_t mib) { + return (mib * 1024 * 1024 + GB_BYTES - 1) / GB_BYTES; +} + +/** + * Convert displayed prune target GB to configured MiB. Round down so roundtrip + * GB -> MiB -> GB conversion is stable. + */ +static inline int64_t PruneGBtoMiB(int gb) { + return gb * GB_BYTES / 1024 / 1024; +} + /** Interface from Qt to configuration data structure for Bitcoin client. To Qt, the options are presented as a list with the different options laid out vertically. This can be changed to a tree once the settings become sufficiently complex. */ class OptionsModel : public QAbstractListModel { Q_OBJECT public: explicit OptionsModel(QObject *parent = nullptr, bool resetSettings = false); enum OptionID { StartAtStartup, // bool HideTrayIcon, // bool MinimizeToTray, // bool MapPortUPnP, // bool MinimizeOnClose, // bool ProxyUse, // bool ProxyIP, // QString ProxyPort, // int ProxyUseTor, // bool ProxyIPTor, // QString ProxyPortTor, // int DisplayUnit, // BitcoinUnits::Unit ThirdPartyTxUrls, // QString Language, // QString CoinControlFeatures, // bool ThreadsScriptVerif, // int Prune, // bool PruneSize, // int DatabaseCache, // int SpendZeroConfChange, // bool Listen, // bool OptionIDRowCount, }; void Init(bool resetSettings = false); void Reset(); int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; /** Updates current unit in memory, settings and emits * displayUnitChanged(newUnit) signal */ void setDisplayUnit(const QVariant &value); /* Explicit getters */ bool getHideTrayIcon() const { return fHideTrayIcon; } bool getMinimizeToTray() const { return fMinimizeToTray; } bool getMinimizeOnClose() const { return fMinimizeOnClose; } int getDisplayUnit() const { return nDisplayUnit; } QString getThirdPartyTxUrls() const { return strThirdPartyTxUrls; } bool getProxySettings(QNetworkProxy &proxy) const; bool getCoinControlFeatures() const { return fCoinControlFeatures; } const QString &getOverriddenByCommandLine() { return strOverriddenByCommandLine; } /* Explicit setters */ void SetPruneEnabled(bool prune, bool force = false); void SetPruneTargetGB(int prune_target_gb, bool force = false); /* Restart flag helper */ void setRestartRequired(bool fRequired); bool isRestartRequired() const; interfaces::Node &node() const { assert(m_node); return *m_node; } void setNode(interfaces::Node &node) { assert(!m_node); m_node = &node; } private: interfaces::Node *m_node = nullptr; /* Qt-only settings */ bool fHideTrayIcon; bool fMinimizeToTray; bool fMinimizeOnClose; QString language; int nDisplayUnit; QString strThirdPartyTxUrls; bool fCoinControlFeatures; /* settings that were overridden by command-line */ QString strOverriddenByCommandLine; // Add option to list of GUI options overridden through command line/config // file void addOverriddenOption(const std::string &option); // Check settings version and upgrade default values if required void checkAndMigrate(); Q_SIGNALS: void displayUnitChanged(int unit); void coinControlFeaturesChanged(bool); void hideTrayIconChanged(bool); }; #endif // BITCOIN_QT_OPTIONSMODEL_H