From 551ea921cda0fdfaaeb2829693a8101ab5465d5f Mon Sep 17 00:00:00 2001 From: howard-stearns Date: Fri, 11 Aug 2017 11:45:40 -0700 Subject: [PATCH] Make API asynchronous, for communicating with server --- .../resources/qml/hifi/commerce/Checkout.qml | 23 ++++++++++----- interface/src/commerce/Ledger.cpp | 29 ++++++++++++------- interface/src/commerce/Ledger.h | 12 ++++++-- interface/src/commerce/QmlCommerce.cpp | 22 +++++++++----- interface/src/commerce/QmlCommerce.h | 13 +++++++-- interface/src/commerce/Wallet.cpp | 7 ++++- interface/src/commerce/Wallet.h | 1 + 7 files changed, 75 insertions(+), 32 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/Checkout.qml b/interface/resources/qml/hifi/commerce/Checkout.qml index 48d5f5b1d5..7e1bc2ecb9 100644 --- a/interface/resources/qml/hifi/commerce/Checkout.qml +++ b/interface/resources/qml/hifi/commerce/Checkout.qml @@ -30,6 +30,18 @@ Rectangle { color: hifi.colors.baseGray; Hifi.QmlCommerce { id: commerce; + onBuyResult: { + if (failureMessage.length) { + console.log('buy failed', failureMessage); + //fixme sendToScript({method: 'checkout_cancelClicked', params: itemId}); + } else { + console.log('buy ok'); + //fixme sendToScript({method: 'checkout_buyClicked', success: , itemId: itemId, itemHref: itemHref}); + } + } + // FIXME: remove these two after testing + onBalanceResult: console.log('balance', balance, failureMessage); + onInventoryResult: console.log('inventory', inventory, failureMessage); } // @@ -247,13 +259,13 @@ Rectangle { width: parent.width/2 - anchors.leftMargin*2; text: "Cancel" onClicked: { - sendToScript({method: 'checkout_cancelClicked', params: itemId}); + sendToScript({method: 'checkout_cancelClicked', params: itemId}); //fixme } } // "Buy" button HifiControlsUit.Button { - property bool buyFailed: false; + property bool buyFailed: false; // fixme id: buyButton; color: hifi.buttons.black; colorScheme: hifi.colorSchemes.dark; @@ -266,11 +278,8 @@ Rectangle { width: parent.width/2 - anchors.rightMargin*2; text: "Buy" onClicked: { - if (buyFailed) { - sendToScript({method: 'checkout_cancelClicked', params: itemId}); - } else { - sendToScript({method: 'checkout_buyClicked', success: commerce.buy(itemId, parseInt(itemPriceText.text)), itemId: itemId, itemHref: itemHref}); - } + // fixme do spinner thing + commerce.buy(itemId, parseInt(itemPriceText.text)); } } } diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 41dd42ea22..b8f9277a2a 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -16,7 +16,7 @@ #include "Ledger.h" #include "CommerceLogging.h" -bool Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername) { +void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername) { QJsonObject transaction; transaction["hfc_key"] = hfc_key; transaction["hfc"] = cost; @@ -34,32 +34,41 @@ bool Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact); // FIXME: talk to server instead - QStringList keySet{ hfc_key }; - if (initializedBalance() < cost) return false; + if (_inventory.contains(asset_id)) { + // This is here more for testing than as a definition of semantics. + // When we have popcerts, you will certainly be able to buy a new instance of an item that you already own a different instance of. + // I'm not sure what the server should do for now in this project's MVP. + return emit buyResult("Already owned."); + } + if (initializedBalance() < cost) { + return emit buyResult("Insufficient funds."); + } _balance -= cost; _inventory.push_back(asset_id); - return true; // FIXME send to server. + emit buyResult(""); } bool Ledger::receiveAt(const QString& hfc_key) { auto accountManager = DependencyManager::get(); if (!accountManager->isLoggedIn()) { qCWarning(commerce) << "Cannot set receiveAt when not logged in."; - return false; + emit receiveAtResult("Not logged in"); + return false; // We know right away that we will fail, so tell the caller. } auto username = accountManager->getAccountInfo().getUsername(); qCInfo(commerce) << "Setting default receiving key for" << username; - return true; // FIXME send to server. + emit receiveAtResult(""); // FIXME: talk to server instead. + return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } -int Ledger::balance(const QStringList& keys) { +void Ledger::balance(const QStringList& keys) { // FIXME: talk to server instead qCInfo(commerce) << "Balance:" << initializedBalance(); - return _balance; + emit balanceResult(_balance, ""); } -QStringList Ledger::inventory(const QStringList& keys) { +void Ledger::inventory(const QStringList& keys) { // FIXME: talk to server instead qCInfo(commerce) << "Inventory:" << _inventory; - return _inventory; + emit inventoryResult(_inventory, ""); } \ No newline at end of file diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 092af13d75..4cf6aec4ff 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -21,10 +21,16 @@ class Ledger : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - bool buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername = ""); + void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const QString& buyerUsername = ""); bool receiveAt(const QString& hfc_key); - int balance(const QStringList& keys); - QStringList inventory(const QStringList& keys); + void balance(const QStringList& keys); + void inventory(const QStringList& keys); + +signals: + void buyResult(const QString& failureReason); + void receiveAtResult(const QString& failureReason); + void balanceResult(int balance, const QString& failureReason); + void inventoryResult(QStringList inventory, const QString& failureReason); private: // These in-memory caches is temporary, until we start sending things to the server. diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 2f7d1f0b34..63bfca4f31 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -17,29 +17,35 @@ HIFI_QML_DEF(QmlCommerce) -bool QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) { +QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { + auto ledger = DependencyManager::get(); + connect(ledger.data(), &Ledger::buyResult, this, &QmlCommerce::buyResult); + connect(ledger.data(), &Ledger::balanceResult, this, &QmlCommerce::balanceResult); + connect(ledger.data(), &Ledger::inventoryResult, this, &QmlCommerce::inventoryResult); +} + +void QmlCommerce::buy(const QString& assetId, int cost, const QString& buyerUsername) { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); QStringList keys = wallet->listPublicKeys(); if (keys.count() == 0) { - return false; + return emit buyResult("Uninitialized Wallet."); } QString key = keys[0]; // For now, we receive at the same key that pays for it. - bool success = ledger->buy(key, cost, assetId, key, buyerUsername); + ledger->buy(key, cost, assetId, key, buyerUsername); // FIXME: until we start talking to server, report post-transaction balance and inventory so we can see log for testing. balance(); inventory(); - return success; } -int QmlCommerce::balance() { +void QmlCommerce::balance() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - return ledger->balance(wallet->listPublicKeys()); + ledger->balance(wallet->listPublicKeys()); } -QStringList QmlCommerce::inventory() { +void QmlCommerce::inventory() { auto ledger = DependencyManager::get(); auto wallet = DependencyManager::get(); - return ledger->inventory(wallet->listPublicKeys()); + ledger->inventory(wallet->listPublicKeys()); } \ No newline at end of file diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index fd695b2d1c..050371a801 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -21,13 +21,20 @@ class QmlCommerce : public OffscreenQmlDialog { Q_OBJECT HIFI_QML_DECL +public: + QmlCommerce(QQuickItem* parent = nullptr); + signals: void buyResult(const QString& failureMessage); + // Balance and Inventory are NOT properties, because QML can't change them (without risk of failure), and + // because we can't scalably know of out-of-band changes (e.g., another machine interacting with the block chain). + void balanceResult(int balance, const QString& failureMessage); + void inventoryResult(QStringList inventory, const QString& failureMessage); protected: - Q_INVOKABLE bool buy(const QString& assetId, int cost, const QString& buyerUsername = ""); - Q_INVOKABLE int balance(); - Q_INVOKABLE QStringList inventory(); + Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = ""); + Q_INVOKABLE void balance(); + Q_INVOKABLE void inventory(); }; #endif // hifi_QmlCommerce_h diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index d2a6ae4809..34d89b54b0 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -25,7 +25,12 @@ bool Wallet::generateKeyPair() { // FIXME: need private key, too, and persist in file. qCInfo(commerce) << "Generating keypair."; QString key = QUuid::createUuid().toString(); - _publicKeys.push_back(key); + + _publicKeys.push_back(key); // Keep in memory for synchronous speed. + // It's arguable whether we want to change the receiveAt every time, but: + // 1. It's certainly needed the first time, when createIfNeeded answers true. + // 2. It is maximally private, and we can step back from that later if desired. + // 3. It maximally exercises all the machinery, so we are most likely to surface issues now. auto ledger = DependencyManager::get(); return ledger->receiveAt(key); } diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index 7cfb14c30d..79de5e81da 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -21,6 +21,7 @@ class Wallet : public QObject, public Dependency { SINGLETON_DEPENDENCY public: + // These are currently blocking calls, although they might take a moment. bool createIfNeeded(); bool generateKeyPair(); QStringList listPublicKeys();