Make API asynchronous, for communicating with server

This commit is contained in:
howard-stearns 2017-08-11 11:45:40 -07:00
parent 8f0fd10e2b
commit 551ea921cd
7 changed files with 75 additions and 32 deletions

View file

@ -30,6 +30,18 @@ Rectangle {
color: hifi.colors.baseGray; color: hifi.colors.baseGray;
Hifi.QmlCommerce { Hifi.QmlCommerce {
id: commerce; 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; width: parent.width/2 - anchors.leftMargin*2;
text: "Cancel" text: "Cancel"
onClicked: { onClicked: {
sendToScript({method: 'checkout_cancelClicked', params: itemId}); sendToScript({method: 'checkout_cancelClicked', params: itemId}); //fixme
} }
} }
// "Buy" button // "Buy" button
HifiControlsUit.Button { HifiControlsUit.Button {
property bool buyFailed: false; property bool buyFailed: false; // fixme
id: buyButton; id: buyButton;
color: hifi.buttons.black; color: hifi.buttons.black;
colorScheme: hifi.colorSchemes.dark; colorScheme: hifi.colorSchemes.dark;
@ -266,11 +278,8 @@ Rectangle {
width: parent.width/2 - anchors.rightMargin*2; width: parent.width/2 - anchors.rightMargin*2;
text: "Buy" text: "Buy"
onClicked: { onClicked: {
if (buyFailed) { // fixme do spinner thing
sendToScript({method: 'checkout_cancelClicked', params: itemId}); commerce.buy(itemId, parseInt(itemPriceText.text));
} else {
sendToScript({method: 'checkout_buyClicked', success: commerce.buy(itemId, parseInt(itemPriceText.text)), itemId: itemId, itemHref: itemHref});
}
} }
} }
} }

View file

@ -16,7 +16,7 @@
#include "Ledger.h" #include "Ledger.h"
#include "CommerceLogging.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; QJsonObject transaction;
transaction["hfc_key"] = hfc_key; transaction["hfc_key"] = hfc_key;
transaction["hfc"] = cost; 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); qCInfo(commerce) << "Transaction:" << QJsonDocument(request).toJson(QJsonDocument::Compact);
// FIXME: talk to server instead // FIXME: talk to server instead
QStringList keySet{ hfc_key }; if (_inventory.contains(asset_id)) {
if (initializedBalance() < cost) return false; // 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; _balance -= cost;
_inventory.push_back(asset_id); _inventory.push_back(asset_id);
return true; // FIXME send to server. emit buyResult("");
} }
bool Ledger::receiveAt(const QString& hfc_key) { bool Ledger::receiveAt(const QString& hfc_key) {
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
if (!accountManager->isLoggedIn()) { if (!accountManager->isLoggedIn()) {
qCWarning(commerce) << "Cannot set receiveAt when not logged in."; 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(); auto username = accountManager->getAccountInfo().getUsername();
qCInfo(commerce) << "Setting default receiving key for" << username; 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 // FIXME: talk to server instead
qCInfo(commerce) << "Balance:" << initializedBalance(); 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 // FIXME: talk to server instead
qCInfo(commerce) << "Inventory:" << _inventory; qCInfo(commerce) << "Inventory:" << _inventory;
return _inventory; emit inventoryResult(_inventory, "");
} }

View file

@ -21,10 +21,16 @@ class Ledger : public QObject, public Dependency {
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public: 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); bool receiveAt(const QString& hfc_key);
int balance(const QStringList& keys); void balance(const QStringList& keys);
QStringList inventory(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: private:
// These in-memory caches is temporary, until we start sending things to the server. // These in-memory caches is temporary, until we start sending things to the server.

View file

@ -17,29 +17,35 @@
HIFI_QML_DEF(QmlCommerce) 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<Ledger>();
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<Ledger>(); auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>(); auto wallet = DependencyManager::get<Wallet>();
QStringList keys = wallet->listPublicKeys(); QStringList keys = wallet->listPublicKeys();
if (keys.count() == 0) { if (keys.count() == 0) {
return false; return emit buyResult("Uninitialized Wallet.");
} }
QString key = keys[0]; QString key = keys[0];
// For now, we receive at the same key that pays for it. // 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. // FIXME: until we start talking to server, report post-transaction balance and inventory so we can see log for testing.
balance(); balance();
inventory(); inventory();
return success;
} }
int QmlCommerce::balance() { void QmlCommerce::balance() {
auto ledger = DependencyManager::get<Ledger>(); auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>(); auto wallet = DependencyManager::get<Wallet>();
return ledger->balance(wallet->listPublicKeys()); ledger->balance(wallet->listPublicKeys());
} }
QStringList QmlCommerce::inventory() { void QmlCommerce::inventory() {
auto ledger = DependencyManager::get<Ledger>(); auto ledger = DependencyManager::get<Ledger>();
auto wallet = DependencyManager::get<Wallet>(); auto wallet = DependencyManager::get<Wallet>();
return ledger->inventory(wallet->listPublicKeys()); ledger->inventory(wallet->listPublicKeys());
} }

View file

@ -21,13 +21,20 @@ class QmlCommerce : public OffscreenQmlDialog {
Q_OBJECT Q_OBJECT
HIFI_QML_DECL HIFI_QML_DECL
public:
QmlCommerce(QQuickItem* parent = nullptr);
signals: signals:
void buyResult(const QString& failureMessage); 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: protected:
Q_INVOKABLE bool buy(const QString& assetId, int cost, const QString& buyerUsername = ""); Q_INVOKABLE void buy(const QString& assetId, int cost, const QString& buyerUsername = "");
Q_INVOKABLE int balance(); Q_INVOKABLE void balance();
Q_INVOKABLE QStringList inventory(); Q_INVOKABLE void inventory();
}; };
#endif // hifi_QmlCommerce_h #endif // hifi_QmlCommerce_h

View file

@ -25,7 +25,12 @@ bool Wallet::generateKeyPair() {
// FIXME: need private key, too, and persist in file. // FIXME: need private key, too, and persist in file.
qCInfo(commerce) << "Generating keypair."; qCInfo(commerce) << "Generating keypair.";
QString key = QUuid::createUuid().toString(); 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<Ledger>(); auto ledger = DependencyManager::get<Ledger>();
return ledger->receiveAt(key); return ledger->receiveAt(key);
} }

View file

@ -21,6 +21,7 @@ class Wallet : public QObject, public Dependency {
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public: public:
// These are currently blocking calls, although they might take a moment.
bool createIfNeeded(); bool createIfNeeded();
bool generateKeyPair(); bool generateKeyPair();
QStringList listPublicKeys(); QStringList listPublicKeys();