diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp
index b3a92f2f54..009bd42e88 100644
--- a/assignment-client/src/AssignmentClient.cpp
+++ b/assignment-client/src/AssignmentClient.cpp
@@ -67,9 +67,20 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
if (argumentIndex != -1) {
assignmentPool = argumentList[argumentIndex + 1];
}
+
// setup our _requestAssignment member variable from the passed arguments
_requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool);
+ // check if we were passed a wallet UUID on the command line
+ // this would represent where the user running AC wants funds sent to
+
+ const QString ASSIGNMENT_WALLET_DESTINATION_ID_OPTION = "--wallet";
+ if ((argumentIndex = argumentList.indexOf(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) != -1) {
+ QUuid walletUUID = QString(argumentList[argumentIndex + 1]);
+ qDebug() << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
+ _requestAssignment.setWalletUUID(walletUUID);
+ }
+
// create a NodeList as an unassigned client
NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned);
diff --git a/domain-server/resources/web/index.shtml b/domain-server/resources/web/index.shtml
index afd0af1679..b6ba8f67db 100644
--- a/domain-server/resources/web/index.shtml
+++ b/domain-server/resources/web/index.shtml
@@ -13,6 +13,7 @@
Public |
Local |
Uptime (s) |
+ Pending Credits |
Kill? |
diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js
index a4884486c3..b564d9392f 100644
--- a/domain-server/resources/web/js/tables.js
+++ b/domain-server/resources/web/js/tables.js
@@ -42,6 +42,8 @@ $(document).ready(function(){
var uptimeSeconds = (Date.now() - data.wake_timestamp) / 1000;
nodesTableBody += "" + uptimeSeconds.toLocaleString() + " | ";
+ nodesTableBody += "" + (typeof data.pending_credits == 'number' ? data.pending_credits.toLocaleString() : 'N/A') + " | ";
+
nodesTableBody += " | ";
nodesTableBody += "";
});
diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp
index f6af63fd7a..9ad36e6956 100644
--- a/domain-server/src/DomainServer.cpp
+++ b/domain-server/src/DomainServer.cpp
@@ -35,6 +35,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_httpsManager(NULL),
_allAssignments(),
_unfulfilledAssignments(),
+ _pendingAssignedNodes(),
_isUsingDTLS(false),
_oauthProviderURL(),
_oauthClientID(),
@@ -49,13 +50,14 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
- if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
- // we either read a certificate and private key or were not passed one, good to load assignments
- // and set up the node list
+ _networkAccessManager = new QNetworkAccessManager(this);
+
+ if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
+ // we either read a certificate and private key or were not passed one
+ // and completed login or did not need to
+
qDebug() << "Setting up LimitedNodeList and assignments.";
setupNodeListAndAssignments();
-
- _networkAccessManager = new QNetworkAccessManager(this);
}
}
@@ -129,7 +131,7 @@ bool DomainServer::optionallySetupOAuth() {
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
_hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
- if (!_oauthProviderURL.isEmpty() || !_hostname.isEmpty() || !_oauthClientID.isEmpty()) {
+ if (!_oauthClientID.isEmpty()) {
if (_oauthProviderURL.isEmpty()
|| _hostname.isEmpty()
|| _oauthClientID.isEmpty()
@@ -187,6 +189,65 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
addStaticAssignmentsToQueue();
}
+bool DomainServer::optionallySetupAssignmentPayment() {
+ // check if we have a username and password set via env
+ const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
+ const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
+ const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
+
+ if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION)) {
+ if (!_oauthProviderURL.isEmpty()) {
+
+ AccountManager& accountManager = AccountManager::getInstance();
+ accountManager.setAuthURL(_oauthProviderURL);
+
+ if (!accountManager.hasValidAccessToken()) {
+ // we don't have a valid access token so we need to get one
+ QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
+ QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
+
+ if (!username.isEmpty() && !password.isEmpty()) {
+ accountManager.requestAccessToken(username, password);
+
+ // connect to loginFailed signal from AccountManager so we can quit if that is the case
+ connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
+ } else {
+ qDebug() << "Missing access-token or username and password combination. domain-server will now quit.";
+ QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
+ return false;
+ }
+ }
+
+ // assume that the fact we are authing against HF data server means we will pay for assignments
+ // setup a timer to send transactions to pay assigned nodes every 30 seconds
+ QTimer* creditSetupTimer = new QTimer(this);
+ connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
+
+ const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
+ creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
+
+ QTimer* nodePaymentTimer = new QTimer(this);
+ connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
+
+ const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
+ nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
+
+ } else {
+ qDebug() << "Missing OAuth provider URL, but assigned node payment was enabled. domain-server will now quit.";
+ QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
+
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void DomainServer::loginFailed() {
+ qDebug() << "Login to data server has failed. domain-server will now quit";
+ QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
+}
+
void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) {
// check for configs from the command line, these take precedence
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
@@ -339,10 +400,23 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
QUuid packetUUID = uuidFromPacketHeader(packet);
// check if this connect request matches an assignment in the queue
- bool isFulfilledOrUnfulfilledAssignment = _allAssignments.contains(packetUUID);
+ bool isAssignment = _pendingAssignedNodes.contains(packetUUID);
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
- if (isFulfilledOrUnfulfilledAssignment) {
- matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType);
+ PendingAssignedNodeData* pendingAssigneeData = NULL;
+
+ if (isAssignment) {
+ pendingAssigneeData = _pendingAssignedNodes.take(packetUUID);
+
+ if (pendingAssigneeData) {
+ matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(pendingAssigneeData->getAssignmentUUID(), nodeType);
+
+ if (matchingQueuedAssignment) {
+ qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID)
+ << "matches unfulfilled assignment"
+ << uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID());
+ }
+ }
+
}
if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
@@ -371,8 +445,8 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
}
}
- if ((!isFulfilledOrUnfulfilledAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
- || (isFulfilledOrUnfulfilledAssignment && matchingQueuedAssignment)) {
+ if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
+ || (isAssignment && matchingQueuedAssignment)) {
// this was either not a static assignment or it was and we had a matching one in the queue
// create a new session UUID for this node
@@ -384,8 +458,12 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
// if this was a static assignment set the UUID, set the sendingSockAddr
DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData());
- if (isFulfilledOrUnfulfilledAssignment) {
- nodeData->setAssignmentUUID(packetUUID);
+ if (isAssignment) {
+ nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
+ nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID());
+
+ // now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data
+ delete pendingAssigneeData;
}
nodeData->setSendingSockAddr(senderSockAddr);
@@ -564,14 +642,21 @@ void DomainServer::readAvailableDatagrams() {
Assignment requestAssignment(receivedPacket);
// Suppress these for Assignment::AgentType to once per 5 seconds
- static quint64 lastNoisyMessage = usecTimestampNow();
- quint64 timeNow = usecTimestampNow();
- const quint64 NOISY_TIME_ELAPSED = 5 * USECS_PER_SECOND;
- bool noisyMessage = false;
- if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) {
+ static QElapsedTimer noisyMessageTimer;
+ static bool wasNoisyTimerStarted = false;
+
+ if (!wasNoisyTimerStarted) {
+ noisyMessageTimer.start();
+ wasNoisyTimerStarted = true;
+ }
+
+ const quint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000;
+
+ if (requestAssignment.getType() != Assignment::AgentType
+ || noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
qDebug() << "Received a request for assignment type" << requestAssignment.getType()
- << "from" << senderSockAddr;
- noisyMessage = true;
+ << "from" << senderSockAddr;
+ noisyMessageTimer.restart();
}
SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
@@ -582,23 +667,29 @@ void DomainServer::readAvailableDatagrams() {
// give this assignment out, either the type matches or the requestor said they will take any
assignmentPacket.resize(numAssignmentPacketHeaderBytes);
+ // setup a copy of this assignment that will have a unique UUID, for packaging purposes
+ Assignment uniqueAssignment(*assignmentToDeploy.data());
+ uniqueAssignment.setUUID(QUuid::createUuid());
+
QDataStream assignmentStream(&assignmentPacket, QIODevice::Append);
- assignmentStream << *assignmentToDeploy.data();
+ assignmentStream << uniqueAssignment;
nodeList->getNodeSocket().writeDatagram(assignmentPacket,
senderSockAddr.getAddress(), senderSockAddr.getPort());
+
+ // add the information for that deployed assignment to the hash of pending assigned nodes
+ PendingAssignedNodeData* pendingNodeData = new PendingAssignedNodeData(assignmentToDeploy->getUUID(),
+ requestAssignment.getWalletUUID());
+ _pendingAssignedNodes.insert(uniqueAssignment.getUUID(), pendingNodeData);
} else {
- if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) {
+ if (requestAssignment.getType() != Assignment::AgentType
+ || noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
- << "from" << senderSockAddr;
- noisyMessage = true;
+ << "from" << senderSockAddr;
+ noisyMessageTimer.restart();
}
}
-
- if (noisyMessage) {
- lastNoisyMessage = timeNow;
- }
} else if (!_isUsingDTLS) {
// not using DTLS, process datagram normally
processDatagram(receivedPacket, senderSockAddr);
@@ -618,6 +709,97 @@ void DomainServer::readAvailableDatagrams() {
}
}
+void DomainServer::setupPendingAssignmentCredits() {
+ // enumerate the NodeList to find the assigned nodes
+ foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
+ DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData());
+
+ if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) {
+ // check if we have a non-finalized transaction for this node to add this amount to
+ TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID());
+ WalletTransaction* existingTransaction = NULL;
+
+ while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) {
+ if (!i.value()->isFinalized()) {
+ existingTransaction = i.value();
+ break;
+ } else {
+ ++i;
+ }
+ }
+
+ qint64 elapsedMsecsSinceLastPayment = nodeData->getPaymentIntervalTimer().elapsed();
+ nodeData->getPaymentIntervalTimer().restart();
+
+ const float CREDITS_PER_HOUR = 3;
+ const float CREDITS_PER_MSEC = CREDITS_PER_HOUR / (60 * 60 * 1000);
+
+ float pendingCredits = elapsedMsecsSinceLastPayment * CREDITS_PER_MSEC;
+
+ if (existingTransaction) {
+ existingTransaction->incrementAmount(pendingCredits);
+ } else {
+ // create a fresh transaction to pay this node, there is no transaction to append to
+ WalletTransaction* freshTransaction = new WalletTransaction(nodeData->getWalletUUID(), pendingCredits);
+ _pendingAssignmentCredits.insert(nodeData->getWalletUUID(), freshTransaction);
+ }
+ }
+ }
+}
+
+void DomainServer::sendPendingTransactionsToServer() {
+
+ AccountManager& accountManager = AccountManager::getInstance();
+
+ if (accountManager.hasValidAccessToken()) {
+
+ // enumerate the pending transactions and send them to the server to complete payment
+ TransactionHash::iterator i = _pendingAssignmentCredits.begin();
+
+ JSONCallbackParameters transactionCallbackParams;
+
+ transactionCallbackParams.jsonCallbackReceiver = this;
+ transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback";
+
+ while (i != _pendingAssignmentCredits.end()) {
+ accountManager.authenticatedRequest("api/v1/transactions", QNetworkAccessManager::PostOperation,
+ transactionCallbackParams, i.value()->postJson().toJson());
+
+ // set this transaction to finalized so we don't add additional credits to it
+ i.value()->setIsFinalized(true);
+
+ ++i;
+ }
+ }
+
+}
+
+void DomainServer::transactionJSONCallback(const QJsonObject& data) {
+ // check if this was successful - if so we can remove it from our list of pending
+ if (data.value("status").toString() == "success") {
+ // create a dummy wallet transaction to unpack the JSON to
+ WalletTransaction dummyTransaction;
+ dummyTransaction.loadFromJson(data);
+
+ TransactionHash::iterator i = _pendingAssignmentCredits.find(dummyTransaction.getDestinationUUID());
+
+ while (i != _pendingAssignmentCredits.end() && i.key() == dummyTransaction.getDestinationUUID()) {
+ if (i.value()->getUUID() == dummyTransaction.getUUID()) {
+ // we have a match - we can remove this from the hash of pending credits
+ // and delete it for clean up
+
+ WalletTransaction* matchingTransaction = i.value();
+ _pendingAssignmentCredits.erase(i);
+ delete matchingTransaction;
+
+ break;
+ } else {
+ ++i;
+ }
+ }
+ }
+}
+
void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) {
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
@@ -667,6 +849,7 @@ const char JSON_KEY_TYPE[] = "type";
const char JSON_KEY_PUBLIC_SOCKET[] = "public";
const char JSON_KEY_LOCAL_SOCKET[] = "local";
const char JSON_KEY_POOL[] = "pool";
+const char JSON_KEY_PENDING_CREDITS[] = "pending_credits";
const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp";
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
@@ -695,6 +878,18 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
if (matchingAssignment) {
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
+
+ if (!nodeData->getWalletUUID().isNull()) {
+ TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID());
+ double pendingCreditAmount = 0;
+
+ while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) {
+ pendingCreditAmount += i.value()->getAmount();
+ ++i;
+ }
+
+ nodeJson[JSON_KEY_PENDING_CREDITS] = pendingCreditAmount;
+ }
}
return nodeJson;
@@ -764,6 +959,24 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
connection->respond(HTTPConnection::StatusCode200, assignmentDocument.toJson(), qPrintable(JSON_MIME_TYPE));
// we've processed this request
+ return true;
+ } else if (url.path() == "/transactions.json") {
+ // enumerate our pending transactions and display them in an array
+ QJsonObject rootObject;
+ QJsonArray transactionArray;
+
+ TransactionHash::iterator i = _pendingAssignmentCredits.begin();
+ while (i != _pendingAssignmentCredits.end()) {
+ transactionArray.push_back(i.value()->toJson());
+ ++i;
+ }
+
+ rootObject["pending_transactions"] = transactionArray;
+
+ // print out the created JSON
+ QJsonDocument transactionsDocument(rootObject);
+ connection->respond(HTTPConnection::StatusCode200, transactionsDocument.toJson(), qPrintable(JSON_MIME_TYPE));
+
return true;
} else if (url.path() == QString("%1.json").arg(URI_NODES)) {
// setup the JSON
@@ -1062,11 +1275,15 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
}
}
-SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
+SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& assignmentUUID, NodeType_t nodeType) {
QQueue::iterator i = _unfulfilledAssignments.begin();
while (i != _unfulfilledAssignments.end()) {
- if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
+ if (i->data()->getType() == Assignment::typeForNodeType(nodeType)
+ && i->data()->getUUID() == assignmentUUID) {
+ // we have an unfulfilled assignment to return
+
+ // return the matching assignment
return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin());
} else {
++i;
diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h
index 71809d9e16..b038850b3d 100644
--- a/domain-server/src/DomainServer.h
+++ b/domain-server/src/DomainServer.h
@@ -24,7 +24,12 @@
#include
#include
+#include "WalletTransaction.h"
+
+#include "PendingAssignedNodeData.h"
+
typedef QSharedPointer SharedAssignmentPointer;
+typedef QMultiHash TransactionHash;
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
Q_OBJECT
@@ -42,13 +47,18 @@ public slots:
/// Called by NodeList to inform us a node has been killed
void nodeKilled(SharedNodePointer node);
-private slots:
+ void transactionJSONCallback(const QJsonObject& data);
+private slots:
+ void loginFailed();
void readAvailableDatagrams();
+ void setupPendingAssignmentCredits();
+ void sendPendingTransactionsToServer();
private:
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
bool optionallySetupOAuth();
bool optionallyReadX509KeyAndCertificate();
+ bool optionallySetupAssignmentPayment();
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
@@ -85,6 +95,8 @@ private:
QHash _allAssignments;
QQueue _unfulfilledAssignments;
+ QHash _pendingAssignedNodes;
+ TransactionHash _pendingAssignmentCredits;
QVariantMap _argumentVariantMap;
diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp
index a43e17ae60..68903cc106 100644
--- a/domain-server/src/DomainServerNodeData.cpp
+++ b/domain-server/src/DomainServerNodeData.cpp
@@ -20,11 +20,13 @@
DomainServerNodeData::DomainServerNodeData() :
_sessionSecretHash(),
_assignmentUUID(),
+ _walletUUID(),
+ _paymentIntervalTimer(),
_statsJSONObject(),
_sendingSockAddr(),
_isAuthenticated(true)
{
-
+ _paymentIntervalTimer.start();
}
void DomainServerNodeData::parseJSONStatsPacket(const QByteArray& statsPacket) {
diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h
index 011bee57c1..a7d7233874 100644
--- a/domain-server/src/DomainServerNodeData.h
+++ b/domain-server/src/DomainServerNodeData.h
@@ -12,6 +12,8 @@
#ifndef hifi_DomainServerNodeData_h
#define hifi_DomainServerNodeData_h
+
+#include
#include
#include
@@ -30,6 +32,11 @@ public:
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
+ void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
+ const QUuid& getWalletUUID() const { return _walletUUID; }
+
+ QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; }
+
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
@@ -42,6 +49,8 @@ private:
QHash _sessionSecretHash;
QUuid _assignmentUUID;
+ QUuid _walletUUID;
+ QElapsedTimer _paymentIntervalTimer;
QJsonObject _statsJSONObject;
HifiSockAddr _sendingSockAddr;
bool _isAuthenticated;
diff --git a/domain-server/src/PendingAssignedNodeData.cpp b/domain-server/src/PendingAssignedNodeData.cpp
new file mode 100644
index 0000000000..21b3aa4ca4
--- /dev/null
+++ b/domain-server/src/PendingAssignedNodeData.cpp
@@ -0,0 +1,19 @@
+//
+// PendingAssignedNodeData.cpp
+// domain-server/src
+//
+// Created by Stephen Birarda on 2014-05-20.
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include "PendingAssignedNodeData.h"
+
+PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID) :
+ _assignmentUUID(assignmentUUID),
+ _walletUUID(walletUUID)
+{
+
+}
\ No newline at end of file
diff --git a/domain-server/src/PendingAssignedNodeData.h b/domain-server/src/PendingAssignedNodeData.h
new file mode 100644
index 0000000000..93d99a6f8f
--- /dev/null
+++ b/domain-server/src/PendingAssignedNodeData.h
@@ -0,0 +1,33 @@
+//
+// PendingAssignedNodeData.h
+// domain-server/src
+//
+// Created by Stephen Birarda on 2014-05-20.
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_PendingAssignedNodeData_h
+#define hifi_PendingAssignedNodeData_h
+
+#include
+#include
+
+class PendingAssignedNodeData : public QObject {
+ Q_OBJECT
+public:
+ PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID);
+
+ void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
+ const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
+
+ void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
+ const QUuid& getWalletUUID() const { return _walletUUID; }
+private:
+ QUuid _assignmentUUID;
+ QUuid _walletUUID;
+};
+
+#endif // hifi_PendingAssignedNodeData_h
\ No newline at end of file
diff --git a/domain-server/src/WalletTransaction.cpp b/domain-server/src/WalletTransaction.cpp
new file mode 100644
index 0000000000..6ff57f063c
--- /dev/null
+++ b/domain-server/src/WalletTransaction.cpp
@@ -0,0 +1,67 @@
+//
+// WalletTransaction.cpp
+// domain-server/src
+//
+// Created by Stephen Birarda on 2014-05-20.
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#include
+
+#include
+
+#include "WalletTransaction.h"
+
+WalletTransaction::WalletTransaction() :
+ _uuid(),
+ _destinationUUID(),
+ _amount(),
+ _isFinalized(false)
+{
+
+}
+
+WalletTransaction::WalletTransaction(const QUuid& destinationUUID, double amount) :
+ _uuid(QUuid::createUuid()),
+ _destinationUUID(destinationUUID),
+ _amount(amount),
+ _isFinalized(false)
+{
+
+}
+
+const QString TRANSACTION_ID_KEY = "id";
+const QString TRANSACTION_DESTINATION_WALLET_ID_KEY = "destination_wallet_id";
+const QString TRANSACTION_AMOUNT_KEY = "amount";
+
+const QString ROOT_OBJECT_TRANSACTION_KEY = "transaction";
+
+QJsonDocument WalletTransaction::postJson() {
+ QJsonObject rootObject;
+
+ rootObject.insert(ROOT_OBJECT_TRANSACTION_KEY, toJson());
+
+ return QJsonDocument(rootObject);
+}
+
+QJsonObject WalletTransaction::toJson() {
+ QJsonObject transactionObject;
+
+ transactionObject.insert(TRANSACTION_ID_KEY, uuidStringWithoutCurlyBraces(_uuid));
+ transactionObject.insert(TRANSACTION_DESTINATION_WALLET_ID_KEY, uuidStringWithoutCurlyBraces(_destinationUUID));
+ transactionObject.insert(TRANSACTION_AMOUNT_KEY, _amount);
+
+ return transactionObject;
+}
+
+void WalletTransaction::loadFromJson(const QJsonObject& jsonObject) {
+ // pull the destination wallet and ID of the transaction to match it
+ QJsonObject transactionObject = jsonObject.value("data").toObject().value(ROOT_OBJECT_TRANSACTION_KEY).toObject();
+
+ _uuid = QUuid(transactionObject.value(TRANSACTION_ID_KEY).toString());
+ _destinationUUID = QUuid(transactionObject.value(TRANSACTION_DESTINATION_WALLET_ID_KEY).toString());
+ _amount = transactionObject.value(TRANSACTION_AMOUNT_KEY).toDouble();
+}
\ No newline at end of file
diff --git a/domain-server/src/WalletTransaction.h b/domain-server/src/WalletTransaction.h
new file mode 100644
index 0000000000..8f36d10302
--- /dev/null
+++ b/domain-server/src/WalletTransaction.h
@@ -0,0 +1,46 @@
+//
+// WalletTransaction.h
+// domain-server/src
+//
+// Created by Stephen Birarda on 2014-05-20.
+// Copyright 2014 High Fidelity, Inc.
+//
+// Distributed under the Apache License, Version 2.0.
+// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
+//
+
+#ifndef hifi_WalletTransaction_h
+#define hifi_WalletTransaction_h
+
+#include
+#include
+#include
+
+class WalletTransaction : public QObject {
+public:
+ WalletTransaction();
+ WalletTransaction(const QUuid& destinationUUID, double amount);
+
+ const QUuid& getUUID() const { return _uuid; }
+
+ void setDestinationUUID(const QUuid& destinationUUID) { _destinationUUID = destinationUUID; }
+ const QUuid& getDestinationUUID() const { return _destinationUUID; }
+
+ double getAmount() const { return _amount; }
+ void setAmount(double amount) { _amount = amount; }
+ void incrementAmount(double increment) { _amount += increment; }
+
+ bool isFinalized() const { return _isFinalized; }
+ void setIsFinalized(bool isFinalized) { _isFinalized = isFinalized; }
+
+ QJsonDocument postJson();
+ QJsonObject toJson();
+ void loadFromJson(const QJsonObject& jsonObject);
+private:
+ QUuid _uuid;
+ QUuid _destinationUUID;
+ double _amount;
+ bool _isFinalized;
+};
+
+#endif // hifi_WalletTransaction_h
\ No newline at end of file
diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp
index 547768ec48..aad2cfb386 100644
--- a/libraries/networking/src/AccountManager.cpp
+++ b/libraries/networking/src/AccountManager.cpp
@@ -136,7 +136,13 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
QNetworkRequest authenticatedRequest;
QUrl requestURL = _authURL;
- requestURL.setPath(path);
+
+ if (path.startsWith("/")) {
+ requestURL.setPath(path);
+ } else {
+ requestURL.setPath("/" + path);
+ }
+
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
authenticatedRequest.setUrl(requestURL);
diff --git a/libraries/networking/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp
index 925ed2930f..fd6ecd4602 100644
--- a/libraries/networking/src/Assignment.cpp
+++ b/libraries/networking/src/Assignment.cpp
@@ -63,7 +63,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
_pool(pool),
_location(location),
_payload(),
- _isStatic(false)
+ _isStatic(false),
+ _walletUUID()
{
if (_command == Assignment::CreateCommand) {
// this is a newly created assignment, generate a random UUID
@@ -74,7 +75,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
Assignment::Assignment(const QByteArray& packet) :
_pool(),
_location(GlobalLocation),
- _payload()
+ _payload(),
+ _walletUUID()
{
PacketType packetType = packetTypeForPacket(packet);
@@ -104,6 +106,7 @@ Assignment::Assignment(const Assignment& otherAssignment) {
_location = otherAssignment._location;
_pool = otherAssignment._pool;
_payload = otherAssignment._payload;
+ _walletUUID = otherAssignment._walletUUID;
}
Assignment& Assignment::operator=(const Assignment& rhsAssignment) {
@@ -121,6 +124,7 @@ void Assignment::swap(Assignment& otherAssignment) {
swap(_location, otherAssignment._location);
swap(_pool, otherAssignment._pool);
swap(_payload, otherAssignment._payload);
+ swap(_walletUUID, otherAssignment._walletUUID);
}
const char* Assignment::getTypeName() const {
@@ -156,6 +160,10 @@ QDebug operator<<(QDebug debug, const Assignment &assignment) {
QDataStream& operator<<(QDataStream &out, const Assignment& assignment) {
out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload;
+ if (assignment._command == Assignment::RequestCommand) {
+ out << assignment._walletUUID;
+ }
+
return out;
}
@@ -166,5 +174,9 @@ QDataStream& operator>>(QDataStream &in, Assignment& assignment) {
in >> assignment._uuid >> assignment._pool >> assignment._payload;
+ if (assignment._command == Assignment::RequestCommand) {
+ in >> assignment._walletUUID;
+ }
+
return in;
}
diff --git a/libraries/networking/src/Assignment.h b/libraries/networking/src/Assignment.h
index 1d97b08bb8..3898b84787 100644
--- a/libraries/networking/src/Assignment.h
+++ b/libraries/networking/src/Assignment.h
@@ -81,6 +81,9 @@ public:
void setIsStatic(bool isStatic) { _isStatic = isStatic; }
bool isStatic() const { return _isStatic; }
+ void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
+ const QUuid& getWalletUUID() const { return _walletUUID; }
+
const char* getTypeName() const;
// implement parseData to return 0 so we can be a subclass of NodeData
@@ -98,6 +101,7 @@ protected:
Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs
QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed
bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled
+ QUuid _walletUUID; /// the UUID for the wallet that should be paid for this assignment
};
#endif // hifi_Assignment_h
diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp
index 5692023ab1..b5a23f6b99 100644
--- a/libraries/networking/src/LimitedNodeList.cpp
+++ b/libraries/networking/src/LimitedNodeList.cpp
@@ -152,10 +152,11 @@ void LimitedNodeList::changeSendSocketBufferSize(int numSendBytes) {
bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) {
PacketType checkType = packetTypeForPacket(packet);
- if (packet[1] != versionForPacketType(checkType)
+ int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
+
+ if (packet[numPacketTypeBytes] != versionForPacketType(checkType)
&& checkType != PacketTypeStunResponse) {
PacketType mismatchType = packetTypeForPacket(packet);
- int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data());
static QMultiMap versionDebugSuppressMap;
diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp
index d20f4276f1..e8ab59ce2d 100644
--- a/libraries/shared/src/HifiConfigVariantMap.cpp
+++ b/libraries/shared/src/HifiConfigVariantMap.cpp
@@ -41,19 +41,19 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL
nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1);
- if (nextKeyIndex == keyIndex + 1) {
+ if (nextKeyIndex == keyIndex + 1 || keyIndex == argumentList.size() - 1) {
// there's no value associated with this option, it's a boolean
// so add it to the variant map with NULL as value
mergedMap.insertMulti(key, QVariant());
} else {
- int maxIndex = (nextKeyIndex == -1) ? argumentList.size() : nextKeyIndex;
+ int maxIndex = (nextKeyIndex == -1) ? argumentList.size() - 1: nextKeyIndex;
// there's at least one value associated with the option
// pull the first value to start
QString value = argumentList[keyIndex + 1];
// for any extra values, append them, with a space, to the value string
- for (int i = keyIndex + 2; i < maxIndex; i++) {
+ for (int i = keyIndex + 2; i <= maxIndex; i++) {
value += " " + argumentList[i];
}