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