From 6c2118743cfd88c7305c01e9f9b9407ed649e722 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Fri, 14 Jun 2024 15:49:30 -0700 Subject: [PATCH 1/9] qml: wiring for custom datadir with android --- src/qml/models/options_model.cpp | 181 ++++++++++++++++++++++++++----- src/qml/models/options_model.h | 24 +++- 2 files changed, 174 insertions(+), 31 deletions(-) diff --git a/src/qml/models/options_model.cpp b/src/qml/models/options_model.cpp index 9e95152311..9e8cbe9a4f 100644 --- a/src/qml/models/options_model.cpp +++ b/src/qml/models/options_model.cpp @@ -9,6 +9,9 @@ #include #include #include +#ifdef __ANDROID__ +#include +#endif #include #include #include @@ -23,28 +26,95 @@ #include #include #include +#include +#include +#include -OptionsQmlModel::OptionsQmlModel(interfaces::Node& node, bool is_onboarded) +OptionsQmlModel::OptionsQmlModel(interfaces::Node* node, bool is_onboarded) : m_node{node} , m_onboarded{is_onboarded} { - m_dbcache_size_mib = SettingToInt(m_node.getPersistentSetting("dbcache"), nDefaultDbCache); + gArgs.LockSettings([&](common::Settings& cs) { + // Clear existing settings to ensure we're only storing new command line arguments + m_cli_settings.command_line_options.clear(); + + // Copy only the command line arguments from the provided settings + m_cli_settings.command_line_options = cs.command_line_options; + }); + + if (!is_onboarded) { + // added this to lock settings to default values + if (!gArgs.IsArgSet("-resetguisettings")) { + gArgs.LockSettings([&](common::Settings& s) { m_previous_settings = s; }); + } + m_dbcache_size_mib = nDefaultDbCache; + m_listen = DEFAULT_LISTEN; + m_natpmp = DEFAULT_NATPMP; + int64_t prune_value = 0; + m_prune = (prune_value > 1); + m_prune_size_gb = m_prune ? PruneMiBtoGB(prune_value) : DEFAULT_PRUNE_TARGET_GB; + m_script_threads = DEFAULT_SCRIPTCHECK_THREADS; + m_server = false; + m_upnp = DEFAULT_UPNP; + } + +#ifdef __ANDROID__ + if (!getCustomDataDirString().isEmpty() && (m_dataDir != getDefaultDataDirString())) { + m_dataDir = getCustomDataDirString(); + } else { + m_dataDir = getDefaultDataDirString(); + } +#else + QSettings settings; + m_dataDir = settings.value("strDataDir", m_dataDir).toString(); +#endif // __ANDROID__ +} + +void OptionsQmlModel::requestShutdown() +{ + Q_EMIT requestedShutdown(); +} + +void OptionsQmlModel::setNode(interfaces::Node* node, bool is_onboarded) { + if (node != nullptr) { + m_node = node; + if (is_onboarded) { + m_dbcache_size_mib = SettingToInt(m_node->getPersistentSetting("dbcache"), nDefaultDbCache); - m_listen = SettingToBool(m_node.getPersistentSetting("listen"), DEFAULT_LISTEN); + m_listen = SettingToBool(m_node->getPersistentSetting("listen"), DEFAULT_LISTEN); - m_natpmp = SettingToBool(m_node.getPersistentSetting("natpmp"), DEFAULT_NATPMP); + m_natpmp = SettingToBool(m_node->getPersistentSetting("natpmp"), DEFAULT_NATPMP); - int64_t prune_value{SettingToInt(m_node.getPersistentSetting("prune"), 0)}; - m_prune = (prune_value > 1); - m_prune_size_gb = m_prune ? PruneMiBtoGB(prune_value) : DEFAULT_PRUNE_TARGET_GB; + int64_t prune_value{SettingToInt(m_node->getPersistentSetting("prune"), 0)}; + m_prune = (prune_value > 1); + m_prune_size_gb = m_prune ? PruneMiBtoGB(prune_value) : DEFAULT_PRUNE_TARGET_GB; - m_script_threads = SettingToInt(m_node.getPersistentSetting("par"), DEFAULT_SCRIPTCHECK_THREADS); + m_script_threads = SettingToInt(m_node->getPersistentSetting("par"), DEFAULT_SCRIPTCHECK_THREADS); - m_server = SettingToBool(m_node.getPersistentSetting("server"), false); + m_server = SettingToBool(m_node->getPersistentSetting("server"), false); - m_upnp = SettingToBool(m_node.getPersistentSetting("upnp"), DEFAULT_UPNP); + m_upnp = SettingToBool(m_node->getPersistentSetting("upnp"), DEFAULT_UPNP); +#ifdef __ANDROID__ + m_dataDir = AndroidCustomDataDir().readCustomDataDir().isEmpty() ? getDefaultDataDirString() + : AndroidCustomDataDir().readCustomDataDir(); +#else + if ((gArgs.IsArgSet("-datadir") && !gArgs.GetPathArg("-datadir").empty())) { + m_dataDir = QString::fromStdString(gArgs.GetPathArg("-datadir").generic_string()); + } + else { + m_custom_datadir_string = m_settings.value("strDataDir", m_custom_datadir_string).toString(); - m_dataDir = getDefaultDataDirString(); + m_dataDir = fs::exists(GUIUtil::QStringToPath(m_custom_datadir_string)) ? m_custom_datadir_string + : QString::fromStdString(SettingToString(m_node->getPersistentSetting("datadir"), "")); + + if (m_dataDir.isEmpty()) { + m_dataDir = getDefaultDataDirString(); + } + } +#endif // __ANDROID__ + } + return; + } } void OptionsQmlModel::setDbcacheSizeMiB(int new_dbcache_size_mib) @@ -52,7 +122,7 @@ void OptionsQmlModel::setDbcacheSizeMiB(int new_dbcache_size_mib) if (new_dbcache_size_mib != m_dbcache_size_mib) { m_dbcache_size_mib = new_dbcache_size_mib; if (m_onboarded) { - m_node.updateRwSetting("dbcache", new_dbcache_size_mib); + m_node->updateRwSetting("dbcache", new_dbcache_size_mib); } Q_EMIT dbcacheSizeMiBChanged(new_dbcache_size_mib); } @@ -63,7 +133,7 @@ void OptionsQmlModel::setListen(bool new_listen) if (new_listen != m_listen) { m_listen = new_listen; if (m_onboarded) { - m_node.updateRwSetting("listen", new_listen); + m_node->updateRwSetting("listen", new_listen); } Q_EMIT listenChanged(new_listen); } @@ -74,7 +144,7 @@ void OptionsQmlModel::setNatpmp(bool new_natpmp) if (new_natpmp != m_natpmp) { m_natpmp = new_natpmp; if (m_onboarded) { - m_node.updateRwSetting("natpmp", new_natpmp); + m_node->updateRwSetting("natpmp", new_natpmp); } Q_EMIT natpmpChanged(new_natpmp); } @@ -85,7 +155,7 @@ void OptionsQmlModel::setPrune(bool new_prune) if (new_prune != m_prune) { m_prune = new_prune; if (m_onboarded) { - m_node.updateRwSetting("prune", pruneSetting()); + m_node->updateRwSetting("prune", pruneSetting()); } Q_EMIT pruneChanged(new_prune); } @@ -96,7 +166,7 @@ void OptionsQmlModel::setPruneSizeGB(int new_prune_size_gb) if (new_prune_size_gb != m_prune_size_gb) { m_prune_size_gb = new_prune_size_gb; if (m_onboarded) { - m_node.updateRwSetting("prune", pruneSetting()); + m_node->updateRwSetting("prune", pruneSetting()); } Q_EMIT pruneSizeGBChanged(new_prune_size_gb); } @@ -107,7 +177,7 @@ void OptionsQmlModel::setScriptThreads(int new_script_threads) if (new_script_threads != m_script_threads) { m_script_threads = new_script_threads; if (m_onboarded) { - m_node.updateRwSetting("par", new_script_threads); + m_node->updateRwSetting("par", new_script_threads); } Q_EMIT scriptThreadsChanged(new_script_threads); } @@ -118,7 +188,7 @@ void OptionsQmlModel::setServer(bool new_server) if (new_server != m_server) { m_server = new_server; if (m_onboarded) { - m_node.updateRwSetting("server", new_server); + m_node->updateRwSetting("server", new_server); } Q_EMIT serverChanged(new_server); } @@ -129,7 +199,7 @@ void OptionsQmlModel::setUpnp(bool new_upnp) if (new_upnp != m_upnp) { m_upnp = new_upnp; if (m_onboarded) { - m_node.updateRwSetting("upnp", new_upnp); + m_node->updateRwSetting("upnp", new_upnp); } Q_EMIT upnpChanged(new_upnp); } @@ -158,6 +228,33 @@ QUrl OptionsQmlModel::getDefaultDataDirectory() return QUrl::fromLocalFile(path); } +void OptionsQmlModel::defaultReset() +{ + QSettings settings; + // Save the strDataDir setting + QString path = GUIUtil::getDefaultDataDirectory(); + + setDataDir(path); + + // Remove all entries from our QSettings object + settings.clear(); + + // Set strDataDir + settings.setValue("strDataDir", path); + + // Set that this was reset + settings.setValue("fReset", true); + + // Clear the settings + gArgs.LockSettings([&](common::Settings& s) { s = m_previous_settings; }); + + // reinstate command line arguments + gArgs.LockSettings([&](common::Settings& cs) { cs = m_cli_settings; }); + gArgs.SoftSetBoolArg("-printtoconsole", false); + + gArgs.ClearPathCache(); +} + bool OptionsQmlModel::setCustomDataDirArgs(QString path) { if (!path.isEmpty()) { @@ -167,13 +264,35 @@ bool OptionsQmlModel::setCustomDataDirArgs(QString path) QString originalPrefix = "content://com.android.externalstorage.documents/tree/primary%3A"; QString newPrefix = "/storage/self/primary/"; QString path = uri.replace(originalPrefix, newPrefix); + QString dataDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); + QFile file(dataDir + "/filepath.txt"); + if (file.open(QIODevice::WriteOnly)) { + QTextStream out(&file); + out << path; // Store the QString path into filepath.txt + file.close(); + } #else + QSettings settings; path = QUrl(path).toLocalFile(); #endif // __ANDROID__ - qDebug() << "PlaceHolder: Created data directory: " << path; + try{ + if (TryCreateDirectories(GUIUtil::QStringToPath(path))) { + // qDebug() << "Created data directory: " << path; + TryCreateDirectories(GUIUtil::QStringToPath(path) / "wallets"); + } + } catch (const std::exception& e) { + qDebug() << "Error creating data directory: " << e.what(); + } +#ifndef __ANDROID__ + settings.setValue("strDataDir", path); +#endif // __ANDROID__ + if(path != GUIUtil::getDefaultDataDirectory()) { + gArgs.SoftSetArg("-datadir", fs::PathToString(GUIUtil::QStringToPath(path))); + } + gArgs.ClearPathCache(); + Q_EMIT customDataDirStringChanged(m_custom_datadir_string); m_custom_datadir_string = path; - Q_EMIT customDataDirStringChanged(path); setDataDir(path); return true; } @@ -203,27 +322,31 @@ void OptionsQmlModel::setDataDir(QString new_data_dir) void OptionsQmlModel::onboard() { - m_node.resetSettings(); + m_node->resetSettings(); if (m_dbcache_size_mib != nDefaultDbCache) { - m_node.updateRwSetting("dbcache", m_dbcache_size_mib); + m_node->updateRwSetting("dbcache", m_dbcache_size_mib); } if (m_listen) { - m_node.updateRwSetting("listen", m_listen); + m_node->updateRwSetting("listen", m_listen); } if (m_natpmp) { - m_node.updateRwSetting("natpmp", m_natpmp); + m_node->updateRwSetting("natpmp", m_natpmp); } if (m_prune) { - m_node.updateRwSetting("prune", pruneSetting()); + m_node->updateRwSetting("prune", pruneSetting()); } if (m_script_threads != DEFAULT_SCRIPTCHECK_THREADS) { - m_node.updateRwSetting("par", m_script_threads); + m_node->updateRwSetting("par", m_script_threads); } if (m_server) { - m_node.updateRwSetting("server", m_server); + m_node->updateRwSetting("server", m_server); } if (m_upnp) { - m_node.updateRwSetting("upnp", m_upnp); + m_node->updateRwSetting("upnp", m_upnp); + } + if (m_dataDir != getDefaultDataDirString()) { + std::string dataDirStd = m_dataDir.toStdString(); + m_node->updateRwSetting("datadir", UniValue(dataDirStd)); } m_onboarded = true; } diff --git a/src/qml/models/options_model.h b/src/qml/models/options_model.h index 459d40b574..3ff6f1ce0d 100644 --- a/src/qml/models/options_model.h +++ b/src/qml/models/options_model.h @@ -6,11 +6,15 @@ #define BITCOIN_QML_MODELS_OPTIONS_MODEL_H #include +#include +#include #include #include +#include #include #include +#include #include #include @@ -37,10 +41,14 @@ class OptionsQmlModel : public QObject Q_PROPERTY(QString dataDir READ dataDir WRITE setDataDir NOTIFY dataDirChanged) Q_PROPERTY(QString getDefaultDataDirString READ getDefaultDataDirString CONSTANT) Q_PROPERTY(QUrl getDefaultDataDirectory READ getDefaultDataDirectory CONSTANT) + Q_PROPERTY(QString fullClientVersion READ fullClientVersion CONSTANT) + Q_PROPERTY(quint64 assumedBlockchainSize READ assumedBlockchainSize CONSTANT) + Q_PROPERTY(quint64 assumedChainstateSize READ assumedChainstateSize CONSTANT) public: - explicit OptionsQmlModel(interfaces::Node& node, bool is_onboarded); + explicit OptionsQmlModel(interfaces::Node* node, bool is_onboarded); + void setNode(interfaces::Node* node, bool is_onboarded); int dbcacheSizeMiB() const { return m_dbcache_size_mib; } void setDbcacheSizeMiB(int new_dbcache_size_mib); bool listen() const { return m_listen; } @@ -67,12 +75,17 @@ class OptionsQmlModel : public QObject QUrl getDefaultDataDirectory(); Q_INVOKABLE bool setCustomDataDirArgs(QString path); Q_INVOKABLE QString getCustomDataDirString(); + Q_INVOKABLE void defaultReset(); + QString fullClientVersion() const { return QString::fromStdString(FormatFullVersion()); } + quint64 assumedBlockchainSize() const { return m_assumed_blockchain_size; }; + quint64 assumedChainstateSize() const { return m_assumed_chainstate_size; }; public Q_SLOTS: void setCustomDataDirString(const QString &new_custom_datadir_string) { m_custom_datadir_string = new_custom_datadir_string; } Q_INVOKABLE void onboard(); + Q_INVOKABLE void requestShutdown(); Q_SIGNALS: void dbcacheSizeMiBChanged(int new_dbcache_size_mib); @@ -83,11 +96,13 @@ public Q_SLOTS: void scriptThreadsChanged(int new_script_threads); void serverChanged(bool new_server); void upnpChanged(bool new_upnp); + void onboardingFinished(); + void requestedShutdown(); void customDataDirStringChanged(QString new_custom_datadir_string); void dataDirChanged(QString new_data_dir); private: - interfaces::Node& m_node; + interfaces::Node* m_node; bool m_onboarded; // Properties that are exposed to QML. @@ -103,8 +118,13 @@ public Q_SLOTS: int m_script_threads; bool m_server; bool m_upnp; + QSettings m_settings; QString m_custom_datadir_string; QString m_dataDir; + common::Settings m_previous_settings; + common::Settings m_cli_settings; + quint64 m_assumed_blockchain_size{ Params().AssumedBlockchainSize() }; + quint64 m_assumed_chainstate_size{ Params().AssumedChainStateSize() }; common::SettingsValue pruneSetting() const; }; From e38c8fb0479f3eaa749c229522ae2541f499370b Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Fri, 14 Jun 2024 15:51:55 -0700 Subject: [PATCH 2/9] qml: removed "fullClientVersion" from nodemodel.h so it works with options_model --- src/qml/models/nodemodel.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/qml/models/nodemodel.h b/src/qml/models/nodemodel.h index a17f9b0833..61f4b91a48 100644 --- a/src/qml/models/nodemodel.h +++ b/src/qml/models/nodemodel.h @@ -7,7 +7,6 @@ #include #include -#include #include @@ -27,7 +26,6 @@ class NodeModel : public QObject { Q_OBJECT Q_PROPERTY(int blockTipHeight READ blockTipHeight NOTIFY blockTipHeightChanged) - Q_PROPERTY(QString fullClientVersion READ fullClientVersion CONSTANT) Q_PROPERTY(int numOutboundPeers READ numOutboundPeers NOTIFY numOutboundPeersChanged) Q_PROPERTY(int maxNumOutboundPeers READ maxNumOutboundPeers CONSTANT) Q_PROPERTY(int remainingSyncTime READ remainingSyncTime NOTIFY remainingSyncTimeChanged) @@ -40,7 +38,6 @@ class NodeModel : public QObject int blockTipHeight() const { return m_block_tip_height; } void setBlockTipHeight(int new_height); - QString fullClientVersion() const { return QString::fromStdString(FormatFullVersion()); } int numOutboundPeers() const { return m_num_outbound_peers; } void setNumOutboundPeers(int new_num); int maxNumOutboundPeers() const { return m_max_num_outbound_peers; } From 740e575ab88b067c748398d54a03a6fbb5a1793b Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Mon, 1 Jul 2024 16:13:13 -0700 Subject: [PATCH 3/9] qml: updated pages so they use optionsModel instead of nodeModel --- src/qml/components/AboutOptions.qml | 2 +- src/qml/components/StorageOptions.qml | 4 ++-- src/qml/pages/main.qml | 5 +++-- src/qml/pages/onboarding/OnboardingStorageLocation.qml | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/qml/components/AboutOptions.qml b/src/qml/components/AboutOptions.qml index e20adf9609..5568d4ec68 100644 --- a/src/qml/components/AboutOptions.qml +++ b/src/qml/components/AboutOptions.qml @@ -51,7 +51,7 @@ ColumnLayout { header: qsTr("Version") actionItem: ExternalLink { parentState: versionLink.state - description: nodeModel.fullClientVersion + description: optionsModel.fullClientVersion link: "https://bitcoin.org/en/download" iconSource: "image://images/caret-right" iconWidth: 18 diff --git a/src/qml/components/StorageOptions.qml b/src/qml/components/StorageOptions.qml index eed962b951..60eb656b2d 100644 --- a/src/qml/components/StorageOptions.qml +++ b/src/qml/components/StorageOptions.qml @@ -22,7 +22,7 @@ ColumnLayout { Layout.fillWidth: true ButtonGroup.group: group text: qsTr("Reduce storage") - description: qsTr("Uses about %1GB. For simple wallet use.").arg(chainModel.assumedChainstateSize + 2) + description: qsTr("Uses about %1GB. For simple wallet use.").arg(optionsModel.assumedChainstateSize + 2) recommended: true checked: !root.customStorage && optionsModel.prune onClicked: { @@ -40,7 +40,7 @@ ColumnLayout { text: qsTr("Store all data") checked: !optionsModel.prune description: qsTr("Uses about %1GB. Support the network.").arg( - chainModel.assumedBlockchainSize + chainModel.assumedChainstateSize) + optionsModel.assumedBlockchainSize + optionsModel.assumedChainstateSize) onClicked: { optionsModel.prune = false } diff --git a/src/qml/pages/main.qml b/src/qml/pages/main.qml index 67526c80ae..eee1a59e3b 100644 --- a/src/qml/pages/main.qml +++ b/src/qml/pages/main.qml @@ -49,14 +49,14 @@ ApplicationWindow { focus: true Keys.onReleased: { if (event.key == Qt.Key_Back) { - nodeModel.requestShutdown() + optionsModel.requestShutdown() event.accepted = true } } } Connections { - target: nodeModel + target: optionsModel function onRequestedShutdown() { main.clear() main.push(shutdown) @@ -78,6 +78,7 @@ ApplicationWindow { OnboardingConnection {} onFinishedChanged: { + optionsModel.onboardingFinished() optionsModel.onboard() if (AppMode.walletEnabled && AppMode.isDesktop) { main.push(desktopWallets) diff --git a/src/qml/pages/onboarding/OnboardingStorageLocation.qml b/src/qml/pages/onboarding/OnboardingStorageLocation.qml index b2c3aa1e00..e507865c54 100644 --- a/src/qml/pages/onboarding/OnboardingStorageLocation.qml +++ b/src/qml/pages/onboarding/OnboardingStorageLocation.qml @@ -19,7 +19,7 @@ InformationPage { bold: true headerText: qsTr("Storage location") headerMargin: 0 - description: qsTr("Where do you want to store the downloaded block data?\nYou need a minimum of %1GB of storage.").arg(chainModel.assumedChainstateSize + 1) + description: qsTr("Where do you want to store the downloaded block data?\nYou need a minimum of %1GB of storage.").arg(optionsModel.assumedChainstateSize + 1) descriptionMargin: 20 detailActive: true detailItem: StorageLocations {} From a9cf27fcc1fa80f99f38191081a51bec79839452 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Mon, 1 Jul 2024 16:14:47 -0700 Subject: [PATCH 4/9] qml: backend wiring for custom datadir functionality --- src/qml/components/StorageLocations.qml | 28 ++++++++++++++++++------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/src/qml/components/StorageLocations.qml b/src/qml/components/StorageLocations.qml index acaa69b40a..04679c1ce9 100644 --- a/src/qml/components/StorageLocations.qml +++ b/src/qml/components/StorageLocations.qml @@ -22,11 +22,22 @@ ColumnLayout { ButtonGroup.group: group text: qsTr("Default") description: qsTr("Your application directory.") - customDir: optionsModel.getDefaultDataDirString - checked: optionsModel.dataDir === optionsModel.getDefaultDataDirString + checked: true + customDir: updateCustomDir() + function updateCustomDir() { + if (checked) { + optionsModel.dataDir = optionsModel.getDefaultDataDirString; + optionsModel.defaultReset(); + return optionsModel.dataDir; + } else { + return ""; + } + } onClicked: { defaultDirOption.checked = true + customDirOption.checked = false optionsModel.dataDir = optionsModel.getDefaultDataDirString + optionsModel.defaultReset() } } OptionButton { @@ -35,9 +46,12 @@ ColumnLayout { ButtonGroup.group: group text: qsTr("Custom") description: qsTr("Choose the directory and storage device.") - customDir: customDirOption.checked ? fileDialog.folder : "" + customDir: customDirOption.checked ? optionsModel.getCustomDataDirString() : "" checked: optionsModel.dataDir !== optionsModel.getDefaultDataDirString - onClicked: fileDialog.open() + onClicked: { + defaultDirOption.checked = false + fileDialog.open(); + } } FileDialog { id: fileDialog @@ -49,10 +63,8 @@ ColumnLayout { if (customDataDir !== "") { optionsModel.setCustomDataDirArgs(customDataDir) customDirOption.customDir = optionsModel.getCustomDataDirString() - if (optionsModel.dataDir !== optionsModel.getDefaultDataDirString) { - customDirOption.checked = true - defaultDirOption.checked = false - } + customDirOption.checked = true + defaultDirOption.checked = false } } onRejected: { From 144e4c8b441a84db1b391bc37b39139af900828f Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Mon, 1 Jul 2024 16:15:18 -0700 Subject: [PATCH 5/9] cpp: added univalue.h to include because it was causing compiler crash --- src/common/settings.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/common/settings.h b/src/common/settings.h index 0e9d376e23..30290c59f8 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -10,6 +10,7 @@ #include #include #include +#include #include class UniValue; From 366b0fc60cbd178b5369db41c11f1df5822c97a2 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Mon, 1 Jul 2024 16:18:03 -0700 Subject: [PATCH 6/9] qml: refactoring to allow for custom datadir and android support --- src/qml/bitcoin.cpp | 305 +++++++++++++++++++++++++++++++------------- 1 file changed, 214 insertions(+), 91 deletions(-) diff --git a/src/qml/bitcoin.cpp b/src/qml/bitcoin.cpp index 367512cbe1..7ddf5a6815 100644 --- a/src/qml/bitcoin.cpp +++ b/src/qml/bitcoin.cpp @@ -17,6 +17,7 @@ #include #ifdef __ANDROID__ #include +#include #endif #include #include @@ -47,6 +48,7 @@ #include #include #include +#include #include #include #include @@ -160,6 +162,194 @@ void setupChainQSettings(QGuiApplication* app, QString chain) app->setApplicationName(QAPP_APP_NAME_REGTEST); } } + +bool setCustomDataDir(QString strDataDir) +{ + if(fs::exists(GUIUtil::QStringToPath(strDataDir))){ + gArgs.SoftSetArg("-datadir", fs::PathToString(GUIUtil::QStringToPath(strDataDir))); + gArgs.ClearPathCache(); + return true; + } else { + return false; + } +} + +QGuiApplication* m_app; +QQmlApplicationEngine* m_engine; +boost::signals2::connection m_handler_message_box; +std::unique_ptr m_init; +std::unique_ptr m_node; +std::unique_ptr m_chain; +NodeModel* m_node_model{nullptr}; +InitExecutor* m_executor{nullptr}; +ChainModel* m_chain_model{nullptr}; +OptionsQmlModel* m_options_model{nullptr}; +int m_argc; +char** m_argv; +NetworkTrafficTower* m_network_traffic_tower; +PeerTableModel* m_peer_model; +PeerListSortProxy* m_peer_model_sort_proxy; +bool m_isOnboarded; +WalletController *m_wallet_controller; +WalletListModel *m_wallet_list_model; + +bool createNode(QGuiApplication& app, QQmlApplicationEngine& engine, int& argc, char* argv[], ArgsManager& gArgs) +{ + m_engine = &engine; + + InitLogging(gArgs); + InitParameterInteraction(gArgs); + + m_init = interfaces::MakeGuiInit(argc, argv); + + m_node = m_init->makeNode(); + m_chain = m_init->makeChain(); + + if (!m_node->baseInitialize()) { + // A dialog with detailed error will have been shown by InitError(). + return EXIT_FAILURE; + } + + m_handler_message_box.disconnect(); + + m_node_model = new NodeModel{*m_node}; + m_executor = new InitExecutor{*m_node}; + QObject::connect(m_node_model, &NodeModel::requestedInitialize, m_executor, &InitExecutor::initialize); + QObject::connect(m_node_model, &NodeModel::requestedShutdown, m_executor, &InitExecutor::shutdown); + QObject::connect(m_executor, &InitExecutor::initializeResult, m_node_model, &NodeModel::initializeResult); + QObject::connect(m_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection); + + m_network_traffic_tower = new NetworkTrafficTower{*m_node_model}; +#ifdef __ANDROID__ + AndroidNotifier android_notifier{*m_node_model}; +#endif + + m_chain_model = new ChainModel{*m_chain}; + m_chain_model->setCurrentNetworkName(QString::fromStdString(ChainTypeToString(gArgs.GetChainType()))); + setupChainQSettings(m_app, m_chain_model->currentNetworkName()); + + QObject::connect(m_node_model, &NodeModel::setTimeRatioList, m_chain_model, &ChainModel::setTimeRatioList); + QObject::connect(m_node_model, &NodeModel::setTimeRatioListInitial, m_chain_model, &ChainModel::setTimeRatioListInitial); + + qGuiApp->setQuitOnLastWindowClosed(false); + QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] { + m_node->startShutdown(); + }); + + m_peer_model = new PeerTableModel{*m_node, nullptr}; + m_peer_model_sort_proxy = new PeerListSortProxy{nullptr}; + m_peer_model_sort_proxy->setSourceModel(m_peer_model); + + m_wallet_controller = new WalletController{*m_node}; + + m_wallet_list_model = new WalletListModel{*m_node, nullptr}; + + m_engine->rootContext()->setContextProperty("networkTrafficTower", m_network_traffic_tower); + m_engine->rootContext()->setContextProperty("nodeModel", m_node_model); + m_engine->rootContext()->setContextProperty("chainModel", m_chain_model); + m_engine->rootContext()->setContextProperty("peerTableModel", m_peer_model); + m_engine->rootContext()->setContextProperty("peerListModelProxy", m_peer_model_sort_proxy); + m_engine->rootContext()->setContextProperty("walletController", m_wallet_controller); + m_engine->rootContext()->setContextProperty("walletListModel", m_wallet_list_model); + + m_options_model->setNode(&(*m_node), m_isOnboarded); + + QObject::connect(m_options_model, &OptionsQmlModel::requestedShutdown, m_executor, &InitExecutor::shutdown); + + m_engine->rootContext()->setContextProperty("optionsModel", m_options_model); + + m_node_model->startShutdownPolling(); + + return true; +} + +void startNodeAndTransitionSlot() { createNode(*m_app, *m_engine, m_argc, m_argv, gArgs); } + +int initializeAndRunApplication(QGuiApplication* app, QQmlApplicationEngine* m_engine) { + AppMode app_mode = SetupAppMode(); + + // Register the singleton instance for AppMode with the QML engine + qmlRegisterSingletonInstance("org.bitcoincore.qt", 1, 0, "AppMode", &app_mode); + + // Register custom QML types + qmlRegisterType("org.bitcoincore.qt", 1, 0, "BlockClockDial"); + qmlRegisterType("org.bitcoincore.qt", 1, 0, "LineGraph"); + + // Load the main QML file + m_engine->load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml"))); + + // Check if the QML engine failed to load the main QML file + if (m_engine->rootObjects().isEmpty()) { + return EXIT_FAILURE; + } + + // Get the first root object as a QQuickWindow + auto window = qobject_cast(m_engine->rootObjects().first()); + if (!window) { + return EXIT_FAILURE; + } + + // Install the custom message handler for qDebug() + qInstallMessageHandler(DebugMessageHandler); + + // Log the graphics API in use + qInfo() << "Graphics API in use:" << QmlUtil::GraphicsApi(window); + + // Execute the application + return qGuiApp->exec(); +} + +bool startNode(QGuiApplication& app, QQmlApplicationEngine& engine, int& argc, char* argv[]) +{ + m_engine = &engine; + QScopedPointer network_style{NetworkStyle::instantiate(Params().GetChainType())}; + assert(!network_style.isNull()); + m_engine->addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()}); + + m_isOnboarded = true; + + m_options_model = new OptionsQmlModel{nullptr, m_isOnboarded}; + m_engine->rootContext()->setContextProperty("optionsModel", m_options_model); + + // the settings.json file is read and parsed before creating the node + std::string error; + /// Read and parse settings.json file. + std::vector errors; + if (!gArgs.ReadSettingsFile(&errors)) { + error = strprintf("Failed loading settings file:\n%s\n", MakeUnorderedList(errors)); + InitError(Untranslated(error)); + return EXIT_FAILURE; + } + + createNode(*m_app, *m_engine, argc, argv, gArgs); + + initializeAndRunApplication(&app, m_engine); + return true; +} + +bool startOnboarding(QGuiApplication& app, QQmlApplicationEngine& engine, ArgsManager& gArgs) +{ + m_engine = &engine; + QScopedPointer network_style{NetworkStyle::instantiate(Params().GetChainType())}; + assert(!network_style.isNull()); + m_engine->addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()}); + + m_isOnboarded = false; + + m_options_model = new OptionsQmlModel{nullptr, m_isOnboarded}; + + if (gArgs.IsArgSet("-resetguisettings")) { + m_options_model->defaultReset(); + } + + m_engine->rootContext()->setContextProperty("optionsModel", m_options_model); + + QObject::connect(m_options_model, &OptionsQmlModel::onboardingFinished, startNodeAndTransitionSlot); + + initializeAndRunApplication(&app, m_engine); + + return true; +} } // namespace @@ -177,9 +367,7 @@ int QmlGuiMain(int argc, char* argv[]) QGuiApplication::styleHints()->setTabFocusBehavior(Qt::TabFocusAllControls); QGuiApplication app(argc, argv); - auto handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(InitErrorMessageBox); - - std::unique_ptr init = interfaces::MakeGuiInit(argc, argv); + auto m_handler_message_box = ::uiInterface.ThreadSafeMessageBox_connect(InitErrorMessageBox); SetupEnvironment(); util::ThreadSetInternalName("main"); @@ -191,6 +379,10 @@ int QmlGuiMain(int argc, char* argv[]) app.setOrganizationDomain(QAPP_ORG_DOMAIN); app.setApplicationName(QAPP_APP_NAME_DEFAULT); + QSettings settings; + QString dataDir; + dataDir = settings.value("strDataDir", dataDir).toString(); + /// Parse command-line options. We do this after qt in order to show an error if there are problems parsing these. SetupServerArgs(gArgs); SetupUIArgs(gArgs); @@ -220,20 +412,24 @@ int QmlGuiMain(int argc, char* argv[]) return EXIT_FAILURE; } - /// Read and parse settings.json file. - std::vector errors; - if (!gArgs.ReadSettingsFile(&errors)) { - error = strprintf("Failed loading settings file:\n%s\n", MakeUnorderedList(errors)); - InitError(Untranslated(error)); - return EXIT_FAILURE; - } - QVariant need_onboarding(true); - if (gArgs.IsArgSet("-datadir") && !gArgs.GetPathArg("-datadir").empty()) { +#ifdef __ANDROID__ + AndroidCustomDataDir custom_data_dir; + QString storePath = custom_data_dir.readCustomDataDir(); + if (!storePath.isEmpty()) { + custom_data_dir.setDataDir(storePath); need_onboarding.setValue(false); } else if (ConfigurationFileExists(gArgs)) { need_onboarding.setValue(false); } +#else + if ((gArgs.IsArgSet("-datadir") && !gArgs.GetPathArg("-datadir").empty()) || fs::exists(GUIUtil::QStringToPath(dataDir)) ) { + setCustomDataDir(dataDir); + need_onboarding.setValue(false); + } else if (ConfigurationFileExists(gArgs)) { + need_onboarding.setValue(false); + } +#endif // __ANDROID__ if (gArgs.IsArgSet("-resetguisettings")) { need_onboarding.setValue(true); @@ -242,95 +438,22 @@ int QmlGuiMain(int argc, char* argv[]) // Default printtoconsole to false for the GUI. GUI programs should not // print to the console unnecessarily. gArgs.SoftSetBoolArg("-printtoconsole", false); - InitLogging(gArgs); - InitParameterInteraction(gArgs); GUIUtil::LogQtInfo(); - - std::unique_ptr node = init->makeNode(); - std::unique_ptr chain = init->makeChain(); - if (!node->baseInitialize()) { - // A dialog with detailed error will have been shown by InitError(). - return EXIT_FAILURE; - } - - handler_message_box.disconnect(); - - NodeModel node_model{*node}; - InitExecutor init_executor{*node}; - QObject::connect(&node_model, &NodeModel::requestedInitialize, &init_executor, &InitExecutor::initialize); - QObject::connect(&node_model, &NodeModel::requestedShutdown, &init_executor, &InitExecutor::shutdown); - QObject::connect(&init_executor, &InitExecutor::initializeResult, &node_model, &NodeModel::initializeResult); - QObject::connect(&init_executor, &InitExecutor::shutdownResult, qGuiApp, &QGuiApplication::quit, Qt::QueuedConnection); - // QObject::connect(&init_executor, &InitExecutor::runawayException, &node_model, &NodeModel::handleRunawayException); - - NetworkTrafficTower network_traffic_tower{node_model}; -#ifdef __ANDROID__ - AndroidNotifier android_notifier{node_model}; -#endif - - ChainModel chain_model{*chain}; - chain_model.setCurrentNetworkName(QString::fromStdString(ChainTypeToString(gArgs.GetChainType()))); - setupChainQSettings(&app, chain_model.currentNetworkName()); - - QObject::connect(&node_model, &NodeModel::setTimeRatioList, &chain_model, &ChainModel::setTimeRatioList); - QObject::connect(&node_model, &NodeModel::setTimeRatioListInitial, &chain_model, &ChainModel::setTimeRatioListInitial); - - qGuiApp->setQuitOnLastWindowClosed(false); - QObject::connect(qGuiApp, &QGuiApplication::lastWindowClosed, [&] { - node->startShutdown(); - }); - - PeerTableModel peer_model{*node, nullptr}; - PeerListSortProxy peer_model_sort_proxy{nullptr}; - peer_model_sort_proxy.setSourceModel(&peer_model); - GUIUtil::LoadFont(":/fonts/inter/regular"); GUIUtil::LoadFont(":/fonts/inter/semibold"); - WalletController wallet_controller(*node); + m_app = &app; QQmlApplicationEngine engine; - QScopedPointer network_style{NetworkStyle::instantiate(Params().GetChainType())}; - assert(!network_style.isNull()); - engine.addImageProvider(QStringLiteral("images"), new ImageProvider{network_style.data()}); - - WalletListModel wallet_list_model{*node, nullptr}; - - engine.rootContext()->setContextProperty("networkTrafficTower", &network_traffic_tower); - engine.rootContext()->setContextProperty("nodeModel", &node_model); - engine.rootContext()->setContextProperty("chainModel", &chain_model); - engine.rootContext()->setContextProperty("peerTableModel", &peer_model); - engine.rootContext()->setContextProperty("peerListModelProxy", &peer_model_sort_proxy); - engine.rootContext()->setContextProperty("walletController", &wallet_controller); - engine.rootContext()->setContextProperty("walletListModel", &wallet_list_model); - - OptionsQmlModel options_model(*node, !need_onboarding.toBool()); - engine.rootContext()->setContextProperty("optionsModel", &options_model); engine.rootContext()->setContextProperty("needOnboarding", need_onboarding); - AppMode app_mode = SetupAppMode(); - - qmlRegisterSingletonInstance("org.bitcoincore.qt", 1, 0, "AppMode", &app_mode); - qmlRegisterType("org.bitcoincore.qt", 1, 0, "BlockClockDial"); - qmlRegisterType("org.bitcoincore.qt", 1, 0, "LineGraph"); - - engine.load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml"))); - if (engine.rootObjects().isEmpty()) { - return EXIT_FAILURE; - } - - auto window = qobject_cast(engine.rootObjects().first()); - if (!window) { - return EXIT_FAILURE; + if(need_onboarding.toBool()) { + startOnboarding(*m_app, engine, gArgs); + } else { + startNode(*m_app, engine, argc, argv); } - // Install qDebug() message handler to route to debug.log - qInstallMessageHandler(DebugMessageHandler); - - qInfo() << "Graphics API in use:" << QmlUtil::GraphicsApi(window); - - node_model.startShutdownPolling(); - return qGuiApp->exec(); + return 0; } From f89a2f968574dc3e3c81a7f1fb6074a0a89e3475 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Tue, 2 Jul 2024 11:48:30 -0700 Subject: [PATCH 7/9] android: permissions for custom datadir --- src/qt/android/AndroidManifest.xml | 2 ++ .../org/bitcoincore/qt/BitcoinQtActivity.java | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/qt/android/AndroidManifest.xml b/src/qt/android/AndroidManifest.xml index 8d2c1ac901..c9b6b2b80e 100644 --- a/src/qt/android/AndroidManifest.xml +++ b/src/qt/android/AndroidManifest.xml @@ -3,7 +3,9 @@ + + diff --git a/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java b/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java index 97db345d4e..16a9648366 100644 --- a/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java +++ b/src/qt/android/src/org/bitcoincore/qt/BitcoinQtActivity.java @@ -8,6 +8,8 @@ import android.system.Os; import android.view.WindowManager; import android.view.View; +import android.Manifest; +import android.content.pm.PackageManager; import org.qtproject.qt5.android.bindings.QtActivity; @@ -15,6 +17,8 @@ public class BitcoinQtActivity extends QtActivity { + private static final int PERMISSIONS_REQUEST_CODE = 123; + @Override public void onCreate(Bundle savedInstanceState) { @@ -36,5 +40,22 @@ public void onCreate(Bundle savedInstanceState) getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); super.onCreate(savedInstanceState); + + if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[] { + Manifest.permission.WRITE_EXTERNAL_STORAGE + }, PERMISSIONS_REQUEST_CODE); + } + } + + @Override + public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { + if (requestCode == PERMISSIONS_REQUEST_CODE) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + System.out.println("Permission was granted"); + } else { + System.out.println("Permission was denied"); + } + } } } From 8bb72f068c1843f75f9df5126378e8c9ad3b327c Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Tue, 2 Jul 2024 11:49:39 -0700 Subject: [PATCH 8/9] android: custom datadir retrieve filepath --- src/qml/androidcustomdatadir.cpp | 48 ++++++++++++++++++++++++++++++++ src/qml/androidcustomdatadir.h | 32 +++++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 src/qml/androidcustomdatadir.cpp create mode 100644 src/qml/androidcustomdatadir.h diff --git a/src/qml/androidcustomdatadir.cpp b/src/qml/androidcustomdatadir.cpp new file mode 100644 index 0000000000..eba691fc93 --- /dev/null +++ b/src/qml/androidcustomdatadir.cpp @@ -0,0 +1,48 @@ +// Copyright (c) 2023 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 + +#include +#include +#include +#include + +#include +#include +#include +#include + +AndroidCustomDataDir::AndroidCustomDataDir(QObject * parent) + : QObject(parent) +{ + m_default_data_dir = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); +} + +void AndroidCustomDataDir::setDataDir(const QString & new_data_dir) +{ + if (m_data_dir == new_data_dir) { + return; + } + + m_data_dir = new_data_dir; + gArgs.SoftSetArg("-datadir", fs::PathToString(GUIUtil::QStringToPath(m_data_dir))); + gArgs.ClearPathCache(); + Q_EMIT dataDirChanged(); +} + +QString AndroidCustomDataDir::readCustomDataDir() +{ + QFile file(m_default_data_dir + "/filepath.txt"); + QString storedPath; + + if (file.open(QIODevice::ReadOnly)) { + QTextStream in(&file); + storedPath = in.readAll().trimmed(); + file.close(); + // Process the retrieved path + qDebug() << "Retrieved path: " << storedPath; + } + return storedPath; +} diff --git a/src/qml/androidcustomdatadir.h b/src/qml/androidcustomdatadir.h new file mode 100644 index 0000000000..24578f33b8 --- /dev/null +++ b/src/qml/androidcustomdatadir.h @@ -0,0 +1,32 @@ +// Copyright (c) 2024 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_QML_ANDROIDCUSTOMDATADIR_H +#define BITCOIN_QML_ANDROIDCUSTOMDATADIR_H + +#include +#include +#include + +class AndroidCustomDataDir : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString dataDir READ dataDir WRITE setDataDir NOTIFY dataDirChanged) + +public: + explicit AndroidCustomDataDir(QObject * parent = nullptr); + + QString dataDir() const { return m_data_dir; } + void setDataDir(const QString & new_data_dir); + QString readCustomDataDir(); + +Q_SIGNALS: + void dataDirChanged(); + +private: + QString m_data_dir; + QString m_default_data_dir; +}; + +#endif // BITCOIN_QML_ANDROIDCUSTOMDATADIR_H From d76761d30650e49751d055575fb4fbf996a27bb1 Mon Sep 17 00:00:00 2001 From: D33r-Gee Date: Tue, 2 Jul 2024 11:52:26 -0700 Subject: [PATCH 9/9] make: added qml/androidcustomdatadir files --- src/Makefile.qt.include | 9 ++++++--- src/qml/androidcustomdatadir.cpp | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 31106bd1d7..1d567fbe3a 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -433,9 +433,12 @@ QML_RES_QML = \ qml/pages/wallet/WalletSelect.qml if TARGET_ANDROID -BITCOIN_QT_H += qml/androidnotifier.h -BITCOIN_QML_BASE_CPP += qml/androidnotifier.cpp -QT_MOC_CPP += qml/moc_androidnotifier.cpp +BITCOIN_QT_H += qml/androidnotifier.h \ + qml/androidcustomdatadir.h +BITCOIN_QML_BASE_CPP += qml/androidnotifier.cpp \ + qml/androidcustomdatadir.cpp +QT_MOC_CPP += qml/moc_androidnotifier.cpp \ + qml/moc_androidcustomdatadir.cpp endif BITCOIN_QT_CPP = $(BITCOIN_QT_BASE_CPP) diff --git a/src/qml/androidcustomdatadir.cpp b/src/qml/androidcustomdatadir.cpp index eba691fc93..6240cac526 100644 --- a/src/qml/androidcustomdatadir.cpp +++ b/src/qml/androidcustomdatadir.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2023 The Bitcoin Core developers +// Copyright (c) 2023-present The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php.