diff --git a/main.qml b/main.qml index 7641abaa..cd962ca8 100644 --- a/main.qml +++ b/main.qml @@ -405,7 +405,7 @@ ApplicationWindow { currentWallet.pauseRefresh(); appWindow.showProcessingSplash(qsTr("Waiting for daemon to start...")) - daemonManager.start(flags, persistentSettings.testnet); + daemonManager.start(flags, persistentSettings.testnet, persistentSettings.blockchainDataDir); persistentSettings.daemonFlags = flags } @@ -884,6 +884,7 @@ ApplicationWindow { property string daemonUsername: "" property string daemonPassword: "" property bool transferShowAdvanced: false + property string blockchainDataDir: "" } // Information dialog diff --git a/pages/Settings.qml b/pages/Settings.qml index 038af0ae..5a908c0c 100644 --- a/pages/Settings.qml +++ b/pages/Settings.qml @@ -247,11 +247,38 @@ Rectangle { } } - - } - ColumnLayout { + RowLayout { + id: blockchainFolderRow + Label { + id: blockchainFolderLabel + color: "#4A4949" + text: qsTr("Blockchain location") + translationManager.emptyString + fontSize: 16 + } + LineEdit { + id: blockchainFolder + Layout.preferredWidth: 200 + Layout.fillWidth: true + text: persistentSettings.blockchainDataDir + placeholderText: qsTr("(optional)") + translationManager.emptyString + + MouseArea { + anchors.fill: parent + onClicked: { + mouse.accepted = false + if(persistentSettings.blockchainDataDir != "") + blockchainFileDialog.folder = "file://" + persistentSettings.blockchainDataDir + blockchainFileDialog.open() + blockchainFolder.focus = true + } + } + + } + } + + RowLayout { id: daemonFlagsRow Label { id: daemonFlagsLabel @@ -540,6 +567,62 @@ Rectangle { } } + // Choose blockchain folder + FileDialog { + id: blockchainFileDialog + title: qsTr("Please choose a folder") + translationManager.emptyString; + selectFolder: true + folder: "file://" + persistentSettings.blockchainDataDir + + onAccepted: { + var dataDir = walletManager.urlToLocalPath(blockchainFileDialog.fileUrl) + var validator = daemonManager.validateDataDir(dataDir); + if(!validator.valid) { + + confirmationDialog.title = qsTr("Warning") + translationManager.emptyString; + confirmationDialog.text = ""; + if(validator.readOnly) { + confirmationDialog.text += qsTr("Error: Filesystem is read only") + "\n\n" + } + + if(validator.storageAvailable < 20) { + confirmationDialog.text += qsTr("Warning: There's only %1 GB available on the device. Blockchain requires ~%2 GB of data.").arg(validator.storageAvailable).arg(15) + "\n\n" + } else { + confirmationDialog.text += qsTr("Note: There's %1 GB available on the device. Blockchain requires ~%2 GB of data.").arg(validator.storageAvailable).arg(15) + "\n\n" + } + + if(!validator.lmdbExists) { + confirmationDialog.text += qsTr("Note: lmdb folder not found. A new folder will be created.") + "\n\n" + } + + + confirmationDialog.icon = StandardIcon.Question + confirmationDialog.cancelText = qsTr("Cancel") + + // Continue + confirmationDialog.onAcceptedCallback = function() { + persistentSettings.blockchainDataDir = dataDir + } + + // Cancel + confirmationDialog.onRejectedCallback = function() { + }; + + confirmationDialog.open() + } else { + persistentSettings.blockchainDataDir = dataDir + } + + delete validator; + + + } + onRejected: { + console.log("data dir selection canceled") + } + + } + // fires on every page load function onPageCompleted() { console.log("Settings page loaded"); diff --git a/src/daemon/DaemonManager.cpp b/src/daemon/DaemonManager.cpp index 5d95b26b..58002014 100644 --- a/src/daemon/DaemonManager.cpp +++ b/src/daemon/DaemonManager.cpp @@ -8,6 +8,10 @@ #include #include #include +#include +#include +#include +#include namespace { static const int DAEMON_START_TIMEOUT_SECONDS = 30; @@ -28,7 +32,7 @@ DaemonManager *DaemonManager::instance(const QStringList *args) return m_instance; } -bool DaemonManager::start(const QString &flags, bool testnet) +bool DaemonManager::start(const QString &flags, bool testnet, const QString &dataDir) { // prepare command line arguments and pass to monerod QStringList arguments; @@ -54,8 +58,19 @@ bool DaemonManager::start(const QString &flags, bool testnet) arguments << str; } + // Custom data-dir + if(!dataDir.isEmpty()) { + if(testnet) + arguments << "--testnet-data-dir"; + else + arguments << "--data-dir"; + arguments << dataDir; + } + arguments << "--check-updates" << "disabled"; + + qDebug() << "starting monerod " + m_monerod; qDebug() << "With command line arguments " << arguments; @@ -236,6 +251,44 @@ void DaemonManager::exit() m_app_exit = true; } +QVariantMap DaemonManager::validateDataDir(const QString &dataDir) const +{ + QVariantMap result; + bool valid = true; + bool readOnly = false; + int storageAvailable = 0; + bool lmdbExists = true; + + QStorageInfo storage(dataDir); + if (storage.isValid() && storage.isReady()) { + if (storage.isReadOnly()) { + readOnly = true; + valid = false; + } + + // Make sure there is 20GB storage available + storageAvailable = storage.bytesAvailable()/1000/1000/1000; + if (storageAvailable < 20) { + valid = false; + } + } else { + valid = false; + } + + + if (!QDir(dataDir+"/lmdb").exists()) { + lmdbExists = false; + valid = false; + } + + result.insert("valid", valid); + result.insert("lmdbExists", lmdbExists); + result.insert("readOnly", readOnly); + result.insert("storageAvailable", storageAvailable); + + return result; +} + DaemonManager::DaemonManager(QObject *parent) : QObject(parent) { diff --git a/src/daemon/DaemonManager.h b/src/daemon/DaemonManager.h index 428630e1..2b9c8b84 100644 --- a/src/daemon/DaemonManager.h +++ b/src/daemon/DaemonManager.h @@ -4,6 +4,7 @@ #include #include #include +#include class DaemonManager : public QObject { @@ -13,7 +14,7 @@ public: static DaemonManager * instance(const QStringList *args); - Q_INVOKABLE bool start(const QString &flags, bool testnet); + Q_INVOKABLE bool start(const QString &flags, bool testnet, const QString &dataDir = ""); Q_INVOKABLE bool stop(bool testnet); // return true if daemon process is started @@ -21,6 +22,7 @@ public: // Send daemon command from qml and prints output in console window. Q_INVOKABLE bool sendCommand(const QString &cmd, bool testnet) const; Q_INVOKABLE void exit(); + Q_INVOKABLE QVariantMap validateDataDir(const QString &dataDir) const; private: