PageRenderTime 69ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 2ms

/src/core/moneychanger.cpp

http://github.com/FellowTraveler/Moneychanger
C++ | 8161 lines | 3371 code | 1077 blank | 3713 comment | 608 complexity | cce05d675dee3ab5a9537a61bc3a4fb3 MD5 | raw file
Possible License(s): LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. /*
  2. * MoneyChanger
  3. * moneychanger.cpp
  4. *
  5. * File organized as follows:
  6. *
  7. * /-- Constructor (Lengthy)--/
  8. * /-- Destructor --/
  9. *
  10. * /-- Systray Functions --/
  11. *
  12. * /-- Menu Dialog Functions --/
  13. *
  14. */
  15. #ifndef __STABLE_HPP__
  16. #include <core/stable.hpp>
  17. #endif
  18. #include <core/moneychanger.hpp>
  19. #include <core/handlers/DBHandler.hpp>
  20. #include <core/handlers/contacthandler.hpp>
  21. #include <core/handlers/modeltradearchive.hpp>
  22. #include <gui/widgets/compose.hpp>
  23. #include <gui/widgets/overridecursor.hpp>
  24. #include <gui/widgets/editdetails.hpp>
  25. #include <gui/widgets/requestdlg.hpp>
  26. #include <gui/widgets/dlgchooser.hpp>
  27. #include <gui/widgets/senddlg.hpp>
  28. #include <gui/widgets/proposeplandlg.hpp>
  29. #include <gui/widgets/createinsurancecompany.hpp>
  30. #include <gui/widgets/wizardconfirmsmartcontract.hpp>
  31. #include <gui/widgets/wizardrunsmartcontract.hpp>
  32. #include <gui/widgets/wizardpartyacct.hpp>
  33. #include <gui/widgets/settings.hpp>
  34. #include <gui/ui/dlgimport.hpp>
  35. #include <gui/ui/dlglog.hpp>
  36. #include <gui/ui/dlgmenu.hpp>
  37. #include <gui/ui/dlgmarkets.hpp>
  38. #include <gui/ui/dlgtradearchive.hpp>
  39. #include <gui/ui/dlgencrypt.hpp>
  40. #include <gui/ui/dlgdecrypt.hpp>
  41. #include <gui/ui/dlginbailment.hpp>
  42. #include <gui/ui/dlgpairnode.hpp>
  43. #include <gui/ui/dlgpassphrasemanager.hpp>
  44. #include <gui/ui/messages.hpp>
  45. #include <gui/ui/payments.hpp>
  46. #include <gui/ui/agreements.hpp>
  47. #include <gui/ui/activity.hpp>
  48. #include <gui/ui/getstringdialog.hpp>
  49. #include <opentxs/opentxs.hpp>
  50. #include <QMenu>
  51. #include <QApplication>
  52. #include <QFile>
  53. #include <QDateTime>
  54. #include <QDebug>
  55. #include <QMessageBox>
  56. #include <QSystemTrayIcon>
  57. #include <QTimer>
  58. #include <QRegExp>
  59. #include <QFlags>
  60. #include <chrono>
  61. #include <functional>
  62. #include <sstream>
  63. #include <utility>
  64. #include <memory>
  65. template class opentxs::Pimpl<opentxs::network::zeromq::PairEventCallback>;
  66. template class std::shared_ptr<const opentxs::OTPayment>;
  67. bool Moneychanger::is_base64(QString string)
  68. {
  69. QRegExp rx("[^a-zA-Z0-9+/=]");
  70. if ( rx.indexIn(string) == -1
  71. && (string.length()%4) == 0
  72. && string.length()>=4) {
  73. return true;
  74. }
  75. return false;
  76. }
  77. /**
  78. * Constructor & Destructor
  79. **/
  80. // Ownership passes to caller.
  81. //static
  82. Moneychanger * Moneychanger::Instantiate(const opentxs::api::client::Manager& manager, QWidget *parent/*=0*/)
  83. {
  84. return new Moneychanger(manager, parent);
  85. }
  86. static Moneychanger * It(bool bShuttingDown = false,
  87. QScopedPointer<Moneychanger> * pMoneychangerMain = nullptr);
  88. // NOT owned by caller.
  89. //static
  90. Moneychanger * Moneychanger::It(bool bShuttingDown/*=false*/, QScopedPointer<Moneychanger> * pMoneychangerMain/*=nullptr*/)
  91. {
  92. // See main.cpp
  93. static QScopedPointer<Moneychanger> & pMoneychanger = *pMoneychangerMain;
  94. if (bShuttingDown)
  95. {
  96. // bShuttingDown is only passed in the very last time this is called,
  97. // (at application shutdown.)
  98. //
  99. // (If we don't delete it at this time, then it won't get deleted until
  100. // the static objects are deleted, which causes a crash since apparently
  101. // that's too late to be deleting the widgets.)
  102. //
  103. pMoneychanger->disconnect();
  104. delete pMoneychanger.take();
  105. return nullptr;
  106. }
  107. // -------------------------------------
  108. if (pMoneychanger.isNull())
  109. return nullptr;
  110. return pMoneychanger.data();
  111. }
  112. Moneychanger::Moneychanger(const opentxs::api::client::Manager& manager, QWidget *parent)
  113. : QWidget(parent),
  114. ot_(manager),
  115. notify_bailment_callback_(opentxs::network::zeromq::ListenCallback::Factory(
  116. [this](const opentxs::network::zeromq::Message& message) -> void {
  117. this->process_notify_bailment(message);
  118. })),
  119. notify_bailment_(ot_.ZMQ().Context().SubscribeSocket(notify_bailment_callback_)),
  120. pair_event_callback_(opentxs::network::zeromq::PairEventCallback::Factory(
  121. [this](const opentxs::proto::PairEvent& event) -> void {
  122. this->process_pair_event(event);
  123. })),
  124. pair_events_(ot_.ZMQ().Context().PairEventListener(pair_event_callback_, ot_.Instance())),
  125. widget_update_callback_(opentxs::network::zeromq::ListenCallback::Factory(
  126. [this](const opentxs::network::zeromq::Message& message) -> void {
  127. this->process_widget_update(message);
  128. })),
  129. widget_update_(ot_.ZMQ().Context().SubscribeSocket(widget_update_callback_)),
  130. // m_list(*(new MTNameLookupQT)),
  131. mc_overall_init(false)
  132. {
  133. /**
  134. ** Init variables *
  135. **/
  136. refresh_count_.store(0);
  137. notify_bailment_->Start(ot_.Endpoints().PendingBailment());
  138. widget_update_->Start(ot_.Endpoints().WidgetUpdate());
  139. ot_.Schedule(
  140. std::chrono::seconds(5),
  141. [&]()->void
  142. {
  143. const auto count =
  144. ot_.Sync().RefreshCount();
  145. const auto existing = refresh_count_.load();
  146. if (existing < count) {
  147. refresh_count_.store(count);
  148. accept_cheques();
  149. //emit needToPopulateRecordlist();
  150. }
  151. }
  152. );
  153. ot_.Schedule(
  154. std::chrono::seconds(20),
  155. [this]()->void{ ot_.Sync().Refresh(); },
  156. (std::chrono::seconds(std::time(nullptr))));
  157. //SQLite database
  158. // This can be moved very easily into a different class
  159. // Which I will inevitably end up doing.
  160. /** Default Nym **/
  161. qDebug() << "Setting up Nym table";
  162. if (0 == DBHandler::getInstance()->querySize("SELECT `nym` FROM `default_nym` WHERE `default_id`='1' LIMIT 0,1"))
  163. {
  164. qDebug() << "Default Nym wasn't set in the database. Inserting blank record...";
  165. DBHandler::getInstance()->runQuery("INSERT INTO `default_nym` (`default_id`,`nym`) VALUES('1','')"); // Blank Row
  166. }
  167. else
  168. {
  169. if (DBHandler::getInstance()->isNext("SELECT `nym` FROM `default_nym` WHERE `default_id`='1' LIMIT 0,1"))
  170. {
  171. default_nym_id = DBHandler::getInstance()->queryString("SELECT `nym` FROM `default_nym` WHERE `default_id`='1' LIMIT 0,1", 0, 0);
  172. }
  173. // -------------------------------------------------
  174. // if (default_nym_id.isEmpty() && (ot_.Exec().GetNymCount() > 0))
  175. // {
  176. // default_nym_id = QString::fromStdString(ot_.Exec().GetNym_ID(0));
  177. // }
  178. // // -------------------------------------------------
  179. // //Ask OT what the display name of this nym is and store it for quick retrieval later on(mostly for "Default Nym" displaying purposes)
  180. // if (!default_nym_id.isEmpty())
  181. // {
  182. // default_nym_name = QString::fromStdString(ot_.Exec().GetNym_Name(default_nym_id.toStdString()));
  183. // }
  184. // else
  185. // qDebug() << "Error loading DEFAULT NYM from SQL";
  186. }
  187. /** Default Server **/
  188. //Query for the default server (So we know for setting later on -- Auto select server associations on later dialogs)
  189. if (DBHandler::getInstance()->querySize("SELECT `server` FROM `default_server` WHERE `default_id`='1' LIMIT 0,1") == 0)
  190. {
  191. qDebug() << "Default Server wasn't set in the database. Inserting blank record...";
  192. DBHandler::getInstance()->runQuery("INSERT INTO `default_server` (`default_id`, `server`) VALUES('1','')"); // Blank Row
  193. }
  194. else
  195. {
  196. if (DBHandler::getInstance()->runQuery("SELECT `server` FROM `default_server` WHERE `default_id`='1' LIMIT 0,1"))
  197. {
  198. default_notary_id = DBHandler::getInstance()->queryString("SELECT `server` FROM `default_server` WHERE `default_id`='1' LIMIT 0,1", 0, 0);
  199. }
  200. // -------------------------------------------------
  201. // if (default_notary_id.isEmpty() && (ot_.Exec().GetServerCount() > 0))
  202. // {
  203. // default_notary_id = QString::fromStdString(ot_.Exec().GetServer_ID(0));
  204. // }
  205. // // -------------------------------------------------
  206. // //Ask OT what the display name of this server is and store it for a quick retrieval later on(mostly for "Default Server" displaying purposes)
  207. // if (!default_notary_id.isEmpty())
  208. // {
  209. // default_server_name = QString::fromStdString(ot_.Exec().GetServer_Name(default_notary_id.toStdString()));
  210. // }
  211. // else
  212. // qDebug() << "Error loading DEFAULT SERVER from SQL";
  213. }
  214. /** Default Asset Type **/
  215. //Query for the default asset (So we know for setting later on -- Auto select asset associations on later dialogs)
  216. if (DBHandler::getInstance()->querySize("SELECT `asset` FROM `default_asset` WHERE `default_id`='1' LIMIT 0,1") == 0)
  217. {
  218. qDebug() << "Default Asset Type wasn't set in the database. Inserting blank record...";
  219. DBHandler::getInstance()->runQuery("INSERT INTO `default_asset` (`default_id`,`asset`) VALUES('1','')"); // Blank Row
  220. }
  221. else
  222. {
  223. if (DBHandler::getInstance()->runQuery("SELECT `asset` FROM `default_asset` WHERE `default_id`='1' LIMIT 0,1"))
  224. {
  225. default_asset_id = DBHandler::getInstance()->queryString("SELECT `asset` FROM `default_asset` WHERE `default_id`='1' LIMIT 0,1", 0, 0);
  226. }
  227. // -------------------------------------------------
  228. // if (default_asset_id.isEmpty() && (ot_.Exec().GetAssetTypeCount() > 0))
  229. // {
  230. // default_asset_id = QString::fromStdString(ot_.Exec().GetAssetType_ID(0));
  231. // }
  232. // // -------------------------------------------------
  233. // //Ask OT what the display name of this asset type is and store it for a quick retrieval later on(mostly for "Default Asset" displaying purposes)
  234. // if (!default_asset_id.isEmpty())
  235. // {
  236. // default_asset_name = QString::fromStdString(ot_.Exec().GetAssetType_Name(default_asset_id.toStdString()));
  237. // }
  238. // else
  239. // qDebug() << "Error loading DEFAULT ASSET from SQL";
  240. }
  241. /** Default Account **/
  242. //Query for the default account (So we know for setting later on -- Auto select account associations on later dialogs)
  243. if (DBHandler::getInstance()->querySize("SELECT `account` FROM `default_account` WHERE `default_id`='1' LIMIT 0,1") == 0)
  244. {
  245. qDebug() << "Default Account wasn't set in the database. Inserting blank record...";
  246. DBHandler::getInstance()->runQuery("INSERT INTO `default_account` (`default_id`,`account`) VALUES('1','')"); // Blank Row
  247. }
  248. else
  249. {
  250. if (DBHandler::getInstance()->runQuery("SELECT `account` FROM `default_account` WHERE `default_id`='1' LIMIT 0,1"))
  251. {
  252. default_account_id = DBHandler::getInstance()->queryString("SELECT `account` FROM `default_account` WHERE `default_id`='1' LIMIT 0,1", 0, 0);
  253. }
  254. // -------------------------------------------------
  255. // if (default_account_id.isEmpty() && (ot_.Exec().GetAccountCount() > 0))
  256. // {
  257. // default_account_id = QString::fromStdString(ot_.Exec().GetAccountWallet_ID(0));
  258. // }
  259. // // -------------------------------------------------
  260. // //Ask OT what the display name of this account is and store it for a quick retrieval later on(mostly for "Default Account" displaying purposes)
  261. // if (!default_account_id.isEmpty())
  262. // {
  263. // default_account_name = QString::fromStdString(ot_.Exec().GetAccountWallet_Name(default_account_id.toStdString()));
  264. // }
  265. // else
  266. // qDebug() << "Error loading DEFAULT ACCOUNT from SQL";
  267. }
  268. // ----------------------------------------------------------------------------
  269. //Ask OT for "cash account" information (might be just "Account" balance)
  270. //Ask OT the purse information
  271. /* *** *** ***
  272. * Init Memory Trackers (there may be other int below than just memory trackers but generally there will be mostly memory trackers below)
  273. * Allows the program to boot with a low footprint -- keeps start times low no matter the program complexity;
  274. * Memory will expand as the operator opens dialogs;
  275. * Also prevents HTTP requests from overloading or spamming the operators device by only allowing one window of that request;
  276. * *** *** ***/
  277. //Init MC System Tray Icon
  278. mc_systrayIcon = new QSystemTrayIcon(this);
  279. mc_systrayIcon->setIcon(QIcon(":/icons/moneychanger"));
  280. //Init Icon resources (Loading resources/access Harddrive first; then send to GPU; This specific order will in theory prevent bottle necking between HDD/GPU)
  281. mc_systrayIcon_shutdown = QIcon(":/icons/quit");
  282. mc_systrayIcon_overview = QIcon(":/icons/overview");
  283. mc_systrayIcon_nym = QIcon(":/icons/icons/identity_BW2.png");
  284. mc_systrayIcon_server = QIcon(":/icons/server");
  285. mc_systrayIcon_goldaccount = QIcon(":/icons/icons/safe_box.png");
  286. mc_systrayIcon_purse = QIcon(":/icons/icons/assets.png");
  287. // mc_systrayIcon_sendfunds = QIcon(":/icons/icons/fistful_of_cash_72.png");
  288. mc_systrayIcon_sendfunds = QIcon(":/icons/icons/money_fist4_small.png");
  289. // mc_systrayIcon_sendfunds = QIcon(":/icons/sendfunds");
  290. mc_systrayIcon_requestfunds = QIcon(":/icons/requestpayment");
  291. mc_systrayIcon_proposeplan = QIcon(":/icons/icons/timer.png");
  292. // mc_systrayIcon_contacts = QIcon(":/icons/addressbook");
  293. mc_systrayIcon_contacts = QIcon(":/icons/icons/rolodex_card2");
  294. mc_systrayIcon_composemessage = QIcon(":/icons/icons/pencil.png");
  295. // mc_systrayIcon_composemessage = QIcon(":/icons/icons/compose.png");
  296. mc_systrayIcon_markets = QIcon(":/icons/markets");
  297. mc_systrayIcon_trade_archive = QIcon(":/icons/overview");
  298. mc_systrayIcon_active_agreements = QIcon(":/icons/agreements");
  299. mc_systrayIcon_pending = QIcon(":/icons/icons/pending.png");
  300. // ---------------------------------------------------------------
  301. mc_systrayIcon_bitcoin = QIcon(":/icons/icons/bitcoin.png");
  302. mc_systrayIcon_crypto = QIcon(":/icons/icons/lock.png");
  303. //Submenu
  304. mc_systrayIcon_crypto_encrypt = QIcon(":/icons/icons/lock.png");
  305. mc_systrayIcon_crypto_decrypt = QIcon(":/icons/icons/padlock_open.png");
  306. mc_systrayIcon_crypto_sign = QIcon(":/icons/icons/signature-small.png");
  307. mc_systrayIcon_crypto_verify = QIcon(":/icons/icons/check-mark-small.png");
  308. // ---------------------------------------------------------------
  309. mc_systrayIcon_advanced = QIcon(":/icons/advanced");
  310. //Submenu
  311. mc_systrayIcon_advanced_import = QIcon(":/icons/icons/request.png");
  312. mc_systrayIcon_advanced_smartcontracts = QIcon(":/icons/icons/smart_contract_64.png");
  313. mc_systrayIcon_advanced_corporations = QIcon(":/icons/icons/buildings.png");
  314. mc_systrayIcon_advanced_transport = QIcon(":/icons/icons/p2p.png");
  315. mc_systrayIcon_advanced_log = QIcon(":/icons/icons/p2p.png");
  316. mc_systrayIcon_settings = QIcon(":/icons/settings");
  317. // ----------------------------------------------
  318. QString qstrExpertMode = MTContactHandler::getInstance()->
  319. GetValueByID("expertmode", "parameter1", "settings", "setting");
  320. bExpertMode_ = (0 == qstrExpertMode.compare("on"));
  321. // -------------------------------------------------
  322. QString qstrHideNav = MTContactHandler::getInstance()->
  323. GetValueByID("hidenav", "parameter1", "settings", "setting");
  324. bHideNav_ = (0 == qstrHideNav.compare("on"));
  325. // -------------------------------------------------
  326. // setupRecordList();
  327. mc_overall_init = true;
  328. }
  329. void Moneychanger::accept_cheques() const
  330. {
  331. const auto nyms = ot_.OTAPI().LocalNymList();
  332. for (const auto& nym : nyms) {
  333. ot_.Sync().DepositCheques(nym);
  334. }
  335. }
  336. void Moneychanger::process_notify_bailment(
  337. const opentxs::network::zeromq::Message& multipartMessage)
  338. {
  339. OT_ASSERT(1 == multipartMessage.Body().size());
  340. std::string message(*multipartMessage.Body().begin());
  341. auto request =
  342. opentxs::proto::TextToProto<opentxs::proto::PeerRequest>(message);
  343. if (false == opentxs::proto::Validate(request, false)) {
  344. std::cout << __FUNCTION__ << ": Invalid peer request" << std::endl;
  345. return;
  346. }
  347. if (opentxs::proto::PEERREQUEST_PENDINGBAILMENT != request.type()) {
  348. std::cout << __FUNCTION__ << ": Not a notify bailment request"
  349. << std::endl;
  350. return;
  351. }
  352. QString txid = QString::fromStdString(request.pendingbailment().txid());
  353. QString msg = QString(tr("Received notice of pending deposit from "
  354. "blockchain: ")) + txid;
  355. emit appendToLog(msg);
  356. std::cout << __FUNCTION__ << ": Received notice of pending bailment: "
  357. << request.pendingbailment().txid().c_str() << std::endl;
  358. }
  359. void Moneychanger::process_pair_event(const opentxs::proto::PairEvent& event)
  360. {
  361. std::cout << __FUNCTION__ << ": Received pair event" << std::endl;
  362. switch (event.type()) {
  363. case opentxs::proto::PAIREVENT_RENAME: {
  364. std::cout << __FUNCTION__ << ": Issuer " << event.issuer()
  365. << " is ready to be renamed." << std::endl;
  366. } break;
  367. case opentxs::proto::PAIREVENT_STORESECRET: {
  368. QString msg(tr("Your seed backup has been stored on your Stash "
  369. "Node Pro. You may now remove the backup USB device "
  370. "and store it in a safe location."));
  371. emit appendToLog(msg);
  372. std::cout << __FUNCTION__ << ": Issuer " << event.issuer()
  373. << " has stored a seed backup for us." << std::endl;
  374. } break;
  375. case opentxs::proto::PAIREVENT_ERROR:
  376. default: {
  377. std::cout << __FUNCTION__ << ": Unknown or invalid event type"
  378. << std::endl;
  379. }
  380. }
  381. }
  382. void Moneychanger::process_widget_update(
  383. const opentxs::network::zeromq::Message& multipartMessage)
  384. {
  385. OT_ASSERT(1 == multipartMessage.Body().size());
  386. std::string message(*multipartMessage.Body().begin());
  387. const std::string str_msg = std::string(message);
  388. const QString qstrMsg = QString::fromStdString(str_msg);
  389. emit opentxsWidgetUpdated(qstrMsg);
  390. }
  391. // RETRIEVE NYM INTERMEDIARY FILES
  392. // Returns:
  393. // True if I have enough numbers, or if there was success getting more
  394. // transaction numbers.
  395. // False if I didn't have enough numbers, tried to get more, and failed
  396. // somehow.
  397. //
  398. bool Moneychanger::retrieve_nym(
  399. const std::string& strNotaryID,
  400. const std::string& strMyNymID) const
  401. {
  402. auto context = ot_.Wallet().mutable_ServerContext(
  403. opentxs::Identifier::Factory(strMyNymID), opentxs::Identifier::Factory(strNotaryID));
  404. opentxs::Utility MsgUtil(context.It(), ot_);
  405. if (0 >= context.It().UpdateRequestNumber()) {
  406. return false;
  407. }
  408. bool msgWasSent = false;
  409. std::int32_t nGetAndProcessNymbox = MsgUtil.getAndProcessNymbox_4(
  410. strNotaryID, strMyNymID, msgWasSent, true);
  411. if (0 > nGetAndProcessNymbox) {
  412. return false;
  413. }
  414. return true;
  415. }
  416. void Moneychanger::onNeedToCheckNym(QString myNymId, QString hisNymId, QString notaryId)
  417. {
  418. if (hisNymId.isEmpty())
  419. {
  420. qDebug() << __FUNCTION__ << ": ERROR: no hisNymId passed in! (Returning.)";
  421. return;
  422. }
  423. // ---------------------------
  424. if (myNymId.isEmpty())
  425. myNymId = getDefaultNymID();
  426. if (myNymId.isEmpty())
  427. {
  428. //Count nyms
  429. const int32_t nym_count = ot_.Exec().GetNymCount();
  430. if (nym_count > 0)
  431. myNymId = QString::fromStdString(ot_.Exec().GetNym_ID(0));
  432. }
  433. if (myNymId.isEmpty())
  434. {
  435. qDebug() << __FUNCTION__ << ": no myNymId passed in, and unable to figure out one on my own! (Returning.)";
  436. return;
  437. }
  438. // ---------------------------
  439. // If a notary was passed in, let's make sure we have its contract!
  440. //
  441. if (!notaryId.isEmpty())
  442. {
  443. const std::string str_notary_contract = ot_.Exec().GetServer_Contract(notaryId.toStdString());
  444. if (str_notary_contract.empty())
  445. {
  446. notaryId = QString("");
  447. qDebug() << __FUNCTION__ << ": We don't have the server contract for the specified notary ID. "
  448. "(I guess we'll try the default, it may yet work.) TODO: Download it from the DHT.";
  449. }
  450. }
  451. // ---------------------------
  452. mapIDName mapServers;
  453. bool bGotServersForNym = false;
  454. // Hmm, no notary ID was passed. Perhaps one wasn't available.
  455. // In that case we'll see if there are any known notaries for hisNymId.
  456. // If so, we'll use them all.
  457. // Otherwise, we'll try to use the default notary instead.
  458. // Otherwise we'll give up :P
  459. //
  460. if (notaryId.isEmpty())
  461. bGotServersForNym = MTContactHandler::getInstance()->GetServers(mapServers, hisNymId);
  462. else
  463. mapServers.insert(notaryId, "Server Name Unused Here");
  464. if (mapServers.size() < 1)
  465. {
  466. notaryId = getDefaultNotaryID();
  467. if (!notaryId.isEmpty())
  468. mapServers.insert(notaryId, "Server Name Unused Here");
  469. }
  470. // Oh well. We tried.
  471. if (mapServers.size() < 1)
  472. {
  473. qDebug() << __FUNCTION__ << ": no notaryId passed in, and unable to figure out one on my own! (Returning.)";
  474. return;
  475. }
  476. // --------------------------------
  477. const std::string my_nym_id = myNymId.toStdString();
  478. const std::string his_nym_id = hisNymId.toStdString();
  479. for (mapIDName::iterator it_servers = mapServers.begin(); it_servers != mapServers.end(); ++it_servers)
  480. {
  481. QString qstrNotaryID = it_servers.key();
  482. // QString qstrNotaryName = it_servers.value();
  483. if (qstrNotaryID.isEmpty()) // Weird, should never happen.
  484. {
  485. qDebug() << __FUNCTION__ << ": Unexpectedly empty NotaryID, should not happen. (Returning.)";
  486. return;
  487. }
  488. const std::string notary_id = qstrNotaryID.toStdString();
  489. // ---------------------------------------------
  490. // Need to verify that I'M registered at this server, before I try to
  491. // download some guy's credentials from a "server he's known to use."
  492. // I may not even be registered there, in which case the check_nym call would fail.
  493. //
  494. // And how do I know if I even have the server contract at all?
  495. //
  496. const std::string str_server_contract = ot_.Exec().GetServer_Contract(notary_id);
  497. if (str_server_contract.empty())
  498. {
  499. qDebug() << __FUNCTION__ << ": We don't have the server contract for this notary ID. (Skipping.) TODO: Download it from the DHT.";
  500. continue;
  501. }
  502. // ---------------------------------------------
  503. const bool isReg = ot_.Exec().IsNym_RegisteredAtServer(my_nym_id, notary_id);
  504. if (!isReg)
  505. {
  506. std::string response;
  507. {
  508. MTSpinner theSpinner;
  509. response = opentxs::String::Factory(ot_.Sync().RegisterNym(opentxs::Identifier::Factory(my_nym_id), opentxs::Identifier::Factory(notary_id), true))->Get();
  510. if (response.empty() && !ot_.Exec().CheckConnection(notary_id))
  511. {
  512. QString qstrErrorMsg;
  513. qstrErrorMsg = QString("%1: %2. %3.").
  514. arg(tr("Failed trying to contact notary")).
  515. arg(qstrNotaryID).arg(tr("Perhaps it is down, or there might be a network problem"));
  516. emit appendToLog(qstrErrorMsg);
  517. continue;
  518. }
  519. }
  520. if (!opentxs::VerifyMessageSuccess(ot_, response)) {
  521. Moneychanger::It()->HasUsageCredits(notary_id, my_nym_id);
  522. continue;
  523. }
  524. else
  525. MTContactHandler::getInstance()->NotifyOfNymServerPair(myNymId, qstrNotaryID);
  526. }
  527. // ------------------------------
  528. {
  529. std::string response;
  530. {
  531. MTSpinner theSpinner;
  532. const auto notaryID = opentxs::Identifier::Factory(notary_id);
  533. const auto myNymID = opentxs::Identifier::Factory(my_nym_id);
  534. const auto hisNymID = opentxs::Identifier::Factory(his_nym_id);
  535. auto action = ot_.ServerAction().DownloadNym(myNymID, notaryID, hisNymID);
  536. response = action->Run();
  537. }
  538. if (response.empty() && !ot_.Exec().CheckConnection(notary_id))
  539. {
  540. QString qstrErrorMsg;
  541. qstrErrorMsg = QString("%1: %2. %3.").
  542. arg(tr("Failed trying to contact notary")).
  543. arg(qstrNotaryID).arg(tr("Perhaps it is down, or there might be a network problem"));
  544. emit appendToLog(qstrErrorMsg);
  545. continue;
  546. }
  547. int32_t nReturnVal = opentxs::VerifyMessageSuccess(ot_, response);
  548. if (1 == nReturnVal)
  549. {
  550. emit nymWasJustChecked(hisNymId);
  551. break;
  552. }
  553. }
  554. } // for (servers)
  555. }
  556. //QString getBitmessageAddressFromClaims(const QString & claimant_nym_id);
  557. //QString getDisplayNameFromClaims(const QString & claimant_nym_id);
  558. // Someone did this: OT_ME::check_nym(id); emit nymWasJustChecked(id);
  559. //
  560. void Moneychanger::onCheckNym(QString nymId)
  561. {
  562. // ----------------------------------------------
  563. auto strNymId = opentxs::String::Factory(nymId.toStdString());
  564. auto id_nym = opentxs::Identifier::Factory(strNymId);
  565. // ----------------------------------------------
  566. // Get the Nym. Make sure we have the latest copy, since his credentials were apparently
  567. // just downloaded and overwritten.
  568. //
  569. std::shared_ptr<const opentxs::Nym> pCurrentNym =
  570. ot_.Wallet().Nym(id_nym);
  571. if (false == bool(pCurrentNym))
  572. {
  573. qDebug() << "onCheckNym: Loading the nym failed. (Which should NOT happen since we supposedly JUST downloaded that Nym's credentials...)";
  574. return;
  575. }
  576. // // ------------------------------------------------
  577. // // Clear the claims and verifications we already have in the database. (If any.)
  578. // //
  579. // MTContactHandler::getInstance()->clearClaimsForNym(nymId);
  580. // // ----------------------------------------------
  581. // // Import the claims.
  582. // const std::string str_checked_nym_id(strNymId.Get());
  583. // const auto data =
  584. // ot_.Exec().GetContactData(nymId.toStdString());
  585. // auto claims =
  586. // opentxs::proto::DataToProto<opentxs::proto::ContactData>
  587. // (opentxs::Data::Factory(data.c_str(), static_cast<uint32_t>(data.length())));
  588. // for (const auto& section: claims.section()) {
  589. // for (const auto& claim: section.item()) {
  590. // // ---------------------------------------
  591. // const uint32_t claim_section = section.name();
  592. // const uint32_t claim_type = claim.type();
  593. // const QString claim_value = QString::fromStdString(claim.value());
  594. // // Add the claim to the database if not there already.
  595. // const bool upserted =
  596. // MTContactHandler::getInstance()->upsertClaim(
  597. // *pCurrentNym,
  598. // claim_section,
  599. // claim);
  600. // if (!upserted) {
  601. // qDebug() << "onCheckNym: the call to upsertClaim just failed. "
  602. // << "(Returning.)";
  603. // return;
  604. // }
  605. // bool claim_att_active = false;
  606. // bool claim_att_primary = false;
  607. // for (const auto& attribute : claim.attribute()) {
  608. // if (opentxs::proto::CITEMATTR_ACTIVE == attribute) {
  609. // claim_att_active = true;
  610. // }
  611. // if (opentxs::proto::CITEMATTR_PRIMARY == attribute) {
  612. // claim_att_primary = true;
  613. // }
  614. // }
  615. // if (claim_att_active && claim_att_primary) {
  616. // if (claim_section == opentxs::proto::CONTACTSECTION_IDENTIFIER) {
  617. // MTContactHandler::getInstance()->NotifyOfNymNamePair(
  618. // nymId,
  619. // claim_value);
  620. // }
  621. // if ((claim_section == opentxs::proto::CONTACTSECTION_COMMUNICATION) &&
  622. // (claim_type == opentxs::proto::CITEMTYPE_BITMESSAGE)) {
  623. // // NOTE: May not need to do anything here. We already
  624. // // imported the claims, and we can already search the
  625. // // claims for Bitmessage address and NymID, which we
  626. // // are already doing.
  627. // }
  628. // }
  629. // }
  630. // }
  631. // // -------------------------------------------------------
  632. // // Import the verifications.
  633. // //
  634. // const auto ver_data =
  635. // ot_.Exec().GetVerificationSet(nymId.toStdString());
  636. // auto the_set =
  637. // opentxs::proto::DataToProto<opentxs::proto::VerificationSet>
  638. // (opentxs::Data::Factory(ver_data.c_str(), ver_data.length()));
  639. // // Internal verifications:
  640. // // Here I'm looping through pCurrentNym's verifications of other people's claims.
  641. // for (auto& claimant: the_set.internal().identity()) {
  642. // // Here we're looping through those other people. (Claimants.)
  643. // const std::string& str_claimant_id = claimant.nym();
  644. // for (auto& verification : claimant.verification()) {
  645. // const bool success =
  646. // MTContactHandler::getInstance()->upsertClaimVerification(
  647. // str_claimant_id,
  648. // str_checked_nym_id,
  649. // verification,
  650. // true); //bIsInternal=true
  651. // if (!success) {
  652. // qDebug() << "onCheckNym: the call to "
  653. // << "upsertInternalClaimVerification just failed. "
  654. // << "(Returning.)";
  655. // return;
  656. // }
  657. // }
  658. // }
  659. // // External verifications:
  660. // // Here I'm looping through other people's verifications of pCurrentNym's claims.
  661. // for (auto& verifier: the_set.external().identity()) {
  662. // const std::string& str_verifier_id = verifier.nym();
  663. // for (auto& verification : verifier.verification()) {
  664. // const bool success =
  665. // MTContactHandler::getInstance()->upsertClaimVerification(
  666. // str_checked_nym_id,
  667. // str_verifier_id,
  668. // verification,
  669. // false); //bIsInternal=true by default.
  670. // if (!success) {
  671. // qDebug() << "onCheckNym: the call to "
  672. // << "upsertExternalClaimVerification just failed. "
  673. // << "(Returning.)";
  674. // return;
  675. // }
  676. // }
  677. // }
  678. // // -------------------------------------------------------
  679. // // Import the repudiations.
  680. // // -------------------------------------------------------
  681. // // emit signal that claims / verifications were updated.
  682. // //
  683. emit claimsUpdatedForNym(nymId);
  684. }
  685. static void blah()
  686. {
  687. //resume
  688. //todo
  689. // OT_API.hpp
  690. //EXPORT VerificationSet GetVerificationSet(const Nym& fromNym) const;
  691. // EXPORT bool SetVerifications(Nym& onNym,
  692. // const proto::VerificationSet&) const;
  693. // Nym.hpp
  694. // std::shared_ptr<proto::VerificationSet> VerificationSet() const;
  695. // bool SetVerificationSet(const proto::VerificationSet& data);
  696. //
  697. // proto::Verification Sign(
  698. // const std::string& claim,
  699. // const bool polarity,
  700. // const int64_t start = 0,
  701. // const int64_t end = 0,
  702. // const OTPasswordData* pPWData = nullptr) const;
  703. // bool Verify(const proto::Verification& item) const;
  704. // VerificationSet has 2 groups, internal and external.
  705. // Internal is for your signatures on other people's claims.
  706. // External is for other people's signatures on your claims.
  707. // When you find that in the external, you copy it to your own credential.
  708. // So external is for re-publishing other people's verifications of your claims.
  709. // If we've repudiated any claims, you can add their IDs to the repudiated field in the verification set.
  710. }
  711. Moneychanger::~Moneychanger()
  712. {
  713. }
  714. //opentxs::OTRecordList & Moneychanger::GetRecordlist()
  715. //{
  716. // return m_list;
  717. //}
  718. //void Moneychanger::setupRecordList()
  719. //{
  720. // int nServerCount = ot_.Exec().GetServerCount();
  721. // int nAssetCount = ot_.Exec().GetAssetTypeCount();
  722. // int nNymCount = ot_.Exec().GetNymCount();
  723. // // ----------------------------------------------------
  724. // GetRecordlist().ClearServers();
  725. // GetRecordlist().ClearAssets();
  726. // GetRecordlist().ClearNyms();
  727. // GetRecordlist().ClearAccounts();
  728. // // ----------------------------------------------------
  729. // for (int ii = 0; ii < nServerCount; ++ii)
  730. // {
  731. // std::string NotaryID = ot_.Exec().GetServer_ID(ii);
  732. // GetRecordlist().AddNotaryID(NotaryID);
  733. // }
  734. // // ----------------------------------------------------
  735. // for (int ii = 0; ii < nAssetCount; ++ii)
  736. // {
  737. // std::string InstrumentDefinitionID = ot_.Exec().GetAssetType_ID(ii);
  738. // GetRecordlist().AddInstrumentDefinitionID(InstrumentDefinitionID);
  739. // }
  740. // // ----------------------------------------------------
  741. // for (int ii = 0; ii < nNymCount; ++ii)
  742. // {
  743. // std::string nymId = ot_.Exec().GetNym_ID(ii);
  744. // GetRecordlist().AddNymID(nymId);
  745. // }
  746. // // ----------------------------------------------------
  747. // for (const auto& [accountID, alias] : ot_.DB().AccountList())
  748. // {
  749. // GetRecordlist().AddAccountID(accountID);
  750. // }
  751. // // ----------------------------------------------------
  752. // GetRecordlist().AcceptChequesAutomatically (true);
  753. // GetRecordlist().AcceptReceiptsAutomatically (true);
  754. // GetRecordlist().AcceptTransfersAutomatically(false);
  755. // GetRecordlist().IgnoreMail(!expertMode());
  756. //}
  757. // Calls OTRecordList::Populate(), and then additionally adds records from Bitmessage, etc.
  758. //
  759. //void Moneychanger::populateRecords(bool bCurrentlyModifying/*=false*/)
  760. //{
  761. // GetRecordlist().Populate(); // Refreshes the OT data from local storage. < << <<==============***
  762. // ---------------------------------------------------------------------
  763. // QList<QString> listCheckOnlyOnce; // So we don't call checkMail more than once for the same connect string.
  764. // // ---------------------------------------------------------------------
  765. // // Let's see if, additionally, there are any Bitmessage records (etc)
  766. // // for the Nyms that we care about. (If we didn't add a Nym ID to GetRecordlist()'s
  767. // // list of Nyms, then we don't care about any Bitmessages for that Nym.)
  768. // //
  769. // bool bNeedsReSorting = false;
  770. // const opentxs::list_of_strings & the_nyms = GetRecordlist().GetNyms();
  771. // for (opentxs::list_of_strings::const_iterator it = the_nyms.begin(); it != the_nyms.end(); ++it)
  772. // {
  773. // const std::string str_nym_id = *it;
  774. // // -----------------------------
  775. // mapIDName mapMethods;
  776. // QString filterByNym = QString::fromStdString(str_nym_id);
  777. // bool bGotMethods = !filterByNym.isEmpty() ? MTContactHandler::getInstance()->GetMsgMethodsByNym(mapMethods, filterByNym, false, QString("")) : false;
  778. // if (bGotMethods)
  779. // {
  780. // // Loop through mapMethods and for each methodID, call GetAddressesByNym.
  781. // // Then for each address, grab the inbox and outbox from MTComms, and add
  782. // // the messages to GetRecordlist().
  783. // //
  784. // for (mapIDName::iterator ii = mapMethods.begin(); ii != mapMethods.end(); ++ii)
  785. // {
  786. // QString qstrID = ii.key();
  787. // int nFilterByMethodID = 0;
  788. // QStringList stringlist = qstrID.split("|");
  789. // if (stringlist.size() >= 2) // Should always be 2...
  790. // {
  791. //// QString qstrType = stringlist.at(0);
  792. // QString qstrMethodID = stringlist.at(1);
  793. // nFilterByMethodID = qstrMethodID.isEmpty() ? 0 : qstrMethodID.toInt();
  794. // // --------------------------------------
  795. // if (nFilterByMethodID > 0)
  796. // {
  797. // QString qstrMethodType = MTContactHandler::getInstance()->GetMethodType (nFilterByMethodID);
  798. // QString qstrTypeDisplay = MTContactHandler::getInstance()->GetMethodTypeDisplay(nFilterByMethodID);
  799. // QString qstrConnectStr = MTContactHandler::getInstance()->GetMethodConnectStr (nFilterByMethodID);
  800. // if (!qstrConnectStr.isEmpty())
  801. // {
  802. // NetworkModule * pModule = MTComms::find(qstrConnectStr.toStdString());
  803. // if ((nullptr == pModule) && MTComms::add(qstrMethodType.toStdString(), qstrConnectStr.toStdString()))
  804. // pModule = MTComms::find(qstrConnectStr.toStdString());
  805. // if (nullptr == pModule)
  806. // // todo probably need a messagebox here.
  807. // qDebug() << QString("PopulateRecords: Unable to add a %1 interface with connection string: %2").arg(qstrMethodType).arg(qstrConnectStr);
  808. // if ((nullptr != pModule) && pModule->accessible())
  809. // {
  810. // if ((-1) == listCheckOnlyOnce.indexOf(qstrConnectStr)) // Not on the list yet.
  811. // {
  812. // pModule->checkMail();
  813. // listCheckOnlyOnce.insert(0, qstrConnectStr);
  814. // }
  815. // // ------------------------------
  816. // mapIDName mapAddresses;
  817. // if (MTContactHandler::getInstance()->GetAddressesByNym(mapAddresses, filterByNym, nFilterByMethodID))
  818. // {
  819. // for (mapIDName::iterator jj = mapAddresses.begin(); jj != mapAddresses.end(); ++jj)
  820. // {
  821. // QString qstrAddress = jj.key();
  822. // if (!qstrAddress.isEmpty())
  823. // {
  824. // // --------------------------------------------------------------------------------------------
  825. // // INBOX
  826. // //
  827. // std::vector< _SharedPtr<NetworkMail> > theInbox = pModule->getInbox(qstrAddress.toStdString());
  828. // for (std::vector< _SharedPtr<NetworkMail> >::size_type nIndex = 0; nIndex < theInbox.size(); ++nIndex)
  829. // {
  830. // _SharedPtr<NetworkMail> & theMsg = theInbox[nIndex];
  831. // std::string strSubject = theMsg->getSubject();
  832. // std::string strContents = theMsg->getMessage();
  833. // // ----------------------------------------------------
  834. // QString qstrFinal;
  835. // if (!strSubject.empty())
  836. // qstrFinal = QString("%1: %2\n%3").
  837. // arg(tr("Subject")).
  838. // arg(QString::fromStdString(strSubject)).
  839. // arg(QString::fromStdString(strContents));
  840. // else
  841. // qstrFinal = QString::fromStdString(strContents);
  842. // // ----------------------------------------------------
  843. // bNeedsReSorting = true;
  844. // if (!theMsg->getMessageID().empty())
  845. // GetRecordlist().AddSpecialMsg(theMsg->getMessageID(),
  846. // false, //bIsOutgoing=false
  847. // static_cast<int32_t>(nFilterByMethodID),
  848. // qstrFinal.toStdString(),
  849. // theMsg->getTo(),
  850. // theMsg->getFrom(),
  851. // qstrMethodType.toStdString(),
  852. // qstrTypeDisplay.toStdString(),
  853. // str_nym_id,
  854. // static_cast<time64_t>(theMsg->getReceivedTime()));
  855. // } // for (inbox)
  856. // // --------------------------------------------------------------------------------------------
  857. // // OUTBOX
  858. // //
  859. // std::vector< _SharedPtr<NetworkMail> > theOutbox = pModule->getOutbox(qstrAddress.toStdString());
  860. // for (std::vector< _SharedPtr<NetworkMail> >::size_type nIndex = 0; nIndex < theOutbox.size(); ++nIndex)
  861. // {
  862. // _SharedPtr<NetworkMail> & theMsg = theOutbox[nIndex];
  863. // std::string strSubject = theMsg->getSubject();
  864. // std::string strContents = theMsg->getMessage();
  865. // // ----------------------------------------------------
  866. // QString qstrFinal;
  867. // if (!strSubject.empty())
  868. // qstrFinal = QString("%1: %2\n%3").
  869. // arg(tr("Subject")).
  870. // arg(QString::fromStdString(strSubject)).
  871. // arg(QString::fromStdString(strContents));
  872. // else
  873. // qstrFinal = QString::fromStdString(strContents);
  874. // // ----------------------------------------------------
  875. // bNeedsReSorting = true;
  876. //// qDebug() << QString("Adding OUTGOING theMsg->getMessageID(): %1 \n filterByNym: %2 \n qstrAddress: %3 \n nIndex: %4")
  877. //// .arg(QString::fromStdString(theMsg->getMessageID()))
  878. //// .arg(filterByNym)
  879. //// .arg(qstrAddress)
  880. //// .arg(nIndex)
  881. //// ;
  882. // if (!theMsg->getMessageID().empty())
  883. // GetRecordlist().AddSpecialMsg(theMsg->getMessageID(),
  884. // true, //bIsOutgoing=true
  885. // static_cast<int32_t>(nFilterByMethodID),
  886. // qstrFinal.toStdString(),
  887. // theMsg->getFrom(),
  888. // theMsg->getTo(),
  889. // qstrMethodType.toStdString(),
  890. // qstrTypeDisplay.toStdString(),
  891. // str_nym_id,
  892. // static_cast<time64_t>(theMsg->getSentTime()));
  893. // } // for (outbox)
  894. // } // if (!qstrAddress.isEmpty())
  895. // } // for (addresses)
  896. // } // if GetAddressesByNym
  897. // } // if ((nullptr != pModule) && pModule->accessible())
  898. // } // if (!qstrConnectStr.isEmpty())
  899. // } // if nFilterByMethodID > 0
  900. // } // if (stringlist.size() >= 2)
  901. // } // for (methods)
  902. // } // if bGotMethods
  903. // } // for (nyms)
  904. // // -----------------------------------------------------
  905. // if (bNeedsReSorting)
  906. // GetRecordlist().SortRecords();
  907. // // -----------------------------------------------------
  908. // // This takes things like market receipts out of the record list
  909. // // and moves to their own database table.
  910. // // Same thing for mail messages, etc.
  911. // //
  912. // if (!bCurrentlyModifying)
  913. // modifyRecords();
  914. //}
  915. // ---------------------------------------------------------------
  916. // Check and see if the Nym has exhausted his usage credits.
  917. //
  918. // Return value: -2 for error, -1 for "unlimited" (or "server isn't enforcing"),
  919. // 0 for "exhausted", and non-zero for the exact number of credits available.
  920. int64_t Moneychanger::HasUsageCredits(const std::string & notary_id,
  921. const std::string & NYM_ID)
  922. {
  923. // Usually when a message fails, Moneychanger calls HasUsageCredits because it assumes
  924. // the failure probably happened due to a lack of usage credits.
  925. // ...But what if there was a network failure? What if messages can't even get out?
  926. //
  927. if (!ot_.Exec().CheckConnection(notary_id))
  928. {
  929. QString qstrErrorMsg;
  930. qstrErrorMsg = tr("HasUsageCredits: Failed trying to contact the notary. Perhaps it is down, or there might be a network problem.");
  931. emit appendToLog(qstrErrorMsg);
  932. return -2;
  933. }
  934. // --------------------------------------------------------
  935. std::string strMessage;
  936. {
  937. MTSpinner theSpinner;
  938. const auto notaryID = opentxs::Identifier::Factory(notary_id);
  939. const auto nymID = opentxs::Identifier::Factory(NYM_ID);
  940. auto action = ot_.ServerAction().AdjustUsageCredits(nymID, notaryID, nymID, 0);
  941. strMessage = action->Run();
  942. }
  943. if (strMessage.empty())
  944. {
  945. // QString qstrErrorMsg;
  946. // qstrErrorMsg = tr("Moneychanger::HasUsageCredits: Error 'strMessage' is Empty!");
  947. // emit appendToLog(qstrErrorMsg);
  948. return -2;
  949. }
  950. // --------------------------------------------------------
  951. const int64_t lReturnValue = ot_.Exec().Message_GetUsageCredits(strMessage);
  952. // --------------------------------------------------------
  953. QString qstrErrorMsg;
  954. switch (lReturnValue)
  955. {
  956. case (-2): // error
  957. qstrErrorMsg = tr("Error checking usage credits. Perhaps the server is down or inaccessible?");
  958. break;
  959. // --------------------------------
  960. case (-1): // unlimited, or server isn't enforcing
  961. qstrErrorMsg = tr("Nym has unlimited usage credits (or the server isn't enforcing credits.')");
  962. break;
  963. // --------------------------------
  964. case (0): // Exhausted
  965. qstrErrorMsg = tr("Sorry, but the Nym attempting this action is all out of usage credits on the server. "
  966. "(You should contact the server operator and purchase more usage credits.)");
  967. break;
  968. // --------------------------------
  969. default: // Nym has X usage credits remaining.
  970. qstrErrorMsg = tr("The Nym still has usage credits remaining. Should be fine.");
  971. break;
  972. }
  973. // --------------------------------
  974. switch (lReturnValue)
  975. {
  976. case (-2): // Error
  977. case (0): // Exhausted
  978. emit appendToLog(qstrErrorMsg);
  979. // --------------------------------
  980. default: // Nym has X usage credits remaining, or server isn't enforcing credits.
  981. break;
  982. }
  983. // --------------------------------
  984. return lReturnValue;
  985. }
  986. int64_t Moneychanger::HasUsageCredits(QString notary_id,
  987. QString NYM_ID)
  988. {
  989. const std::string str_server(notary_id.toStdString());
  990. const std::string str_nym (NYM_ID .toStdString());
  991. return HasUsageCredits(str_server, str_nym);
  992. }
  993. // ---------------------------------------------------------------
  994. /**
  995. * Systray
  996. **/
  997. // Startup
  998. void Moneychanger::bootTray()
  999. {
  1000. connect(this, SIGNAL(appendT

Large files files are truncated, but you can click here to view the full file