1 // Copyright (c) 2019 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 #include <qt/walletcontroller.h>
6
7 #include <interfaces/handler.h>
8 #include <interfaces/node.h>
9
10 #include <algorithm>
11
12 #include <QApplication>
13 #include <QMessageBox>
14 #include <QMutexLocker>
15 #include <QThread>
16 #include <QWindow>
17
WalletController(interfaces::Node & node,const PlatformStyle * platform_style,OptionsModel * options_model,QObject * parent)18 WalletController::WalletController(interfaces::Node& node, const PlatformStyle* platform_style, OptionsModel* options_model, QObject* parent)
19 : QObject(parent)
20 , m_node(node)
21 , m_platform_style(platform_style)
22 , m_options_model(options_model)
23 {
24 m_handler_load_wallet = m_node.handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) {
25 getOrCreateWallet(std::move(wallet));
26 });
27
28 for (std::unique_ptr<interfaces::Wallet>& wallet : m_node.getWallets()) {
29 getOrCreateWallet(std::move(wallet));
30 }
31
32 m_activity_thread.start();
33 }
34
35 // Not using the default destructor because not all member types definitions are
36 // available in the header, just forward declared.
~WalletController()37 WalletController::~WalletController()
38 {
39 m_activity_thread.quit();
40 m_activity_thread.wait();
41 }
42
getWallets() const43 std::vector<WalletModel*> WalletController::getWallets() const
44 {
45 QMutexLocker locker(&m_mutex);
46 return m_wallets;
47 }
48
getWalletsAvailableToOpen() const49 std::vector<std::string> WalletController::getWalletsAvailableToOpen() const
50 {
51 QMutexLocker locker(&m_mutex);
52 std::vector<std::string> wallets = m_node.listWalletDir();
53 for (WalletModel* wallet_model : m_wallets) {
54 auto it = std::remove(wallets.begin(), wallets.end(), wallet_model->wallet().getWalletName());
55 if (it != wallets.end()) wallets.erase(it);
56 }
57 return wallets;
58 }
59
openWallet(const std::string & name,QWidget * parent)60 OpenWalletActivity* WalletController::openWallet(const std::string& name, QWidget* parent)
61 {
62 OpenWalletActivity* activity = new OpenWalletActivity(this, name);
63 activity->moveToThread(&m_activity_thread);
64 return activity;
65 }
66
closeWallet(WalletModel * wallet_model,QWidget * parent)67 void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent)
68 {
69 QMessageBox box(parent);
70 box.setWindowTitle(tr("Close wallet"));
71 box.setText(tr("Are you sure you wish to close wallet <i>%1</i>?").arg(wallet_model->getDisplayName()));
72 box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled."));
73 box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
74 box.setDefaultButton(QMessageBox::Yes);
75 if (box.exec() != QMessageBox::Yes) return;
76
77 // First remove wallet from node.
78 wallet_model->wallet().remove();
79 // Now release the model.
80 removeAndDeleteWallet(wallet_model);
81 }
82
getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet)83 WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet)
84 {
85 QMutexLocker locker(&m_mutex);
86
87 // Return model instance if exists.
88 if (!m_wallets.empty()) {
89 std::string name = wallet->getWalletName();
90 for (WalletModel* wallet_model : m_wallets) {
91 if (wallet_model->wallet().getWalletName() == name) {
92 return wallet_model;
93 }
94 }
95 }
96
97 // Instantiate model and register it.
98 WalletModel* wallet_model = new WalletModel(std::move(wallet), m_node, m_platform_style, m_options_model, nullptr);
99 m_wallets.push_back(wallet_model);
100
101 connect(wallet_model, &WalletModel::unload, [this, wallet_model] {
102 // Defer removeAndDeleteWallet when no modal widget is active.
103 // TODO: remove this workaround by removing usage of QDiallog::exec.
104 if (QApplication::activeModalWidget()) {
105 connect(qApp, &QApplication::focusWindowChanged, wallet_model, [this, wallet_model]() {
106 if (!QApplication::activeModalWidget()) {
107 removeAndDeleteWallet(wallet_model);
108 }
109 }, Qt::QueuedConnection);
110 } else {
111 removeAndDeleteWallet(wallet_model);
112 }
113 });
114
115 // Re-emit coinsSent signal from wallet model.
116 connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent);
117
118 // Notify walletAdded signal on the GUI thread.
119 if (QThread::currentThread() == thread()) {
120 addWallet(wallet_model);
121 } else {
122 // Handler callback runs in a different thread so fix wallet model thread affinity.
123 wallet_model->moveToThread(thread());
124 bool invoked = QMetaObject::invokeMethod(this, "addWallet", Qt::QueuedConnection, Q_ARG(WalletModel*, wallet_model));
125 assert(invoked);
126 }
127
128 return wallet_model;
129 }
130
addWallet(WalletModel * wallet_model)131 void WalletController::addWallet(WalletModel* wallet_model)
132 {
133 // Take ownership of the wallet model and register it.
134 wallet_model->setParent(this);
135 Q_EMIT walletAdded(wallet_model);
136 }
137
removeAndDeleteWallet(WalletModel * wallet_model)138 void WalletController::removeAndDeleteWallet(WalletModel* wallet_model)
139 {
140 // Unregister wallet model.
141 {
142 QMutexLocker locker(&m_mutex);
143 m_wallets.erase(std::remove(m_wallets.begin(), m_wallets.end(), wallet_model));
144 }
145 Q_EMIT walletRemoved(wallet_model);
146 // Currently this can trigger the unload since the model can hold the last
147 // CWallet shared pointer.
148 delete wallet_model;
149 }
150
151
OpenWalletActivity(WalletController * wallet_controller,const std::string & name)152 OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, const std::string& name)
153 : m_wallet_controller(wallet_controller)
154 , m_name(name)
155 {}
156
open()157 void OpenWalletActivity::open()
158 {
159 std::string error, warning;
160 std::unique_ptr<interfaces::Wallet> wallet = m_wallet_controller->m_node.loadWallet(m_name, error, warning);
161 if (!warning.empty()) {
162 Q_EMIT message(QMessageBox::Warning, QString::fromStdString(warning));
163 }
164 if (wallet) {
165 Q_EMIT opened(m_wallet_controller->getOrCreateWallet(std::move(wallet)));
166 } else {
167 Q_EMIT message(QMessageBox::Critical, QString::fromStdString(error));
168 }
169 Q_EMIT finished();
170 }
171