From 74ea1548a929f4936684cc1d9ab59adfc553a85a Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 13 Sep 2018 14:24:49 -0700 Subject: [PATCH 01/63] Use chrono::system_clock for usecTimestampNow() on all platforms --- libraries/shared/src/SharedUtil.cpp | 81 ++--------------------------- 1 file changed, 5 insertions(+), 76 deletions(-) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index bb22a1e753..886445824b 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -19,8 +19,8 @@ #include #include #include -#include #include +#include #include @@ -122,87 +122,16 @@ void setGlobalInstance(const char* propertyName, const QVariant& variant) { } } +// Do we still need this? static qint64 usecTimestampNowAdjust = 0; // in usec void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; } -static std::atomic TIME_REFERENCE { 0 }; // in usec -static std::once_flag usecTimestampNowIsInitialized; -static QElapsedTimer timestampTimer; - quint64 usecTimestampNow(bool wantDebug) { - std::call_once(usecTimestampNowIsInitialized, [&] { - TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec - timestampTimer.start(); - }); - - quint64 now; - quint64 nsecsElapsed = timestampTimer.nsecsElapsed(); - quint64 usecsElapsed = nsecsElapsed / NSECS_PER_USEC; // nsec to usec - - // QElapsedTimer may not advance if the CPU has gone to sleep. In which case it - // will begin to deviate from real time. We detect that here, and reset if necessary - quint64 msecsCurrentTime = QDateTime::currentMSecsSinceEpoch(); - quint64 msecsEstimate = (TIME_REFERENCE + usecsElapsed) / USECS_PER_MSEC; // usecs to msecs - int possibleSkew = msecsEstimate - msecsCurrentTime; - const int TOLERANCE = 10 * MSECS_PER_SECOND; // up to 10 seconds of skew is tolerated - if (abs(possibleSkew) > TOLERANCE) { - // reset our TIME_REFERENCE and timer - TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * USECS_PER_MSEC; // ms to usec - timestampTimer.restart(); - now = TIME_REFERENCE + ::usecTimestampNowAdjust; - - if (wantDebug) { - qCDebug(shared) << "usecTimestampNow() - resetting QElapsedTimer. "; - qCDebug(shared) << " msecsCurrentTime:" << msecsCurrentTime; - qCDebug(shared) << " msecsEstimate:" << msecsEstimate; - qCDebug(shared) << " possibleSkew:" << possibleSkew; - qCDebug(shared) << " TOLERANCE:" << TOLERANCE; - - qCDebug(shared) << " nsecsElapsed:" << nsecsElapsed; - qCDebug(shared) << " usecsElapsed:" << usecsElapsed; - - QDateTime currentLocalTime = QDateTime::currentDateTime(); - - quint64 msecsNow = now / 1000; // usecs to msecs - QDateTime nowAsString; - nowAsString.setMSecsSinceEpoch(msecsNow); - - qCDebug(shared) << " now:" << now; - qCDebug(shared) << " msecsNow:" << msecsNow; - - qCDebug(shared) << " nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz"); - qCDebug(shared) << " currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz"); - } - } else { - now = TIME_REFERENCE + usecsElapsed + ::usecTimestampNowAdjust; - } - - if (wantDebug) { - QDateTime currentLocalTime = QDateTime::currentDateTime(); - - quint64 msecsNow = now / 1000; // usecs to msecs - QDateTime nowAsString; - nowAsString.setMSecsSinceEpoch(msecsNow); - - quint64 msecsTimeReference = TIME_REFERENCE / 1000; // usecs to msecs - QDateTime timeReferenceAsString; - timeReferenceAsString.setMSecsSinceEpoch(msecsTimeReference); - - qCDebug(shared) << "usecTimestampNow() - details... "; - qCDebug(shared) << " TIME_REFERENCE:" << TIME_REFERENCE; - qCDebug(shared) << " timeReferenceAsString:" << timeReferenceAsString.toString("yyyy-MM-dd hh:mm:ss.zzz"); - qCDebug(shared) << " usecTimestampNowAdjust:" << usecTimestampNowAdjust; - qCDebug(shared) << " nsecsElapsed:" << nsecsElapsed; - qCDebug(shared) << " usecsElapsed:" << usecsElapsed; - qCDebug(shared) << " now:" << now; - qCDebug(shared) << " msecsNow:" << msecsNow; - qCDebug(shared) << " nowAsString:" << nowAsString.toString("yyyy-MM-dd hh:mm:ss.zzz"); - qCDebug(shared) << " currentLocalTime:" << currentLocalTime.toString("yyyy-MM-dd hh:mm:ss.zzz"); - } - - return now; + using namespace std::chrono; + static const auto unixEpoch = system_clock::from_time_t(0); + return duration_cast(system_clock::now() - unixEpoch).count() + usecTimestampNowAdjust; } float secTimestampNow() { From 727aea20251af8bbe3c2b77d4ec35b9feda02662 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Thu, 13 Sep 2018 18:21:16 -0700 Subject: [PATCH 02/63] Remove comment --- libraries/shared/src/SharedUtil.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index 886445824b..012e7aa1f5 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -122,7 +122,6 @@ void setGlobalInstance(const char* propertyName, const QVariant& variant) { } } -// Do we still need this? static qint64 usecTimestampNowAdjust = 0; // in usec void usecTimestampNowForceClockSkew(qint64 clockSkew) { ::usecTimestampNowAdjust = clockSkew; From f2a046da9160edafec7b94d838f7ed19983a46ce Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 13 Sep 2018 12:15:18 -0700 Subject: [PATCH 03/63] Install marketplace item tester --- .../MarketplaceItemTester.qml | 157 ++++++++++++++++++ interface/src/Application.cpp | 9 +- interface/src/commerce/QmlCommerce.cpp | 1 + scripts/system/commerce/wallet.js | 23 +++ 4 files changed, 186 insertions(+), 4 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml new file mode 100644 index 0000000000..80cf82678f --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -0,0 +1,157 @@ +// +// marketplaceItemTester +// qml/hifi/commerce/marketplaceItemTester +// +// Load items not in the marketplace for testing purposes +// +// Created by Zach Fox on 2018-09-05 +// Copyright 2018 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 +// + +import QtQuick 2.5 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Dialogs 1.0 +import QtQuick.Layouts 1.1 +import Hifi 1.0 as Hifi +import "../../../styles-uit" as HifiStylesUit +import "../../../controls-uit" as HifiControlsUit + + + +Rectangle { + id:root + HifiStylesUit.HifiConstants { id: hifi } + color: hifi.colors.white + ListModel { id: listModel } + ListView { + anchors.fill: parent + anchors.leftMargin: 12 + anchors.bottomMargin: 40 + model: listModel + spacing: 5 + delegate: RowLayout { + anchors.left: parent.left + width: parent.width + spacing: 5 + Text { + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + font.pointSize: 12 + Layout.preferredWidth: root.width * .6 + horizontalAlignment: Text.AlignBottom + } + Text { + text: assetType + font.pointSize: 10 + Layout.preferredWidth: root.width * .2 + horizontalAlignment: Text.AlignBottom + } + property var actions: { + "forward": function(resource, assetType){ + if ("application" == assetType) { + Commerce.installApp(resource); + Commerce.openApp(resource); + } + // XXX support other resource types here. + }, + "trash": function(){ + if ("application" == assetType) { + Commerce.uninstallApp(resource); + } + // XXX support other resource types here. + listModel.remove(index); + } + } + Repeater { + model: [ + { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 }, + { "name": "trash", "glyph": hifi.glyphs.trash, "size": 22} + ] + HifiStylesUit.HiFiGlyphs { + text: modelData.glyph + size: modelData.size + color: hifi.colors.black + horizontalAlignment: Text.AlignHCenter + MouseArea { + anchors.fill: parent + onClicked: { + actions[modelData.name](resource, assetType); + } + } + } + } + } + headerPositioning: ListView.OverlayHeader + header: HifiStylesUit.RalewayRegular { + id: rootHeader + text: "Marketplace Item Tester" + height: 80 + width: paintedWidth + size: 22 + color: hifi.colors.black + anchors.left: parent.left + anchors.leftMargin: 12 + } + footerPositioning: ListView.OverlayFooter + footer: Row { + id: rootActions + spacing: 20 + anchors.horizontalCenter: parent.horizontalCenter + property string currentAction + function assetType(resource) { + return (resource.match(/\.app\.json$/) ? "application" : + resource.match(/\.(?:fbx|fst)$/) ? "avatar" : + resource.match(/\.json\.gz$/) ? "content set" : + resource.match(/\.json$/) ? "entity or wearable" : + "unknown") + } + function onResourceSelected(resource) { + // It is possible that we received the present signal + // from something other than our browserAsync window. + // Alas, there is nothing we can do about that so charge + // ahead as though we are sure the present signal is one + // we expect. + if ("load file" == currentAction) { + print("disconnecting load file"); + Window.browseChanged.disconnect(onResourceSelected); + } else if ("load url" == currentAction) { + print("disconnecting load url"); + Window.promptTextChanged.disconnect(onResourceSelected); + } + if (resource) { + listModel.append( { + "resource": resource.trim(), + "assetType": assetType(resource.trim()) } ); + } + } + property var actions: { + "Load File": function(){ + rootActions.currentAction = "load file"; + Window.browseChanged.connect(onResourceSelected); + Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)"); + }, + "Load URL": function(){ + rootActions.currentAction = "load url"; + Window.promptTextChanged.connect(onResourceSelected); + Window.promptAsync("Please enter a URL", ""); + } + } + Repeater { + model: [ "Load File", "Load URL" ] + HifiControlsUit.Button { + color: hifi.buttons.blue + fontSize: 20 + text: modelData + width: root.width / 3 + height: 40 + onClicked: actions[text]() + } + } + } + } +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 826f6a87a9..0415475397 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1691,21 +1691,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo return DependencyManager::get()->navigationFocused() ? 1 : 0; }); _applicationStateDevice->setInputVariant(STATE_PLATFORM_WINDOWS, []() -> float { -#if defined(Q_OS_WIN) +#if defined(Q_OS_WIN) return 1; #else return 0; #endif }); _applicationStateDevice->setInputVariant(STATE_PLATFORM_MAC, []() -> float { -#if defined(Q_OS_MAC) +#if defined(Q_OS_MAC) return 1; #else return 0; #endif }); _applicationStateDevice->setInputVariant(STATE_PLATFORM_ANDROID, []() -> float { -#if defined(Q_OS_ANDROID) +#if defined(Q_OS_ANDROID) return 1; #else return 0; @@ -2881,9 +2881,10 @@ void Application::initializeUi() { QUrl{ "hifi/commerce/common/CommerceLightbox.qml" }, QUrl{ "hifi/commerce/common/EmulatedMarketplaceHeader.qml" }, QUrl{ "hifi/commerce/common/FirstUseTutorial.qml" }, - QUrl{ "hifi/commerce/common/SortableListModel.qml" }, QUrl{ "hifi/commerce/common/sendAsset/SendAsset.qml" }, + QUrl{ "hifi/commerce/common/SortableListModel.qml" }, QUrl{ "hifi/commerce/inspectionCertificate/InspectionCertificate.qml" }, + QUrl{ "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"}, QUrl{ "hifi/commerce/purchases/PurchasedItem.qml" }, QUrl{ "hifi/commerce/purchases/Purchases.qml" }, QUrl{ "hifi/commerce/wallet/Help.qml" }, diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 06da18148f..10be228310 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -228,6 +228,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { // Thus, we protect against deleting the .app.json from the user's disk (below) // by skipping that check for the app we just installed. if ((justInstalledAppID != "") && ((justInstalledAppID + ".app.json") == appFileName)) { + installedAppsFromMarketplace += appFileName + ","; continue; } diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 5939b36438..12ec7dce6b 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -583,6 +583,27 @@ // // Manage the connection between the button and the window. // + var DEVELOPER_MENU = "Developer"; + var MARKETPLACE_ITEM_TESTER_LABEL = "Marktplace Item Tester"; + var MARKETPLACE_ITEM_TESTER_QML_SOURCE = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; + function installMarketplaceItemTester() { + if (!Menu.menuExists(DEVELOPER_MENU)) { + Menu.addMenu(DEVELOPER_MENU); + } + + if (!Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL)) { + Menu.addMenuItem({ menuName: DEVELOPER_MENU, + menuItemName: MARKETPLACE_ITEM_TESTER_LABEL, + isCheckable: false }) + } + + Menu.menuItemEvent.connect(function (menuItem) { + if (menuItem === MARKETPLACE_ITEM_TESTER_LABEL) { + tablet.loadQMLSource(MARKETPLACE_ITEM_TESTER_QML_SOURCE); + } + }); + } + var button; var buttonName = "WALLET"; var tablet = null; @@ -600,7 +621,9 @@ button.clicked.connect(onButtonClicked); tablet.screenChanged.connect(onTabletScreenChanged); } + installMarketplaceItemTester(); } + var isWired = false; var isUpdateOverlaysWired = false; function off() { From 8f5923d5624026d492ef5067dc915ccdfe56d4dc Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Tue, 18 Sep 2018 11:23:32 -0700 Subject: [PATCH 04/63] Show installed apps on start of marketplace item tester Also, refactor a bit. --- .../MarketplaceItemTester.qml | 120 ++++++++++++------ 1 file changed, 79 insertions(+), 41 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 80cf82678f..b36deb8b70 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -22,39 +22,61 @@ import "../../../controls-uit" as HifiControlsUit Rectangle { - id:root + id: root + property string installedApps HifiStylesUit.HifiConstants { id: hifi } + ListModel { id: resourceListModel } + color: hifi.colors.white - ListModel { id: listModel } + + function buildResourceObj(resource) { + resource = resource.trim(); + var assetType = (resource.match(/\.app\.json$/) ? "application" : + resource.match(/\.(?:fbx|fst)$/) ? "avatar" : + resource.match(/\.json\.gz$/) ? "content set" : + resource.match(/\.json$/) ? "entity or wearable" : + "unknown"); + return { "resource": resource, "assetType": assetType }; + } + + function installResourceObj(resourceObj) { + if ("application" == resourceObj["assetType"]) { + Commerce.installApp(resourceObj["resource"]); + } + // XXX support other asset types here + } + + function addAllInstalledAppsToList() { + var i, apps = Commerce.getInstalledApps().split(","), len = apps.length; + for(i = 0; i < len - 1; ++i) { + if (i in apps) { + resourceListModel.append(buildResourceObj(apps[i])); + } + } + } + + Component.onCompleted: { + // On startup, list includes all tester-installed assets. + addAllInstalledAppsToList(); + // XXX support other asset types here + } + ListView { anchors.fill: parent anchors.leftMargin: 12 anchors.bottomMargin: 40 - model: listModel + anchors.rightMargin: 12 + model: resourceListModel spacing: 5 + delegate: RowLayout { anchors.left: parent.left width: parent.width spacing: 5 - Text { - text: { - var match = resource.match(/\/([^/]*)$/); - return match ? match[1] : resource; - } - font.pointSize: 12 - Layout.preferredWidth: root.width * .6 - horizontalAlignment: Text.AlignBottom - } - Text { - text: assetType - font.pointSize: 10 - Layout.preferredWidth: root.width * .2 - horizontalAlignment: Text.AlignBottom - } + property var actions: { "forward": function(resource, assetType){ if ("application" == assetType) { - Commerce.installApp(resource); Commerce.openApp(resource); } // XXX support other resource types here. @@ -64,9 +86,27 @@ Rectangle { Commerce.uninstallApp(resource); } // XXX support other resource types here. - listModel.remove(index); + resourceListModel.remove(index); } } + + Text { + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + font.pointSize: 12 + Layout.preferredWidth: root.width * .6 + horizontalAlignment: Text.AlignBottom + } + + Text { + text: assetType + font.pointSize: 10 + Layout.preferredWidth: root.width * .2 + horizontalAlignment: Text.AlignBottom + } + Repeater { model: [ { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 }, @@ -86,6 +126,7 @@ Rectangle { } } } + headerPositioning: ListView.OverlayHeader header: HifiStylesUit.RalewayRegular { id: rootHeader @@ -97,19 +138,27 @@ Rectangle { anchors.left: parent.left anchors.leftMargin: 12 } + footerPositioning: ListView.OverlayFooter footer: Row { id: rootActions spacing: 20 anchors.horizontalCenter: parent.horizontalCenter + property string currentAction - function assetType(resource) { - return (resource.match(/\.app\.json$/) ? "application" : - resource.match(/\.(?:fbx|fst)$/) ? "avatar" : - resource.match(/\.json\.gz$/) ? "content set" : - resource.match(/\.json$/) ? "entity or wearable" : - "unknown") + property var actions: { + "Load File": function(){ + rootActions.currentAction = "load file"; + Window.browseChanged.connect(onResourceSelected); + Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)"); + }, + "Load URL": function(){ + rootActions.currentAction = "load url"; + Window.promptTextChanged.connect(onResourceSelected); + Window.promptAsync("Please enter a URL", ""); + } } + function onResourceSelected(resource) { // It is possible that we received the present signal // from something other than our browserAsync window. @@ -124,23 +173,12 @@ Rectangle { Window.promptTextChanged.disconnect(onResourceSelected); } if (resource) { - listModel.append( { - "resource": resource.trim(), - "assetType": assetType(resource.trim()) } ); - } - } - property var actions: { - "Load File": function(){ - rootActions.currentAction = "load file"; - Window.browseChanged.connect(onResourceSelected); - Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)"); - }, - "Load URL": function(){ - rootActions.currentAction = "load url"; - Window.promptTextChanged.connect(onResourceSelected); - Window.promptAsync("Please enter a URL", ""); + var resourceObj = buildResourceObj(resource); + installResourceObj(resourceObj); + resourceListModel.append(resourceObj); } } + Repeater { model: [ "Load File", "Load URL" ] HifiControlsUit.Button { From a97f9eb79aeccf423517c099d12f3c4daf319cb8 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Sep 2018 12:28:03 -0700 Subject: [PATCH 05/63] Logic for Wallet notifications; some bugfixes --- scripts/modules/appUi.js | 18 +++-- scripts/modules/request.js | 4 +- scripts/system/commerce/wallet.js | 106 +++++++++++++++++++++++++++++- scripts/system/pal.js | 22 ++++--- 4 files changed, 131 insertions(+), 19 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index dab377911b..98f6dad1f9 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -155,8 +155,8 @@ function AppUi(properties) { return; } - // User is "appearing offline" - if (GlobalServices.findableBy === "none") { + // User is "appearing offline", or is offline, or the app is open + if (GlobalServices.findableBy === "none" || Account.username === "" || that.isOpen) { that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); return; } @@ -164,7 +164,10 @@ function AppUi(properties) { var url = METAVERSE_BASE + that.notificationPollEndpoint; if (that.notificationPollCaresAboutSince) { - url = url + "&since=" + (new Date().getTime()); + var settingsKey = "notifications/" + that.buttonName + "/lastSince"; + var timestamp = Settings.getValue(settingsKey, new Date().getTime()); + url = url + "&since=" + timestamp; + Settings.setValue(settingsKey, timestamp); } console.debug(that.buttonName, 'polling for notifications at endpoint', url); @@ -203,7 +206,8 @@ function AppUi(properties) { // This won't do anything if there isn't a notification endpoint set that.notificationPoll(); - function availabilityChanged() { + function restartNotificationPoll() { + that.notificationInitialCallbackMade = false; if (that.notificationPollTimeout) { Script.clearTimeout(that.notificationPollTimeout); that.notificationPollTimeout = false; @@ -303,7 +307,8 @@ function AppUi(properties) { } : that.ignore; that.onScriptEnding = function onScriptEnding() { // Close if necessary, clean up any remaining handlers, and remove the button. - GlobalServices.findableByChanged.disconnect(availabilityChanged); + GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll); + GlobalServices.findableByChanged.disconnect(restartNotificationPoll); if (that.isOpen) { that.close(); } @@ -323,6 +328,7 @@ function AppUi(properties) { that.tablet.screenChanged.connect(that.onScreenChanged); that.button.clicked.connect(that.onClicked); Script.scriptEnding.connect(that.onScriptEnding); - GlobalServices.findableByChanged.connect(availabilityChanged); + GlobalServices.findableByChanged.connect(restartNotificationPoll); + GlobalServices.myUsernameChanged.connect(restartNotificationPoll); } module.exports = AppUi; diff --git a/scripts/modules/request.js b/scripts/modules/request.js index 3516554567..d0037f9b43 100644 --- a/scripts/modules/request.js +++ b/scripts/modules/request.js @@ -19,7 +19,7 @@ module.exports = { // ------------------------------------------------------------------ request: function (options, callback) { // cb(error, responseOfCorrectContentType) of url. A subset of npm request. - var httpRequest = new XMLHttpRequest(), key; + var httpRequest = new XMLHttpRequest(), key; // QT bug: apparently doesn't handle onload. Workaround using readyState. httpRequest.onreadystatechange = function () { var READY_STATE_DONE = 4; @@ -72,7 +72,7 @@ module.exports = { } httpRequest.open(options.method, options.uri, true); httpRequest.send(options.body || null); - } + } }; // =========================================================================================== diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 993ea30c2e..26b8f95bd5 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -491,12 +491,110 @@ function walletOpened() { Controller.mouseMoveEvent.connect(handleMouseMoveEvent); triggerMapping.enable(); triggerPressMapping.enable(); + ui.messagesWaiting(false); } function walletClosed() { off(); } +function notificationDataProcessPage(data) { + return data.data.history; +} + +var shouldShowDot = false; +function notificationPollCallback(historyArray) { + var i; + var someoneElsePurchasedArray = []; + var proofIssuedArray = []; + var moneyReceivedArray = []; + var giftReceivedArray = []; + for (i = 0; i < historyArray.length; i++) { + var currentHistoryTxn = historyArray[i]; + + if (currentHistoryTxn.sent_certs <= 0 && + currentHistoryTxn.received_certs <= 0) { + // This is an HFC transfer. + if (currentHistoryTxn.received_money > 0) { + if (currentHistoryTxn.sender_name === "marketplace") { + someoneElsePurchasedArray.push(currentHistoryTxn); + } else { + moneyReceivedArray.push(currentHistoryTxn); + } + } + } else if (currentHistoryTxn.sent_money <= 0 && + currentHistoryTxn.received_money <= 0 && + currentHistoryTxn.received_certs > 0) { + // This is a non-HFC asset transfer. + if (currentHistoryTxn.sender_name === "marketplace") { + proofIssuedArray.push(currentHistoryTxn); + } else { + giftReceivedArray.push(currentHistoryTxn); + } + } + } + + if (!ui.isOpen) { + shouldShowDot = shouldShowDot || + (someoneElsePurchasedArray.length > 0) || + (proofIssuedArray.length > 0) || + (moneyReceivedArray.length > 0) || + (giftReceivedArray.length > 0); + ui.messagesWaiting(shouldShowDot); + + var notificationCount = someoneElsePurchasedArray.length + + proofIssuedArray.length + + moneyReceivedArray.length + + giftReceivedArray.length; + + if (notificationCount > 0) { + var message; + if (!ui.notificationInitialCallbackMade) { + message = "You have " + notificationCount + " unread wallet " + + "notification" + (notificationCount === 1 ? "" : "s") + "! Open WALLET to see all activity."; + ui.notificationDisplayBanner(message); + } else { + var currentItemName, senderName; + for (i = 0; i < someoneElsePurchasedArray.length; i++) { + currentItemName = (someoneElsePurchasedArray[i].message).match('(.*)')[1]; + message = "Someone purchased your item \"" + currentItemName + "\" from the Marketplace! " + + "Open WALLET to see all activity."; + ui.notificationDisplayBanner(message); + } + for (i = 0; i < proofIssuedArray.length; i++) { + currentItemName = (proofIssuedArray[i].message).match('(.*)')[1]; + message = "You have been issued a proof for your Marketplace item \"" + currentItemName + "\"! " + + "Open WALLET to see all activity."; + ui.notificationDisplayBanner(message); + } + for (i = 0; i < moneyReceivedArray.length; i++) { + senderName = moneyReceivedArray[i].sender_name; + if (senderName === "") { + senderName = "Someone"; + } + message = senderName + " sent you " + moneyReceivedArray[i].received_money + " HFC! " + + "Open WALLET to see all activity."; + ui.notificationDisplayBanner(message); + } + for (i = 0; i < giftReceivedArray.length; i++) { + senderName = giftReceivedArray[i].sender_name; + if (senderName === "") { + senderName = "Someone"; + } + message = senderName + " sent you a gift! " + + "Open WALLET to see all activity."; + ui.notificationDisplayBanner(message); + } + } + } + } +} + +function isReturnedDataEmpty(data) { + var historyArray = data.data.history; + return historyArray.length === 0; +} + // // Manage the connection between the button and the window. // @@ -510,7 +608,13 @@ function startup() { home: WALLET_QML_SOURCE, onOpened: walletOpened, onClosed: walletClosed, - onMessage: fromQml + onMessage: fromQml, + notificationPollEndpoint: "/api/v1/notifications?source=commerce-history&per_page=10", + notificationPollTimeoutMs: 60000, + notificationDataProcessPage: notificationDataProcessPage, + notificationPollCallback: notificationPollCallback, + notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, + notificationPollCaresAboutSince: true }); GlobalServices.myUsernameChanged.connect(onUsernameChanged); } diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 1be5b44786..38359fbab9 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -874,16 +874,18 @@ function notificationPollCallback(connectionsArray) { shouldShowDot: shouldShowDot }); - var message; - if (!ui.notificationInitialCallbackMade) { - message = newlyOnlineConnectionsArray.length + " of your connections " + - (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online. Open PEOPLE to join them!"; - ui.notificationDisplayBanner(message); - } else { - for (i = 0; i < newlyOnlineConnectionsArray.length; i++) { - message = newlyOnlineConnectionsArray[i].username + " is available in " + - newlyOnlineConnectionsArray[i].location.root.name + ". Open PEOPLE to join them!"; + if (newlyOnlineConnectionsArray.length > 0) { + var message; + if (!ui.notificationInitialCallbackMade) { + message = newlyOnlineConnectionsArray.length + " of your connections " + + (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online! Open PEOPLE to join them."; ui.notificationDisplayBanner(message); + } else { + for (i = 0; i < newlyOnlineConnectionsArray.length; i++) { + message = newlyOnlineConnectionsArray[i].username + " is available in " + + newlyOnlineConnectionsArray[i].location.root.name + "! Open PEOPLE to join them."; + ui.notificationDisplayBanner(message); + } } } } @@ -902,7 +904,7 @@ function startup() { onOpened: palOpened, onClosed: off, onMessage: fromQml, - notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10", + notificationPollEndpoint: "/api/v1/notifications?source=users&filter=connections&per_page=10", notificationPollTimeoutMs: 60000, notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, From 645ad2bb7a3493a73f5aace63a49b8d03b605759 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Tue, 18 Sep 2018 12:36:03 -0700 Subject: [PATCH 06/63] Enable marketplace item test for avatar url --- .../marketplaceItemTester/MarketplaceItemTester.qml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index b36deb8b70..fed8480239 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -76,8 +76,12 @@ Rectangle { property var actions: { "forward": function(resource, assetType){ - if ("application" == assetType) { - Commerce.openApp(resource); + switch(assetType) { + case "application": + Commerce.openApp(resource); + break + case "avatar": + MyAvatar.useFullAvatarURL(resource); } // XXX support other resource types here. }, From 04b44d05942d3247ccf5f6eaf523432ec64e7e29 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Sep 2018 12:38:33 -0700 Subject: [PATCH 07/63] Fix small since bug --- scripts/modules/appUi.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 98f6dad1f9..b8e5cc45f9 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -165,9 +165,10 @@ function AppUi(properties) { if (that.notificationPollCaresAboutSince) { var settingsKey = "notifications/" + that.buttonName + "/lastSince"; - var timestamp = Settings.getValue(settingsKey, new Date().getTime()); - url = url + "&since=" + timestamp; - Settings.setValue(settingsKey, timestamp); + var currentTimestamp = new Date().getTime(); + var settingsTimestamp = Settings.getValue(settingsKey, currentTimestamp); + url = url + "&since=" + settingsTimestamp; + Settings.setValue(settingsKey, currentTimestamp); } console.debug(that.buttonName, 'polling for notifications at endpoint', url); From a592565b7b0ee6c941cb6e6de561d0bbd1bbe160 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Tue, 18 Sep 2018 13:25:01 -0700 Subject: [PATCH 08/63] Enable marketplace item test for avatar file --- .../commerce/marketplaceItemTester/MarketplaceItemTester.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index fed8480239..4ef6109752 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -32,7 +32,7 @@ Rectangle { function buildResourceObj(resource) { resource = resource.trim(); var assetType = (resource.match(/\.app\.json$/) ? "application" : - resource.match(/\.(?:fbx|fst)$/) ? "avatar" : + resource.match(/\.fst$/) ? "avatar" : resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); @@ -154,7 +154,7 @@ Rectangle { "Load File": function(){ rootActions.currentAction = "load file"; Window.browseChanged.connect(onResourceSelected); - Window.browseAsync("Please select a file", "", "Assets (*.app.json *.json *.fbx *.json.gz)"); + Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)"); }, "Load URL": function(){ rootActions.currentAction = "load url"; From cfa9d185f02dfd764abb7cf2a7473ebaa097f5c0 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Tue, 18 Sep 2018 14:02:16 -0700 Subject: [PATCH 09/63] Add full resource paths to list in marketplace item tester --- .../MarketplaceItemTester.qml | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 4ef6109752..8b0ecf3436 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -94,21 +94,32 @@ Rectangle { } } - Text { - text: { - var match = resource.match(/\/([^/]*)$/); - return match ? match[1] : resource; - } - font.pointSize: 12 + Column { Layout.preferredWidth: root.width * .6 - horizontalAlignment: Text.AlignBottom + spacing: 5 + Text { + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + font.pointSize: 12 + horizontalAlignment: Text.AlignBottom + } + Text { + text: resource + font.pointSize: 8 + width: root.width * .6 + horizontalAlignment: Text.AlignBottom + wrapMode: Text.WrapAnywhere + } } Text { text: assetType font.pointSize: 10 Layout.preferredWidth: root.width * .2 - horizontalAlignment: Text.AlignBottom + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Test.AlignVCenter } Repeater { From 55a0f77697bb66190b8b8086df14ddb24ad27716 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Sep 2018 14:45:14 -0700 Subject: [PATCH 10/63] Notifications for MARKET --- .../qml/hifi/commerce/wallet/WalletHome.qml | 8 ---- scripts/modules/appUi.js | 10 ++-- scripts/system/commerce/wallet.js | 3 -- scripts/system/marketplaces/marketplaces.js | 46 +++++++++++++++++-- 4 files changed, 47 insertions(+), 20 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml index 50208793fe..627da1d43f 100644 --- a/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml +++ b/interface/resources/qml/hifi/commerce/wallet/WalletHome.qml @@ -45,14 +45,6 @@ Item { onHistoryResult : { transactionHistoryModel.handlePage(null, result); } - - onAvailableUpdatesResult: { - if (result.status !== 'success') { - console.log("Failed to get Available Updates", result.data.message); - } else { - sendToScript({method: 'wallet_availableUpdatesReceived', numUpdates: result.data.updates.length }); - } - } } Connections { diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index b8e5cc45f9..c7f6cc5f39 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -163,13 +163,13 @@ function AppUi(properties) { var url = METAVERSE_BASE + that.notificationPollEndpoint; + var settingsKey = "notifications/" + that.buttonName + "/lastPoll"; + var currentTimestamp = new Date().getTime(); + var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp); if (that.notificationPollCaresAboutSince) { - var settingsKey = "notifications/" + that.buttonName + "/lastSince"; - var currentTimestamp = new Date().getTime(); - var settingsTimestamp = Settings.getValue(settingsKey, currentTimestamp); - url = url + "&since=" + settingsTimestamp; - Settings.setValue(settingsKey, currentTimestamp); + url = url + "&since=" + lastPollTimestamp; } + Settings.setValue(settingsKey, currentTimestamp); console.debug(that.buttonName, 'polling for notifications at endpoint', url); diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 26b8f95bd5..a896bd4071 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -474,9 +474,6 @@ function fromQml(message) { Window.location = "hifi://BankOfHighFidelity"; } break; - case 'wallet_availableUpdatesReceived': - // NOP - break; case 'http.request': // Handled elsewhere, don't log. break; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 13ad1f6b69..1bb0713f50 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -908,10 +908,9 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { removeOverlays(); } break; - case 'wallet_availableUpdatesReceived': case 'purchases_availableUpdatesReceived': - userHasUpdates = message.numUpdates > 0; - ui.messagesWaiting(userHasUpdates); + shouldShowDot = message.numUpdates > 0; + ui.messagesWaiting(shouldShowDot); break; case 'purchases_updateWearables': var currentlyWornWearables = []; @@ -1030,6 +1029,39 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { "\nNew screen URL: " + url + "\nCurrent app open status: " + ui.isOpen + "\n"); }; +function notificationDataProcessPage(data) { + return data.data.updates; +} + +var shouldShowDot = false; +function notificationPollCallback(updatesArray) { + shouldShowDot = shouldShowDot || updatesArray.length > 0; + ui.messagesWaiting(shouldShowDot); + + if (updatesArray.length > 0) { + var message; + if (!ui.notificationInitialCallbackMade) { + message = updatesArray.length + " of your purchased items have updates available! " + + "Open MARKET to update."; + ui.notificationDisplayBanner(message); + + ui.notificationPollCaresAboutSince = true; + } else { + for (var i = 0; i < updatesArray.length; i++) { + message = "There's an update available for your version of \"" + + updatesArray[i].marketplace_item_name + "\"!" + + "Open MARKET to update."; + ui.notificationDisplayBanner(message); + } + } + } +} + +function isReturnedDataEmpty(data) { + var historyArray = data.data.updates; + return historyArray.length === 0; +} + // // Manage the connection between the button and the window. // @@ -1044,7 +1076,13 @@ function startup() { inject: MARKETPLACES_INJECT_SCRIPT_URL, home: MARKETPLACE_URL_INITIAL, onScreenChanged: onTabletScreenChanged, - onMessage: onQmlMessageReceived + onMessage: onQmlMessageReceived, + notificationPollEndpoint: "/api/v1/notifications?source=commerce-available_updates&per_page=10", + notificationPollTimeoutMs: 60000, + notificationDataProcessPage: notificationDataProcessPage, + notificationPollCallback: notificationPollCallback, + notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, + notificationPollCaresAboutSince: false // Changes to true after first poll }); ContextOverlay.contextOverlayClicked.connect(openInspectionCertificateQML); Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged); From 498716470641908c61e39bb0ca52583d282a8002 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Sep 2018 15:30:43 -0700 Subject: [PATCH 11/63] Notifications for GOTO (using a cool algorithm) --- scripts/system/commerce/wallet.js | 9 +- scripts/system/tablet-goto.js | 193 +++++++++++++++--------------- 2 files changed, 99 insertions(+), 103 deletions(-) diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index a896bd4071..2f3f5c109d 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -532,17 +532,12 @@ function notificationPollCallback(historyArray) { } if (!ui.isOpen) { - shouldShowDot = shouldShowDot || - (someoneElsePurchasedArray.length > 0) || - (proofIssuedArray.length > 0) || - (moneyReceivedArray.length > 0) || - (giftReceivedArray.length > 0); - ui.messagesWaiting(shouldShowDot); - var notificationCount = someoneElsePurchasedArray.length + proofIssuedArray.length + moneyReceivedArray.length + giftReceivedArray.length; + shouldShowDot = shouldShowDot || notificationCount > 0; + ui.messagesWaiting(shouldShowDot); if (notificationCount > 0) { var message; diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 804f838d04..26099828dc 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -15,118 +15,119 @@ // (function () { // BEGIN LOCAL_SCOPE -var request = Script.require('request').request; var AppUi = Script.require('appUi'); -var DEBUG = false; -function debug() { - if (!DEBUG) { - return; - } - print('tablet-goto.js:', [].map.call(arguments, JSON.stringify)); -} - -var stories = {}, pingPong = false; -function expire(id) { - var options = { - uri: Account.metaverseServerURL + '/api/v1/user_stories/' + id, - method: 'PUT', - json: true, - body: {expire: "true"} - }; - request(options, function (error, response) { - debug('expired story', options, 'error:', error, 'response:', response); - if (error || (response.status !== 'success')) { - print("ERROR expiring story: ", error || response.status); - } - }); -} -var PER_PAGE_DEBUG = 10; -var PER_PAGE_NORMAL = 100; -function pollForAnnouncements() { - // We could bail now if !Account.isLoggedIn(), but what if we someday have system-wide announcments? - var actions = 'announcement'; - var count = DEBUG ? PER_PAGE_DEBUG : PER_PAGE_NORMAL; - var options = [ - 'now=' + new Date().toISOString(), - 'include_actions=' + actions, - 'restriction=' + (Account.isLoggedIn() ? 'open,hifi' : 'open'), - 'require_online=true', - 'protocol=' + encodeURIComponent(Window.protocolSignature()), - 'per_page=' + count - ]; - var url = Account.metaverseServerURL + '/api/v1/user_stories?' + options.join('&'); - request({ - uri: url - }, function (error, data) { - debug(url, error, data); - if (error || (data.status !== 'success')) { - print("Error: unable to get", url, error || data.status); - return; - } - var didNotify = false, key; - pingPong = !pingPong; - data.user_stories.forEach(function (story) { - var stored = stories[story.id], storedOrNew = stored || story; - debug('story exists:', !!stored, storedOrNew); - if ((storedOrNew.username === Account.username) && (storedOrNew.place_name !== location.placename)) { - if (storedOrNew.audience === 'for_connections') { // Only expire if we haven't already done so. - expire(story.id); - } - return; // before marking - } - storedOrNew.pingPong = pingPong; - if (stored) { // already seen - return; - } - stories[story.id] = story; - var message = story.username + " " + story.action_string + " in " + - story.place_name + ". Open GOTO to join them."; - Window.displayAnnouncement(message); - didNotify = true; - }); - for (key in stories) { // Any story we were tracking that was not marked, has expired. - if (stories[key].pingPong !== pingPong) { - debug('removing story', key); - delete stories[key]; - } - } - if (didNotify) { - ui.messagesWaiting(true); - if (HMD.isHandControllerAvailable()) { - var STRENGTH = 1.0, DURATION_MS = 60, HAND = 2; // both hands - Controller.triggerHapticPulse(STRENGTH, DURATION_MS, HAND); - } - } else if (!Object.keys(stories).length) { // If there's nothing being tracked, then any messageWaiting has expired. - ui.messagesWaiting(false); - } - }); -} -var MS_PER_SEC = 1000; -var DEBUG_POLL_TIME_SEC = 10; -var NORMAL_POLL_TIME_SEC = 60; -var ANNOUNCEMENTS_POLL_TIME_MS = (DEBUG ? DEBUG_POLL_TIME_SEC : NORMAL_POLL_TIME_SEC) * MS_PER_SEC; -var pollTimer = Script.setInterval(pollForAnnouncements, ANNOUNCEMENTS_POLL_TIME_MS); function gotoOpened() { ui.messagesWaiting(false); } +function notificationDataProcessPage(data) { + return data.user_stories; +} + +var shouldShowDot = false; +var pingPong = false; +var storedAnnouncements = {}; +var storedFeaturedStories = {}; +function notificationPollCallback(userStoriesArray) { + // + // START logic for keeping track of new info + // + pingPong = !pingPong; + var totalCountedStories = 0; + userStoriesArray.forEach(function (story) { + if (story.audience !== "for_connections" && + story.audience !== "for_feed") { + return; + } else { + totalCountedStories++; + } + + var stored = storedAnnouncements[story.id] || storedFeaturedStories[story.id]; + var storedOrNew = stored || story; + storedOrNew.pingPong = pingPong; + if (stored) { + return; + } + + if (story.audience === "for_connections") { + storedAnnouncements[story.id] = story; + } else if (story.audience === "for_feed") { + storedFeaturedStories[story.id] = story; + } + }); + var key; + for (key in storedAnnouncements) { + if (storedAnnouncements[key].pingPong !== pingPong) { + delete storedAnnouncements[key]; + } + } + for (key in storedFeaturedStories) { + if (storedFeaturedStories[key].pingPong !== pingPong) { + delete storedFeaturedStories[key]; + } + } + // + // END logic for keeping track of new info + // + + var notificationCount = Object.keys(storedAnnouncements).length + + Object.keys(storedFeaturedStories).length; + shouldShowDot = totalCountedStories > 0 || (notificationCount > 0 && shouldShowDot); + ui.messagesWaiting(shouldShowDot && !ui.isOpen); + + if (notificationCount > 0 && !ui.isOpen) { + var message; + if (!ui.notificationInitialCallbackMade) { + message = "You have " + userStoriesArray.length + "event invitations pending! " + + "Open GOTO to see them."; + ui.notificationDisplayBanner(message); + } else { + for (key in storedAnnouncements) { + message = storedAnnouncements[key].username + " says something is happening in " + + storedAnnouncements[key].place_name + "! Open GOTO to join them."; + ui.notificationDisplayBanner(message); + } + for (key in storedFeaturedStories) { + message = storedFeaturedStories[key].username + " has invited you to an event in " + + storedFeaturedStories[key].place_name + "! Open GOTO to join them."; + ui.notificationDisplayBanner(message); + } + } + } +} + +function isReturnedDataEmpty(data) { + var storiesArray = data.user_stories; + return storiesArray.length === 0; +} + var ui; var GOTO_QML_SOURCE = "hifi/tablet/TabletAddressDialog.qml"; var BUTTON_NAME = "GOTO"; function startup() { + var options = [ + 'include_actions=announcement', + 'restriction=open,hifi', + 'require_online=true', + 'protocol=' + encodeURIComponent(Window.protocolSignature()), + 'per_page=10' + ]; + var endpoint = '/api/v1/notifications?source=user_stories?' + options.join('&'); + ui = new AppUi({ buttonName: BUTTON_NAME, sortOrder: 8, onOpened: gotoOpened, - home: GOTO_QML_SOURCE + home: GOTO_QML_SOURCE, + notificationPollEndpoint: endpoint, + notificationPollTimeoutMs: 60000, + notificationDataProcessPage: notificationDataProcessPage, + notificationPollCallback: notificationPollCallback, + notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, + notificationPollCaresAboutSince: true }); } -function shutdown() { - Script.clearInterval(pollTimer); -} - startup(); -Script.scriptEnding.connect(shutdown); }()); // END LOCAL_SCOPE From 9db11aeda77be5368e2f13cacf1e32bebfcb0bc7 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Sep 2018 16:35:53 -0700 Subject: [PATCH 12/63] Bugfixes and new algo for PAL notifs --- scripts/system/pal.js | 77 +++++++++++++++-------------------- scripts/system/tablet-goto.js | 36 ++++++++-------- 2 files changed, 50 insertions(+), 63 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 38359fbab9..355ed0a504 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -823,46 +823,42 @@ function notificationDataProcessPage(data) { } var shouldShowDot = false; -var storedOnlineUsersArray = []; +var pingPong = false; +var storedOnlineUsers = {}; function notificationPollCallback(connectionsArray) { // // START logic for handling online/offline user changes // - var i, j; - var newlyOnlineConnectionsArray = []; - for (i = 0; i < connectionsArray.length; i++) { - var currentUser = connectionsArray[i]; + pingPong = !pingPong; + var newOnlineUsers = 0; + var message; - if (connectionsArray[i].online) { - var indexOfStoredOnlineUser = -1; - for (j = 0; j < storedOnlineUsersArray.length; j++) { - if (currentUser.username === storedOnlineUsersArray[j].username) { - indexOfStoredOnlineUser = j; - break; - } - } - // If the user record isn't already presesnt inside `storedOnlineUsersArray`... - if (indexOfStoredOnlineUser < 0) { - storedOnlineUsersArray.push(currentUser); - newlyOnlineConnectionsArray.push(currentUser); - } - } else { - var indexOfOfflineUser = -1; - for (j = 0; j < storedOnlineUsersArray.length; j++) { - if (currentUser.username === storedOnlineUsersArray[j].username) { - indexOfOfflineUser = j; - break; - } - } - if (indexOfOfflineUser >= 0) { - storedOnlineUsersArray.splice(indexOfOfflineUser); + connectionsArray.forEach(function (user) { + var stored = storedOnlineUsers[user.username]; + var storedOrNew = stored || user; + storedOrNew.pingPong = pingPong; + if (stored) { + return; + } + + if (user.online) { + newOnlineUsers++; + storedOnlineUsers[user.username] = user; + + if (!ui.isOpen && ui.notificationInitialCallbackMade) { + message = user.username + " is available in " + + user.location.root.name + "! Open PEOPLE to join them."; + ui.notificationDisplayBanner(message); } } + }); + var key; + for (key in storedOnlineUsers) { + if (storedOnlineUsers[key].pingPong !== pingPong) { + delete storedOnlineUsers[key]; + } } - // If there's new data, the light should turn on. - // If the light is already on and you have connections online, the light should stay on. - // In all other cases, the light should turn off or stay off. - shouldShowDot = newlyOnlineConnectionsArray.length > 0 || (storedOnlineUsersArray.length > 0 && shouldShowDot); + shouldShowDot = newOnlineUsers > 0 || (Object.keys(storedOnlineUsers).length > 0 && shouldShowDot); // // END logic for handling online/offline user changes // @@ -874,19 +870,10 @@ function notificationPollCallback(connectionsArray) { shouldShowDot: shouldShowDot }); - if (newlyOnlineConnectionsArray.length > 0) { - var message; - if (!ui.notificationInitialCallbackMade) { - message = newlyOnlineConnectionsArray.length + " of your connections " + - (newlyOnlineConnectionsArray.length === 1 ? "is" : "are") + " online! Open PEOPLE to join them."; - ui.notificationDisplayBanner(message); - } else { - for (i = 0; i < newlyOnlineConnectionsArray.length; i++) { - message = newlyOnlineConnectionsArray[i].username + " is available in " + - newlyOnlineConnectionsArray[i].location.root.name + "! Open PEOPLE to join them."; - ui.notificationDisplayBanner(message); - } - } + if (newOnlineUsers > 0 && !ui.notificationInitialCallbackMade) { + message = newOnlineUsers + " of your connections " + + (newOnlineUsers === 1 ? "is" : "are") + " online! Open PEOPLE to join them."; + ui.notificationDisplayBanner(message); } } } diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 26099828dc..972b9d9841 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -29,12 +29,14 @@ var shouldShowDot = false; var pingPong = false; var storedAnnouncements = {}; var storedFeaturedStories = {}; +var message; function notificationPollCallback(userStoriesArray) { // // START logic for keeping track of new info // pingPong = !pingPong; var totalCountedStories = 0; + var shouldNotifyIndividually = !ui.isOpen && ui.notificationInitialCallbackMade; userStoriesArray.forEach(function (story) { if (story.audience !== "for_connections" && story.audience !== "for_feed") { @@ -52,8 +54,20 @@ function notificationPollCallback(userStoriesArray) { if (story.audience === "for_connections") { storedAnnouncements[story.id] = story; + + if (shouldNotifyIndividually) { + message = storedAnnouncements[key].username + " says something is happening in " + + storedAnnouncements[key].place_name + "! Open GOTO to join them."; + ui.notificationDisplayBanner(message); + } } else if (story.audience === "for_feed") { storedFeaturedStories[story.id] = story; + + if (shouldNotifyIndividually) { + message = storedFeaturedStories[key].username + " has invited you to an event in " + + storedFeaturedStories[key].place_name + "! Open GOTO to join them."; + ui.notificationDisplayBanner(message); + } } }); var key; @@ -76,24 +90,10 @@ function notificationPollCallback(userStoriesArray) { shouldShowDot = totalCountedStories > 0 || (notificationCount > 0 && shouldShowDot); ui.messagesWaiting(shouldShowDot && !ui.isOpen); - if (notificationCount > 0 && !ui.isOpen) { - var message; - if (!ui.notificationInitialCallbackMade) { - message = "You have " + userStoriesArray.length + "event invitations pending! " + - "Open GOTO to see them."; - ui.notificationDisplayBanner(message); - } else { - for (key in storedAnnouncements) { - message = storedAnnouncements[key].username + " says something is happening in " + - storedAnnouncements[key].place_name + "! Open GOTO to join them."; - ui.notificationDisplayBanner(message); - } - for (key in storedFeaturedStories) { - message = storedFeaturedStories[key].username + " has invited you to an event in " + - storedFeaturedStories[key].place_name + "! Open GOTO to join them."; - ui.notificationDisplayBanner(message); - } - } + if (notificationCount > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade) { + message = "You have " + notificationCount + "event invitations pending! " + + "Open GOTO to see them."; + ui.notificationDisplayBanner(message); } } From f70ecdad1698468c87f09b58f47127347c09316d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 18 Sep 2018 17:00:53 -0700 Subject: [PATCH 13/63] Small bugfix --- scripts/system/tablet-goto.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 972b9d9841..63bc8431e8 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -35,14 +35,12 @@ function notificationPollCallback(userStoriesArray) { // START logic for keeping track of new info // pingPong = !pingPong; - var totalCountedStories = 0; + var totalNewStories = 0; var shouldNotifyIndividually = !ui.isOpen && ui.notificationInitialCallbackMade; userStoriesArray.forEach(function (story) { if (story.audience !== "for_connections" && story.audience !== "for_feed") { return; - } else { - totalCountedStories++; } var stored = storedAnnouncements[story.id] || storedFeaturedStories[story.id]; @@ -52,6 +50,8 @@ function notificationPollCallback(userStoriesArray) { return; } + totalNewStories++; + if (story.audience === "for_connections") { storedAnnouncements[story.id] = story; @@ -85,13 +85,13 @@ function notificationPollCallback(userStoriesArray) { // END logic for keeping track of new info // - var notificationCount = Object.keys(storedAnnouncements).length + + var totalStories = Object.keys(storedAnnouncements).length + Object.keys(storedFeaturedStories).length; - shouldShowDot = totalCountedStories > 0 || (notificationCount > 0 && shouldShowDot); + shouldShowDot = totalNewStories > 0 || (totalStories > 0 && shouldShowDot); ui.messagesWaiting(shouldShowDot && !ui.isOpen); - if (notificationCount > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade) { - message = "You have " + notificationCount + "event invitations pending! " + + if (totalStories > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade) { + message = "You have " + totalStories + "event invitations pending! " + "Open GOTO to see them."; ui.notificationDisplayBanner(message); } From 3a7785d10fcd660876d81d9a0437d1821d756bef Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 18 Sep 2018 14:01:12 -0700 Subject: [PATCH 14/63] fix for deadlock in other avatar entity removal --- interface/src/avatar/AvatarManager.cpp | 40 +++++++++------- interface/src/avatar/AvatarManager.h | 5 +- libraries/avatars/src/AvatarHashMap.cpp | 62 ++++++++++++++++++------- libraries/avatars/src/AvatarHashMap.h | 2 + 4 files changed, 74 insertions(+), 35 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1faf17ea9a..d92a5c81da 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -456,25 +456,29 @@ void AvatarManager::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar } void AvatarManager::clearOtherAvatars() { - // Remove other avatars from the world but don't actually remove them from _avatarHash - // each will either be removed on timeout or will re-added to the world on receipt of update. - const render::ScenePointer& scene = qApp->getMain3DScene(); - render::Transaction transaction; - - QReadLocker locker(&_hashLock); - AvatarHash::iterator avatarIterator = _avatarHash.begin(); - while (avatarIterator != _avatarHash.end()) { - auto avatar = std::static_pointer_cast(avatarIterator.value()); - if (avatar != _myAvatar) { - handleRemovedAvatar(avatar); - avatarIterator = _avatarHash.erase(avatarIterator); - } else { - ++avatarIterator; - } - } - assert(scene); - scene->enqueueTransaction(transaction); _myAvatar->clearLookAtTargetAvatar(); + + // setup a vector of removed avatars outside the scope of the hash lock + std::vector removedAvatars; + + { + QWriteLocker locker(&_hashLock); + + auto avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + auto avatar = std::static_pointer_cast(avatarIterator.value()); + if (avatar != _myAvatar) { + removedAvatars.push_back(avatar); + avatarIterator = _avatarHash.erase(avatarIterator); + } else { + ++avatarIterator; + } + } + } + + for (auto& av : removedAvatars) { + handleRemovedAvatar(av); + } } void AvatarManager::deleteAllAvatars() { diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 306ba6f39b..407b3c50de 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -204,7 +204,10 @@ private: void simulateAvatarFades(float deltaTime); AvatarSharedPointer newSharedAvatar() override; - void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; + + // must not be called while holding the hash lock + void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, + KillAvatarReason removalReason = KillAvatarReason::NoReason) override; QVector _avatarsToFade; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index d205a915f8..2ee51cca17 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -66,6 +66,22 @@ void AvatarReplicas::removeReplicas(const QUuid& parentID) { } } +std::vector AvatarReplicas::takeReplicas(const QUuid& parentID) { + std::vector replicas; + + auto it = _replicasMap.find(parentID); + + if (it != _replicasMap.end()) { + // take a copy of the replica shared pointers for this parent + replicas = it->second; + + // erase the replicas for this parent from our map + _replicasMap.erase(it); + } + + return replicas; +} + void AvatarReplicas::processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged) { if (_replicasMap.find(parentID) != _replicasMap.end()) { auto &replicas = _replicasMap[parentID]; @@ -386,24 +402,31 @@ void AvatarHashMap::processKillAvatar(QSharedPointer message, S } void AvatarHashMap::removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason) { - QWriteLocker locker(&_hashLock); + std::vector removedAvatars; - auto replicaIDs = _replicas.getReplicaIDs(sessionUUID); - _replicas.removeReplicas(sessionUUID); - for (auto id : replicaIDs) { - auto removedReplica = _avatarHash.take(id); - if (removedReplica) { - handleRemovedAvatar(removedReplica, removalReason); + { + QWriteLocker locker(&_hashLock); + + auto replicas = _replicas.takeReplicas(sessionUUID); + + for (auto& replica : replicas) { + auto removedReplica = _avatarHash.take(replica->getID()); + if (removedReplica) { + removedAvatars.push_back(removedReplica); + } + } + + _pendingAvatars.remove(sessionUUID); + auto removedAvatar = _avatarHash.take(sessionUUID); + + if (removedAvatar) { + removedAvatars.push_back(removedAvatar); } } - _pendingAvatars.remove(sessionUUID); - auto removedAvatar = _avatarHash.take(sessionUUID); - - if (removedAvatar) { + for (auto& removedAvatar: removedAvatars) { handleRemovedAvatar(removedAvatar, removalReason); } - } void AvatarHashMap::handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason) { @@ -421,11 +444,18 @@ void AvatarHashMap::sessionUUIDChanged(const QUuid& sessionUUID, const QUuid& ol } void AvatarHashMap::clearOtherAvatars() { - QWriteLocker locker(&_hashLock); + QList removedAvatars; - for (auto& av : _avatarHash) { - handleRemovedAvatar(av); + { + QWriteLocker locker(&_hashLock); + + // grab a copy of the current avatars so we can call handleRemoveAvatar for them + removedAvatars = _avatarHash.values(); + + _avatarHash.clear(); } - _avatarHash.clear(); + for (auto& av : removedAvatars) { + handleRemovedAvatar(av); + } } diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 70d7f8c04d..724dd1deac 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -49,6 +49,7 @@ public: void parseDataFromBuffer(const QUuid& parentID, const QByteArray& buffer); void processAvatarIdentity(const QUuid& parentID, const QByteArray& identityData, bool& identityChanged, bool& displayNameChanged); void removeReplicas(const QUuid& parentID); + std::vector takeReplicas(const QUuid& parentID); void processTrait(const QUuid& parentID, AvatarTraits::TraitType traitType, QByteArray traitBinaryData); void processDeletedTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID); void processTraitInstance(const QUuid& parentID, AvatarTraits::TraitType traitType, @@ -180,6 +181,7 @@ protected: virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); + // must not be called while holding the hash lock virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; From 795580f4e1076e2ba9800d54f82e621f2cc030b7 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 19 Sep 2018 09:55:15 -0700 Subject: [PATCH 15/63] adding 404 redirect toggle with interstitial --- interface/src/ConnectionMonitor.cpp | 28 ++++++++++++++++++---- libraries/networking/src/AddressManager.h | 21 ++++++---------- libraries/networking/src/DomainHandler.cpp | 20 +++++++++++----- 3 files changed, 45 insertions(+), 24 deletions(-) diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 3c85cfb339..5eacc5259f 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -18,11 +18,15 @@ #include #include #include +#include // Because the connection monitor is created at startup, the time we wait on initial load // should be longer to allow the application to initialize. static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; +static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; +static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; +Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -37,20 +41,36 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); + if (enableInterstitialMode.get()) { + _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); + } else { + _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + } } connect(&_timer, &QTimer::timeout, this, [this]() { - qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; // set in a timeout error - emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); + if (enableInterstitialMode.get()) { + qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; + emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); + } else { + qDebug() << "ConnectionMonitor: Showing connection failure window"; + DependencyManager::get()->setDomainConnectionFailureVisibility(true); + } }); } void ConnectionMonitor::startTimer() { - _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); + if (enableInterstitialMode.get()) { + _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); + } else { + _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); + } } void ConnectionMonitor::stopTimer() { _timer.stop(); + if (!enableInterstitialMode.get()) { + DependencyManager::get()->setDomainConnectionFailureVisibility(false); + } } diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index c7cdf8f4ea..17041a5fd7 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -140,8 +140,7 @@ public: * * @typedef {number} location.LookupTrigger */ - enum LookupTrigger - { + enum LookupTrigger { UserInput, Back, Forward, @@ -207,9 +206,8 @@ public slots: // functions and signals that should be exposed are moved to a scripting interface class. // // we currently expect this to be called from NodeList once handleLookupString has been called with a path - bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) { - return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); - } + bool goToViewpointForPath(const QString& viewpointString, const QString& pathString) + { return handleViewpoint(viewpointString, false, DomainPathResponse, false, pathString); } /**jsdoc * Go back to the previous location in your navigation history, if there is one. @@ -231,8 +229,7 @@ public slots: * location history is correctly maintained. */ void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { - handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); - } + handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } /**jsdoc * Go to the default "welcome" metaverse address. @@ -364,8 +361,7 @@ signals: * location.locationChangeRequired.connect(onLocationChangeRequired); */ void locationChangeRequired(const glm::vec3& newPosition, - bool hasOrientationChange, - const glm::quat& newOrientation, + bool hasOrientationChange, const glm::quat& newOrientation, bool shouldFaceLocation); /**jsdoc @@ -448,11 +444,8 @@ private: bool handleNetworkAddress(const QString& lookupString, LookupTrigger trigger, bool& hostChanged); void handlePath(const QString& path, LookupTrigger trigger, bool wasPathOnly = false); - bool handleViewpoint(const QString& viewpointString, - bool shouldFace, - LookupTrigger trigger, - bool definitelyPathOnly = false, - const QString& pathString = QString()); + bool handleViewpoint(const QString& viewpointString, bool shouldFace, LookupTrigger trigger, + bool definitelyPathOnly = false, const QString& pathString = QString()); bool handleUsername(const QString& lookupString); bool handleDomainID(const QString& host); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index f34a93de96..d02cb75e8b 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -14,6 +14,7 @@ #include #include +#include #include #include @@ -29,6 +30,8 @@ #include "UserActivityLogger.h" #include "NetworkLogging.h" +Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; + DomainHandler::DomainHandler(QObject* parent) : QObject(parent), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), @@ -485,14 +488,19 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); From 01594d7a52a07c4a7a2e5443fbdcca6b8328a18e Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 19 Sep 2018 10:17:14 -0700 Subject: [PATCH 16/63] fixing settings check in domain handler --- libraries/networking/src/DomainHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index d02cb75e8b..d2d576d8b7 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -30,8 +30,6 @@ #include "UserActivityLogger.h" #include "NetworkLogging.h" -Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; - DomainHandler::DomainHandler(QObject* parent) : QObject(parent), _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), @@ -488,6 +486,8 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer enableInterstitialMode{ "enableInterstitialMode", false }; + if (enableInterstitialMode.get()) { if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) { // ingest the error - this is a "hard" connection refusal. From a43799fffa0199352221535f8237a0f1c18fc3dc Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 19 Sep 2018 10:40:49 -0700 Subject: [PATCH 17/63] moving settings check within slotted functions --- interface/src/ConnectionMonitor.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 5eacc5259f..8f0c12634c 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -26,7 +26,6 @@ static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; -Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -41,6 +40,7 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); if (!domainHandler.isConnected()) { + Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; if (enableInterstitialMode.get()) { _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } else { @@ -50,6 +50,7 @@ void ConnectionMonitor::init() { connect(&_timer, &QTimer::timeout, this, [this]() { // set in a timeout error + Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; if (enableInterstitialMode.get()) { qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); @@ -61,6 +62,7 @@ void ConnectionMonitor::init() { } void ConnectionMonitor::startTimer() { + Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; if (enableInterstitialMode.get()) { _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } else { @@ -70,6 +72,7 @@ void ConnectionMonitor::startTimer() { void ConnectionMonitor::stopTimer() { _timer.stop(); + Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; if (!enableInterstitialMode.get()) { DependencyManager::get()->setDomainConnectionFailureVisibility(false); } From 144771b77b28918d544e2bb5bf13f2d8c73b803f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Sep 2018 11:35:17 -0700 Subject: [PATCH 18/63] Request JSON from server --- scripts/modules/appUi.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index c7f6cc5f39..10c3ed023c 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -197,11 +197,11 @@ function AppUi(properties) { } else { concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response)); currentDataPageToRetrieve++; - request({ uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback); + request({ json: true, uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback); } } - request({ uri: url }, requestCallback); + request({ json: true, uri: url }, requestCallback); }; // This won't do anything if there isn't a notification endpoint set From 7e796f7e7f268a8ab3fd49c4b1f3ef90e41c2447 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Wed, 19 Sep 2018 14:26:30 -0700 Subject: [PATCH 19/63] committing requested changes --- interface/src/ConnectionMonitor.cpp | 13 ++++--------- interface/src/ConnectionMonitor.h | 5 ++++- libraries/networking/src/DomainHandler.cpp | 7 ++----- libraries/networking/src/DomainHandler.h | 3 +++ 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index 8f0c12634c..ca90d039f4 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -18,7 +18,6 @@ #include #include #include -#include // Because the connection monitor is created at startup, the time we wait on initial load // should be longer to allow the application to initialize. @@ -40,8 +39,7 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; - if (enableInterstitialMode.get()) { + if (_enableInterstitialMode.get()) { _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } else { _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); @@ -50,8 +48,7 @@ void ConnectionMonitor::init() { connect(&_timer, &QTimer::timeout, this, [this]() { // set in a timeout error - Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; - if (enableInterstitialMode.get()) { + if (_enableInterstitialMode.get()) { qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); } else { @@ -62,8 +59,7 @@ void ConnectionMonitor::init() { } void ConnectionMonitor::startTimer() { - Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; - if (enableInterstitialMode.get()) { + if (_enableInterstitialMode.get()) { _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } else { _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); @@ -72,8 +68,7 @@ void ConnectionMonitor::startTimer() { void ConnectionMonitor::stopTimer() { _timer.stop(); - Setting::Handle enableInterstitialMode{ "enableInterstitialMode", false }; - if (!enableInterstitialMode.get()) { + if (!_enableInterstitialMode.get()) { DependencyManager::get()->setDomainConnectionFailureVisibility(false); } } diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h index 5e75e2618b..f0589a3b8c 100644 --- a/interface/src/ConnectionMonitor.h +++ b/interface/src/ConnectionMonitor.h @@ -15,6 +15,8 @@ #include #include +#include + class QUrl; class QString; @@ -32,6 +34,7 @@ private slots: private: QTimer _timer; + Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false }; }; -#endif // hifi_ConnectionMonitor_h \ No newline at end of file +#endif // hifi_ConnectionMonitor_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index d2d576d8b7..b2f118c5c8 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -14,7 +14,6 @@ #include #include -#include #include #include @@ -486,9 +485,8 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer enableInterstitialMode{ "enableInterstitialMode", false }; - if (enableInterstitialMode.get()) { + if (_enableInterstitialMode.get()) { if (reasonCode == ConnectionRefusedReason::ProtocolMismatch || reasonCode == ConnectionRefusedReason::NotAuthorized) { // ingest the error - this is a "hard" connection refusal. setRedirectErrorState(_errorDomainURL, (int)reasonCode); @@ -496,8 +494,7 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer #include +#include + #include "HifiSockAddr.h" #include "NetworkPeer.h" #include "NLPacket.h" @@ -221,6 +223,7 @@ private: NetworkPeer _icePeer; bool _isConnected { false }; bool _isInErrorState { false }; + Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; From 5bc7d944f961fb34e3d5b2ce1e35dd2e952ad5ea Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Wed, 19 Sep 2018 15:17:13 -0700 Subject: [PATCH 20/63] Remove whitespace --- .../hifi/commerce/purchases/PurchasedItem.qml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml index 032d9b0199..eeb9ac3c54 100644 --- a/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml +++ b/interface/resources/qml/hifi/commerce/purchases/PurchasedItem.qml @@ -59,7 +59,7 @@ Item { Connections { target: Commerce; - + onContentSetChanged: { if (contentSetHref === root.itemHref) { showConfirmation = true; @@ -135,7 +135,7 @@ Item { anchors.topMargin: 8; width: 30; height: width; - + HiFiGlyphs { id: closeContextMenuGlyph; text: hifi.glyphs.close; @@ -376,7 +376,7 @@ Item { } } } - + transform: Rotation { id: rotation; origin.x: flipable.width/2; @@ -509,7 +509,7 @@ Item { } verticalAlignment: Text.AlignTop; } - + HiFiGlyphs { id: statusIcon; text: { @@ -588,7 +588,7 @@ Item { border.width: 1; border.color: "#E2334D"; } - + HiFiGlyphs { id: contextMenuGlyph; text: hifi.glyphs.verticalEllipsis; @@ -615,7 +615,7 @@ Item { } } } - + Rectangle { id: rezzedNotifContainer; z: 998; @@ -663,13 +663,13 @@ Item { Tablet.playSound(TabletEnums.ButtonHover); } } - + onFocusChanged: { if (focus) { Tablet.playSound(TabletEnums.ButtonHover); } } - + onClicked: { Tablet.playSound(TabletEnums.ButtonClick); if (root.itemType === "contentSet") { @@ -775,7 +775,7 @@ Item { // Style color: hifi.colors.redAccent; horizontalAlignment: Text.AlignRight; - + MouseArea { anchors.fill: parent; hoverEnabled: true; From 66bf45c3ef1819b98ec880c4559592f94660a571 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Wed, 19 Sep 2018 15:18:18 -0700 Subject: [PATCH 21/63] Update location only if certificate id --- interface/src/commerce/QmlCommerce.cpp | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 10be228310..7c5df0f3e3 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -199,12 +199,18 @@ void QmlCommerce::transferAssetToUsername(const QString& username, } void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) { - auto ledger = DependencyManager::get(); - ledger->updateLocation(certificateID, DependencyManager::get()->getPlaceName(), true); + if (!certificateID.isEmpty()) { + auto ledger = DependencyManager::get(); + ledger->updateLocation( + certificateID, + DependencyManager::get()->getPlaceName(), + true); + } qApp->replaceDomainContent(itemHref); - QJsonObject messageProperties = { { "status", "SuccessfulRequestToReplaceContent" }, { "content_set_url", itemHref } }; + QJsonObject messageProperties = { + { "status", "SuccessfulRequestToReplaceContent" }, + { "content_set_url", itemHref } }; UserActivityLogger::getInstance().logAction("replace_domain_content", messageProperties); - emit contentSetChanged(itemHref); } From 38fd9523ee25081005753486dbd45d067080f358 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Wed, 19 Sep 2018 15:57:29 -0700 Subject: [PATCH 22/63] Attempt to load content set This does not result in the user seeing the content set. --- .../MarketplaceItemTester.qml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 8b0ecf3436..69e1a6122c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -55,6 +55,11 @@ Rectangle { } } + function isHttp(str) { + var httpPattern = /^http/i; + return httpPattern.test(str); + } + Component.onCompleted: { // On startup, list includes all tester-installed assets. addAllInstalledAppsToList(); @@ -79,9 +84,15 @@ Rectangle { switch(assetType) { case "application": Commerce.openApp(resource); - break + break; case "avatar": MyAvatar.useFullAvatarURL(resource); + break; + case "content set": + resource = isHttp(resource) ? resource : "file:///" + resource; + Commerce.replaceContentSet(resource, ""); + urlHandler.handleUrl("hifi://localhost/0,0,0"); + break; } // XXX support other resource types here. }, @@ -119,7 +130,7 @@ Rectangle { font.pointSize: 10 Layout.preferredWidth: root.width * .2 horizontalAlignment: Text.AlignHCenter - verticalAlignment: Test.AlignVCenter + verticalAlignment: Text.AlignVCenter } Repeater { From 5445d6f6704ab702947a4da45ddf2ec528e96bc5 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Wed, 19 Sep 2018 16:06:43 -0700 Subject: [PATCH 23/63] Switch to localhost before replacing content --- .../commerce/marketplaceItemTester/MarketplaceItemTester.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 69e1a6122c..23c3bc976f 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -90,8 +90,8 @@ Rectangle { break; case "content set": resource = isHttp(resource) ? resource : "file:///" + resource; - Commerce.replaceContentSet(resource, ""); urlHandler.handleUrl("hifi://localhost/0,0,0"); + Commerce.replaceContentSet(resource, ""); break; } // XXX support other resource types here. From ed84b49ece9fe29171d7c5864fb4405d09014797 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 19 Sep 2018 16:15:59 -0700 Subject: [PATCH 24/63] Small bugfixes and using original endpoints --- .../icons/tablet-icons/goto-a-msg.svg | 57 +++++++++++++++++++ .../{goto-msg.svg => goto-i-msg.svg} | 0 .../icons/tablet-icons/wallet-a-msg.svg | 6 ++ .../icons/tablet-icons/wallet-i-msg.svg | 16 ++++++ .../qml/hifi/commerce/purchases/Purchases.qml | 2 +- scripts/system/commerce/wallet.js | 2 +- scripts/system/marketplaces/marketplaces.js | 5 +- scripts/system/pal.js | 2 +- scripts/system/tablet-goto.js | 2 +- 9 files changed, 85 insertions(+), 7 deletions(-) create mode 100644 interface/resources/icons/tablet-icons/goto-a-msg.svg rename interface/resources/icons/tablet-icons/{goto-msg.svg => goto-i-msg.svg} (100%) create mode 100644 interface/resources/icons/tablet-icons/wallet-a-msg.svg create mode 100644 interface/resources/icons/tablet-icons/wallet-i-msg.svg diff --git a/interface/resources/icons/tablet-icons/goto-a-msg.svg b/interface/resources/icons/tablet-icons/goto-a-msg.svg new file mode 100644 index 0000000000..f1f611adb9 --- /dev/null +++ b/interface/resources/icons/tablet-icons/goto-a-msg.svg @@ -0,0 +1,57 @@ + + + +image/svg+xml + + \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/goto-msg.svg b/interface/resources/icons/tablet-icons/goto-i-msg.svg similarity index 100% rename from interface/resources/icons/tablet-icons/goto-msg.svg rename to interface/resources/icons/tablet-icons/goto-i-msg.svg diff --git a/interface/resources/icons/tablet-icons/wallet-a-msg.svg b/interface/resources/icons/tablet-icons/wallet-a-msg.svg new file mode 100644 index 0000000000..d51c3e99a2 --- /dev/null +++ b/interface/resources/icons/tablet-icons/wallet-a-msg.svg @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/interface/resources/icons/tablet-icons/wallet-i-msg.svg b/interface/resources/icons/tablet-icons/wallet-i-msg.svg new file mode 100644 index 0000000000..676f97a966 --- /dev/null +++ b/interface/resources/icons/tablet-icons/wallet-i-msg.svg @@ -0,0 +1,16 @@ + + + + + + + + + diff --git a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml index 3b8e2c0f4d..2435678e77 100644 --- a/interface/resources/qml/hifi/commerce/purchases/Purchases.qml +++ b/interface/resources/qml/hifi/commerce/purchases/Purchases.qml @@ -93,7 +93,7 @@ Rectangle { console.log("Failed to get Available Updates", result.data.message); } else { sendToScript({method: 'purchases_availableUpdatesReceived', numUpdates: result.data.updates.length }); - root.numUpdatesAvailable = result.data.updates.length; + root.numUpdatesAvailable = result.total_entries; } } diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 2f3f5c109d..c903080f62 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -601,7 +601,7 @@ function startup() { onOpened: walletOpened, onClosed: walletClosed, onMessage: fromQml, - notificationPollEndpoint: "/api/v1/notifications?source=commerce-history&per_page=10", + notificationPollEndpoint: "/api/v1/commerce/history?per_page=10", notificationPollTimeoutMs: 60000, notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 1bb0713f50..9bd3a9facf 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -137,7 +137,6 @@ function onUsernameChanged() { } } -var userHasUpdates = false; function sendCommerceSettings() { ui.sendToHtml({ type: "marketplaces", @@ -147,7 +146,7 @@ function sendCommerceSettings() { userIsLoggedIn: Account.loggedIn, walletNeedsSetup: Wallet.walletStatus === 1, metaverseServerURL: Account.metaverseServerURL, - messagesWaiting: userHasUpdates + messagesWaiting: shouldShowDot } }); } @@ -1077,7 +1076,7 @@ function startup() { home: MARKETPLACE_URL_INITIAL, onScreenChanged: onTabletScreenChanged, onMessage: onQmlMessageReceived, - notificationPollEndpoint: "/api/v1/notifications?source=commerce-available_updates&per_page=10", + notificationPollEndpoint: "/api/v1/commerce/available_updates?per_page=10", notificationPollTimeoutMs: 60000, notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 355ed0a504..b3499d759a 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -891,7 +891,7 @@ function startup() { onOpened: palOpened, onClosed: off, onMessage: fromQml, - notificationPollEndpoint: "/api/v1/notifications?source=users&filter=connections&per_page=10", + notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10", notificationPollTimeoutMs: 60000, notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 63bc8431e8..f37b13c406 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -113,7 +113,7 @@ function startup() { 'protocol=' + encodeURIComponent(Window.protocolSignature()), 'per_page=10' ]; - var endpoint = '/api/v1/notifications?source=user_stories?' + options.join('&'); + var endpoint = '/api/v1/user_stories?' + options.join('&'); ui = new AppUi({ buttonName: BUTTON_NAME, From afa8e44e66e359475ec07b68f1cc0f72121f0096 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 20 Sep 2018 10:53:32 -0700 Subject: [PATCH 25/63] Refactor --- .../marketplaceItemTester/MarketplaceItemTester.qml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 23c3bc976f..d582e9aa82 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -55,9 +55,9 @@ Rectangle { } } - function isHttp(str) { + function toUrl(resource) { var httpPattern = /^http/i; - return httpPattern.test(str); + return httpPattern.test(str) ? resource : "file:///" + resource; } Component.onCompleted: { @@ -89,9 +89,10 @@ Rectangle { MyAvatar.useFullAvatarURL(resource); break; case "content set": - resource = isHttp(resource) ? resource : "file:///" + resource; urlHandler.handleUrl("hifi://localhost/0,0,0"); - Commerce.replaceContentSet(resource, ""); + Commerce.replaceContentSet(toUrl(resource), ""); + break; + case "entity": break; } // XXX support other resource types here. From 87e91a44015710713c9bf5ed0a881df06b7be1fe Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 20 Sep 2018 13:36:50 -0700 Subject: [PATCH 26/63] GOTO doesn't care about SINCE; reduce data for PEOPLE --- scripts/system/pal.js | 16 +++++++--------- scripts/system/tablet-goto.js | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index b3499d759a..02bf92dcfe 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -841,15 +841,13 @@ function notificationPollCallback(connectionsArray) { return; } - if (user.online) { - newOnlineUsers++; - storedOnlineUsers[user.username] = user; + newOnlineUsers++; + storedOnlineUsers[user.username] = user; - if (!ui.isOpen && ui.notificationInitialCallbackMade) { - message = user.username + " is available in " + - user.location.root.name + "! Open PEOPLE to join them."; - ui.notificationDisplayBanner(message); - } + if (!ui.isOpen && ui.notificationInitialCallbackMade) { + message = user.username + " is available in " + + user.location.root.name + "! Open PEOPLE to join them."; + ui.notificationDisplayBanner(message); } }); var key; @@ -891,7 +889,7 @@ function startup() { onOpened: palOpened, onClosed: off, onMessage: fromQml, - notificationPollEndpoint: "/api/v1/users?filter=connections&per_page=10", + notificationPollEndpoint: "/api/v1/users?filter=connections&status=online&per_page=10", notificationPollTimeoutMs: 60000, notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index f37b13c406..fde558d728 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -125,7 +125,7 @@ function startup() { notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, - notificationPollCaresAboutSince: true + notificationPollCaresAboutSince: false }); } From ae4352f450ba93ecf448b1f74434ecc7614800c9 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 20 Sep 2018 13:55:22 -0700 Subject: [PATCH 27/63] Rez entity in marketplace item tester --- .../MarketplaceItemTester.qml | 16 +++++- scripts/system/marketplaces/marketplaces.js | 51 ++++++++++++++----- 2 files changed, 51 insertions(+), 16 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index d582e9aa82..9927c072db 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -23,7 +23,10 @@ import "../../../controls-uit" as HifiControlsUit Rectangle { id: root + property string installedApps + signal sendToScript(var message) + HifiStylesUit.HifiConstants { id: hifi } ListModel { id: resourceListModel } @@ -57,7 +60,11 @@ Rectangle { function toUrl(resource) { var httpPattern = /^http/i; - return httpPattern.test(str) ? resource : "file:///" + resource; + return httpPattern.test(resource) ? resource : "file:///" + resource; + } + + function rezEntity(itemHref, itemType) { + } Component.onCompleted: { @@ -92,7 +99,12 @@ Rectangle { urlHandler.handleUrl("hifi://localhost/0,0,0"); Commerce.replaceContentSet(toUrl(resource), ""); break; - case "entity": + case "entity or wearable": + print("going to rez " + toUrl(resource)); + sendToScript({ + method: 'tester_rezClicked', + itemHref: toUrl(resource), + itemType: "entity"}); break; } // XXX support other resource types here. diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 7b4f05193f..01c21044d2 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -21,20 +21,20 @@ var selectionDisplay = null; // for gridTool.js to ignore Script.include("/~/system/libraries/gridTool.js"); Script.include("/~/system/libraries/connectionUtils.js"); - var METAVERSE_SERVER_URL = Account.metaverseServerURL; + // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; + var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; + var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; + var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; + var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; + var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. - var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); - var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); - var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; - var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; - var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "commerce/inspectionCertificate/InspectionCertificate.qml"; + var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); + var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html"); + var METAVERSE_SERVER_URL = Account.metaverseServerURL; var REZZING_SOUND = SoundCache.getSound(Script.resolvePath("../assets/sounds/rezzing.wav")); - var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; - // var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png"; - // Event bridge messages. var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD"; var CLARA_IO_STATUS = "CLARA.IO STATUS"; @@ -786,7 +786,7 @@ var selectionDisplay = null; // for gridTool.js to ignore } sendAssetRecipient = null; } - + var savedDisablePreviewOptionLocked = false; var savedDisablePreviewOption = Menu.isOptionChecked("Disable Preview");; function maybeEnableHMDPreview() { @@ -855,6 +855,8 @@ var selectionDisplay = null; // for gridTool.js to ignore break; case 'checkout_rezClicked': case 'purchases_rezClicked': + case 'tester_rezClicked': + print("marketplace.js going to rez"); rezEntity(message.itemHref, message.itemType); break; case 'header_marketplaceImageClicked': @@ -1032,19 +1034,40 @@ var selectionDisplay = null; // for gridTool.js to ignore // value of "Home", "Web", "Menu", "QML", or "Closed". The "url" argument is only valid for Web and QML. var onWalletScreen = false; var onCommerceScreen = false; + var onMarketplaceItemTesterScreen = false; function onTabletScreenChanged(type, url) { onMarketplaceScreen = type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1; var onWalletScreenNow = url.indexOf(MARKETPLACE_WALLET_QML_PATH) !== -1; - var onCommerceScreenNow = type === "QML" && (url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || url === MARKETPLACE_PURCHASES_QML_PATH - || url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); + var onCommerceScreenNow = type === "QML" && ( + url.indexOf(MARKETPLACE_CHECKOUT_QML_PATH) !== -1 || + url === MARKETPLACE_PURCHASES_QML_PATH || + url.indexOf(MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH) !== -1); + var onMarketplaceItemTesterScreenNow = ( + url.indexOf(MARKETPLACE_ITEM_TESTER_QML_PATH) !== -1 || + url === MARKETPLACE_ITEM_TESTER_QML_PATH); - if ((!onWalletScreenNow && onWalletScreen) || (!onCommerceScreenNow && onCommerceScreen)) { // exiting wallet or commerce screen + if ((!onWalletScreenNow && onWalletScreen) || + (!onCommerceScreenNow && onCommerceScreen) || + (!onMarketplaceItemTesterScreenNow && onMarketplaceScreen) + ) { + // exiting wallet, commerce, or marketplace item tester screen maybeEnableHMDPreview(); } onCommerceScreen = onCommerceScreenNow; onWalletScreen = onWalletScreenNow; - wireEventBridge(onMarketplaceScreen || onCommerceScreen || onWalletScreen); + onMarketplaceItemTesterScreen = onMarketplaceItemTesterScreenNow; + + print("wire event bridge " + (onMarketplaceScreen || + onCommerceScreen || + onWalletScreen || + onMarketplaceItemTesterScreen)); + + wireEventBridge( + onMarketplaceScreen || + onCommerceScreen || + onWalletScreen || + onMarketplaceItemTesterScreen); if (url === MARKETPLACE_PURCHASES_QML_PATH) { sendToQml({ From 33c04631ea1b459924f8c2dfa15bafe491d88fbd Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 20 Sep 2018 14:34:55 -0700 Subject: [PATCH 28/63] Comments on Confluence doc --- scripts/system/commerce/wallet.js | 23 ++++++--------------- scripts/system/marketplaces/marketplaces.js | 8 +++---- scripts/system/pal.js | 4 ++-- scripts/system/tablet-goto.js | 11 +++++----- 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index c903080f62..1fce45bb6f 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -543,37 +543,26 @@ function notificationPollCallback(historyArray) { var message; if (!ui.notificationInitialCallbackMade) { message = "You have " + notificationCount + " unread wallet " + - "notification" + (notificationCount === 1 ? "" : "s") + "! Open WALLET to see all activity."; + "transaction" + (notificationCount === 1 ? "" : "s") + ". Open WALLET to see all activity."; ui.notificationDisplayBanner(message); } else { - var currentItemName, senderName; for (i = 0; i < someoneElsePurchasedArray.length; i++) { - currentItemName = (someoneElsePurchasedArray[i].message).match('(.*)')[1]; - message = "Someone purchased your item \"" + currentItemName + "\" from the Marketplace! " + + message = '"' + (someoneElsePurchasedArray[i].message) + '" ' + "Open WALLET to see all activity."; ui.notificationDisplayBanner(message); } for (i = 0; i < proofIssuedArray.length; i++) { - currentItemName = (proofIssuedArray[i].message).match('(.*)')[1]; - message = "You have been issued a proof for your Marketplace item \"" + currentItemName + "\"! " + + message = '"' + (proofIssuedArray[i].message) + '" ' + "Open WALLET to see all activity."; ui.notificationDisplayBanner(message); } for (i = 0; i < moneyReceivedArray.length; i++) { - senderName = moneyReceivedArray[i].sender_name; - if (senderName === "") { - senderName = "Someone"; - } - message = senderName + " sent you " + moneyReceivedArray[i].received_money + " HFC! " + + message = '"' + (moneyReceivedArray[i].message) + '" ' + "Open WALLET to see all activity."; ui.notificationDisplayBanner(message); } for (i = 0; i < giftReceivedArray.length; i++) { - senderName = giftReceivedArray[i].sender_name; - if (senderName === "") { - senderName = "Someone"; - } - message = senderName + " sent you a gift! " + + message = '"' + (giftReceivedArray[i].message) + '" ' + "Open WALLET to see all activity."; ui.notificationDisplayBanner(message); } @@ -602,7 +591,7 @@ function startup() { onClosed: walletClosed, onMessage: fromQml, notificationPollEndpoint: "/api/v1/commerce/history?per_page=10", - notificationPollTimeoutMs: 60000, + notificationPollTimeoutMs: 300000, notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 9bd3a9facf..d535884c94 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -1040,15 +1040,15 @@ function notificationPollCallback(updatesArray) { if (updatesArray.length > 0) { var message; if (!ui.notificationInitialCallbackMade) { - message = updatesArray.length + " of your purchased items have updates available! " + + message = updatesArray.length + " of your purchased items have updates available. " + "Open MARKET to update."; ui.notificationDisplayBanner(message); ui.notificationPollCaresAboutSince = true; } else { for (var i = 0; i < updatesArray.length; i++) { - message = "There's an update available for your version of \"" + - updatesArray[i].marketplace_item_name + "\"!" + + message = "Update available for \"" + + updatesArray[i].marketplace_item_name + "\"." + "Open MARKET to update."; ui.notificationDisplayBanner(message); } @@ -1077,7 +1077,7 @@ function startup() { onScreenChanged: onTabletScreenChanged, onMessage: onQmlMessageReceived, notificationPollEndpoint: "/api/v1/commerce/available_updates?per_page=10", - notificationPollTimeoutMs: 60000, + notificationPollTimeoutMs: 300000, notificationDataProcessPage: notificationDataProcessPage, notificationPollCallback: notificationPollCallback, notificationPollStopPaginatingConditionMet: isReturnedDataEmpty, diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 02bf92dcfe..a2ebae1a33 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -846,7 +846,7 @@ function notificationPollCallback(connectionsArray) { if (!ui.isOpen && ui.notificationInitialCallbackMade) { message = user.username + " is available in " + - user.location.root.name + "! Open PEOPLE to join them."; + user.location.root.name + ". Open PEOPLE to join them."; ui.notificationDisplayBanner(message); } }); @@ -870,7 +870,7 @@ function notificationPollCallback(connectionsArray) { if (newOnlineUsers > 0 && !ui.notificationInitialCallbackMade) { message = newOnlineUsers + " of your connections " + - (newOnlineUsers === 1 ? "is" : "are") + " online! Open PEOPLE to join them."; + (newOnlineUsers === 1 ? "is" : "are") + " available online. Open PEOPLE to join them."; ui.notificationDisplayBanner(message); } } diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index fde558d728..22a9752db8 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -57,15 +57,15 @@ function notificationPollCallback(userStoriesArray) { if (shouldNotifyIndividually) { message = storedAnnouncements[key].username + " says something is happening in " + - storedAnnouncements[key].place_name + "! Open GOTO to join them."; + storedAnnouncements[key].place_name + ". Open GOTO to join them."; ui.notificationDisplayBanner(message); } } else if (story.audience === "for_feed") { storedFeaturedStories[story.id] = story; if (shouldNotifyIndividually) { - message = storedFeaturedStories[key].username + " has invited you to an event in " + - storedFeaturedStories[key].place_name + "! Open GOTO to join them."; + message = storedFeaturedStories[key].username + " invites you to an event in " + + storedFeaturedStories[key].place_name + ". Open GOTO to join them."; ui.notificationDisplayBanner(message); } } @@ -91,8 +91,9 @@ function notificationPollCallback(userStoriesArray) { ui.messagesWaiting(shouldShowDot && !ui.isOpen); if (totalStories > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade) { - message = "You have " + totalStories + "event invitations pending! " + - "Open GOTO to see them."; + message = "There " + (totalStories === 1 ? "is " : "are ") + totalStories + "event" + + (totalStories === 1 ? "" : "s") + " to know about. " + + "Open GOTO to see " + (totalStories === 1 ? "it" : "them") + "."; ui.notificationDisplayBanner(message); } } From c1cc031bd0c7ddcadb5215bb50ec264f81d8411c Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 20 Sep 2018 14:50:27 -0700 Subject: [PATCH 29/63] Remove cruft and refactor --- .../MarketplaceItemTester.qml | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 9927c072db..37d2f2c170 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -45,8 +45,9 @@ Rectangle { function installResourceObj(resourceObj) { if ("application" == resourceObj["assetType"]) { Commerce.installApp(resourceObj["resource"]); + } else { + print("Cannot install resource object type " + resourceObj["assetType"]); } - // XXX support other asset types here } function addAllInstalledAppsToList() { @@ -63,14 +64,16 @@ Rectangle { return httpPattern.test(resource) ? resource : "file:///" + resource; } - function rezEntity(itemHref, itemType) { - + function rezEntity(resource, entityType) { + sendToScript({ + method: 'tester_rezClicked', + itemHref: toUrl(resource), + itemType: entityType}); } Component.onCompleted: { // On startup, list includes all tester-installed assets. addAllInstalledAppsToList(); - // XXX support other asset types here } ListView { @@ -99,21 +102,18 @@ Rectangle { urlHandler.handleUrl("hifi://localhost/0,0,0"); Commerce.replaceContentSet(toUrl(resource), ""); break; - case "entity or wearable": - print("going to rez " + toUrl(resource)); - sendToScript({ - method: 'tester_rezClicked', - itemHref: toUrl(resource), - itemType: "entity"}); + case "entity": + case "wearable": + rezEntity(resource, assetType); break; + default: + print("Marketplace item tester unsupported assetType " + assetType); } - // XXX support other resource types here. }, "trash": function(){ if ("application" == assetType) { Commerce.uninstallApp(resource); } - // XXX support other resource types here. resourceListModel.remove(index); } } From f9c25f2f32220e4f22969b9d65e8750f82774499 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 20 Sep 2018 16:36:06 -0700 Subject: [PATCH 30/63] Allow user to select asset type in marketplace item tester --- .../MarketplaceItemTester.qml | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 37d2f2c170..559b6cb29c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -12,6 +12,7 @@ // import QtQuick 2.5 +import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Dialogs 1.0 import QtQuick.Layouts 1.1 @@ -83,6 +84,7 @@ Rectangle { anchors.rightMargin: 12 model: resourceListModel spacing: 5 + interactive: false delegate: RowLayout { anchors.left: parent.left @@ -138,12 +140,25 @@ Rectangle { } } - Text { - text: assetType - font.pointSize: 10 + ComboBox { + id: comboBox + Layout.preferredWidth: root.width * .2 - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter + + model: [ + "application", + "avatar", + "content set", + "entity", + "wearable", + "unknown" + ] + + currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType) + + Component.onCompleted: { + onActivated.connect(function() { assetType = currentText; }); + } } Repeater { @@ -159,7 +174,7 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: { - actions[modelData.name](resource, assetType); + actions[modelData.name](resource, comboBox.currentText); } } } From 6284074ff96f076f9395fdc02a7e478b577ab29a Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 21 Sep 2018 10:46:49 -0700 Subject: [PATCH 31/63] Don't show banner when app is open but continue updating data --- scripts/modules/appUi.js | 8 +++++--- scripts/system/marketplaces/marketplaces.js | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 10c3ed023c..78e9ed38f0 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -107,7 +107,9 @@ function AppUi(properties) { that.notificationPollCaresAboutSince = false; that.notificationInitialCallbackMade = false; that.notificationDisplayBanner = function (message) { - Window.displayAnnouncement(message); + if (that.isOpen) { + Window.displayAnnouncement(message); + } }; // // END Notification Handling Defaults @@ -155,8 +157,8 @@ function AppUi(properties) { return; } - // User is "appearing offline", or is offline, or the app is open - if (GlobalServices.findableBy === "none" || Account.username === "" || that.isOpen) { + // User is "appearing offline" or is offline + if (GlobalServices.findableBy === "none" || Account.username === "") { that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); return; } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index d535884c94..3a239aa774 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -909,7 +909,7 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { break; case 'purchases_availableUpdatesReceived': shouldShowDot = message.numUpdates > 0; - ui.messagesWaiting(shouldShowDot); + ui.messagesWaiting(shouldShowDot && !ui.isOpen); break; case 'purchases_updateWearables': var currentlyWornWearables = []; @@ -1035,7 +1035,7 @@ function notificationDataProcessPage(data) { var shouldShowDot = false; function notificationPollCallback(updatesArray) { shouldShowDot = shouldShowDot || updatesArray.length > 0; - ui.messagesWaiting(shouldShowDot); + ui.messagesWaiting(shouldShowDot && !ui.isOpen); if (updatesArray.length > 0) { var message; From d4450ac7801b043cf885c77a61a1c6db5ce21a2a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Fri, 21 Sep 2018 15:59:00 -0700 Subject: [PATCH 32/63] fix equip and keeping new functionallity --- .../controllers/controllerModules/highlightNearbyEntities.js | 2 +- scripts/system/controllers/controllerModules/inEditMode.js | 2 +- scripts/system/controllers/controllerModules/inVREditMode.js | 2 +- .../controllers/controllerModules/nearActionGrabEntity.js | 2 +- .../controllers/controllerModules/nearGrabHyperLinkEntity.js | 2 +- .../controllers/controllerModules/nearParentGrabEntity.js | 2 +- scripts/system/controllers/controllerModules/nearTrigger.js | 2 +- .../controllers/controllerModules/webSurfaceLaserInput.js | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js index 3a33082f64..bc09ebee7a 100644 --- a/scripts/system/controllers/controllerModules/highlightNearbyEntities.js +++ b/scripts/system/controllers/controllerModules/highlightNearbyEntities.js @@ -37,7 +37,7 @@ this.highlightedEntities = []; this.parameters = dispatcherUtils.makeDispatcherModuleParameters( - 120, + 480, this.hand === dispatcherUtils.RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/inEditMode.js b/scripts/system/controllers/controllerModules/inEditMode.js index 2bdd89f141..d590545532 100644 --- a/scripts/system/controllers/controllerModules/inEditMode.js +++ b/scripts/system/controllers/controllerModules/inEditMode.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/utils.js"); this.reticleMaxY; this.parameters = makeDispatcherModuleParameters( - 200, + 160, this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], [], 100, diff --git a/scripts/system/controllers/controllerModules/inVREditMode.js b/scripts/system/controllers/controllerModules/inVREditMode.js index 02863cf935..7b78d5e1c4 100644 --- a/scripts/system/controllers/controllerModules/inVREditMode.js +++ b/scripts/system/controllers/controllerModules/inVREditMode.js @@ -21,7 +21,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.disableModules = false; var NO_HAND_LASER = -1; // Invalid hand parameter so that default laser is not displayed. this.parameters = makeDispatcherModuleParameters( - 240, // Not too high otherwise the tablet laser doesn't work. + 200, // Not too high otherwise the tablet laser doesn't work. this.hand === RIGHT_HAND ? ["rightHand", "rightHandEquip", "rightHandTrigger"] : ["leftHand", "leftHandEquip", "leftHandTrigger"], diff --git a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js index 27c1b458b8..a8de76aebd 100644 --- a/scripts/system/controllers/controllerModules/nearActionGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearActionGrabEntity.js @@ -26,7 +26,7 @@ Script.include("/~/system/libraries/cloneEntityUtils.js"); this.hapticTargetID = null; this.parameters = makeDispatcherModuleParameters( - 140, + 500, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js index 366fcd3032..962ae89bb9 100644 --- a/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js +++ b/scripts/system/controllers/controllerModules/nearGrabHyperLinkEntity.js @@ -21,7 +21,7 @@ this.hyperlink = ""; this.parameters = makeDispatcherModuleParameters( - 125, + 485, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js index f805dbf60e..cc88371441 100644 --- a/scripts/system/controllers/controllerModules/nearParentGrabEntity.js +++ b/scripts/system/controllers/controllerModules/nearParentGrabEntity.js @@ -57,7 +57,7 @@ Script.include("/~/system/libraries/controllers.js"); this.cloneAllowed = true; this.parameters = makeDispatcherModuleParameters( - 140, + 500, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/nearTrigger.js b/scripts/system/controllers/controllerModules/nearTrigger.js index f1126dedc3..6a9cd9fbcd 100644 --- a/scripts/system/controllers/controllerModules/nearTrigger.js +++ b/scripts/system/controllers/controllerModules/nearTrigger.js @@ -29,7 +29,7 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); this.startSent = false; this.parameters = makeDispatcherModuleParameters( - 120, + 480, this.hand === RIGHT_HAND ? ["rightHandTrigger", "rightHand"] : ["leftHandTrigger", "leftHand"], [], 100); diff --git a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js index 4e36355621..2412e2fa1c 100644 --- a/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js +++ b/scripts/system/controllers/controllerModules/webSurfaceLaserInput.js @@ -121,7 +121,7 @@ Script.include("/~/system/libraries/controllers.js"); controllerData.triggerValues[this.otherHand] <= TRIGGER_OFF_VALUE; var allowThisModule = !otherModuleRunning || isTriggerPressed; - if (allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) { + if ((allowThisModule && this.isPointingAtTriggerable(controllerData, isTriggerPressed, false)) && !this.grabModuleWantsNearbyOverlay(controllerData)) { this.updateAllwaysOn(); if (isTriggerPressed) { this.dominantHandOverride = true; // Override dominant hand. From b31837168d15d4e5090b41ff03eef443ca34c97f Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Mon, 24 Sep 2018 11:27:20 -0700 Subject: [PATCH 33/63] fix bad lock, optimize some operations, clarify comment --- interface/src/avatar/AvatarManager.cpp | 4 +++- interface/src/avatar/AvatarManager.h | 4 +++- libraries/avatars/src/AvatarHashMap.cpp | 2 +- libraries/avatars/src/AvatarHashMap.h | 3 +-- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index d92a5c81da..0fdd246d7b 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -464,6 +464,8 @@ void AvatarManager::clearOtherAvatars() { { QWriteLocker locker(&_hashLock); + removedAvatars.reserve(_avatarHash.size()); + auto avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast(avatarIterator.value()); @@ -484,7 +486,7 @@ void AvatarManager::clearOtherAvatars() { void AvatarManager::deleteAllAvatars() { assert(_avatarsToChangeInPhysics.empty()); - QReadLocker locker(&_hashLock); + QWriteLocker locker(&_hashLock); AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast(avatarIterator.value()); diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index 407b3c50de..9c4287728d 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -205,7 +205,9 @@ private: AvatarSharedPointer newSharedAvatar() override; - // must not be called while holding the hash lock + // called only from the AvatarHashMap thread - cannot be called while this thread holds the + // hash lock, since handleRemovedAvatar needs a write lock on the entity tree and the entity tree + // frequently grabs a read lock on the hash to get a given avatar by ID void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason) override; diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 2ee51cca17..01557e307e 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -73,7 +73,7 @@ std::vector AvatarReplicas::takeReplicas(const QUuid& paren if (it != _replicasMap.end()) { // take a copy of the replica shared pointers for this parent - replicas = it->second; + replicas.swap(it->second); // erase the replicas for this parent from our map _replicasMap.erase(it); diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 724dd1deac..c2cb448e52 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -180,8 +180,7 @@ protected: bool& isNew); virtual AvatarSharedPointer findAvatar(const QUuid& sessionUUID) const; // uses a QReadLocker on the hashLock virtual void removeAvatar(const QUuid& sessionUUID, KillAvatarReason removalReason = KillAvatarReason::NoReason); - - // must not be called while holding the hash lock + virtual void handleRemovedAvatar(const AvatarSharedPointer& removedAvatar, KillAvatarReason removalReason = KillAvatarReason::NoReason); AvatarHash _avatarHash; From c1d65f51188c371592a690e12b7006297a5e9518 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 24 Sep 2018 12:07:08 -0700 Subject: [PATCH 34/63] update avatar shape after it loads --- interface/src/avatar/AvatarManager.cpp | 6 ++++-- interface/src/avatar/OtherAvatar.cpp | 4 ++++ interface/src/avatar/OtherAvatar.h | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 1faf17ea9a..995faa9894 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -234,11 +234,13 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const SortableAvatar& sortData = *it; const auto avatar = std::static_pointer_cast(sortData.getAvatar()); - // TODO: to help us scale to more avatars it would be nice to not have to poll orb state here - // if the geometry is loaded then turn off the orb + // TODO: to help us scale to more avatars it would be nice to not have to poll this stuff every update if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); + if (avatar->needsPhysicsShapeUpdate()) { + _avatarsToChangeInPhysics.insert(avatar); + } } else { avatar->updateOrbPosition(); } diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 29fa98fd1d..4979ce3309 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -119,6 +119,10 @@ bool OtherAvatar::shouldBeInPhysicsSimulation() const { return (_workloadRegion < workload::Region::R3 && !isDead()); } +bool OtherAvatar::needsPhysicsShapeUpdate() const { + return (_motionState && (_motionState->getIncomingDirtyFlags() & (Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS))); +} + void OtherAvatar::rebuildCollisionShape() { if (_motionState) { _motionState->addDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS); diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index 94b98f2747..aaa12be43b 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -43,6 +43,7 @@ public: void setWorkloadRegion(uint8_t region); bool shouldBeInPhysicsSimulation() const; + bool needsPhysicsShapeUpdate() const; friend AvatarManager; From ad73cb3996c08a3bb43b80bac7c2c487727be26a Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 24 Sep 2018 14:02:35 -0700 Subject: [PATCH 35/63] fix avatar highlighting --- .../render-utils/src/HighlightEffect.cpp | 45 ++++++++++--------- libraries/render-utils/src/HighlightEffect.h | 2 - 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/libraries/render-utils/src/HighlightEffect.cpp b/libraries/render-utils/src/HighlightEffect.cpp index 11326b1120..bcac31dd5a 100644 --- a/libraries/render-utils/src/HighlightEffect.cpp +++ b/libraries/render-utils/src/HighlightEffect.cpp @@ -37,6 +37,8 @@ namespace gr { #define OUTLINE_STENCIL_MASK 1 +extern void initZPassPipelines(ShapePlumber& plumber, gpu::StatePointer state); + HighlightRessources::HighlightRessources() { } @@ -180,6 +182,7 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c auto maskPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder); auto maskSkinnedPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned()); + auto maskSkinnedDQPipeline = _shapePlumber->pickPipeline(args, defaultKeyBuilder.withSkinned().withDualQuatSkinned()); // Setup camera, projection and viewport for all items batch.setViewportTransform(args->_viewport); @@ -187,14 +190,17 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c batch.setProjectionJitter(jitter.x, jitter.y); batch.setViewTransform(viewMat); - std::vector skinnedShapeKeys{}; + std::vector skinnedShapeKeys; + std::vector skinnedDQShapeKeys; // Iterate through all inShapes and render the unskinned args->_shapePipeline = maskPipeline; batch.setPipeline(maskPipeline->pipeline); for (const auto& items : inShapes) { itemBounds.insert(itemBounds.end(), items.second.begin(), items.second.end()); - if (items.first.isSkinned()) { + if (items.first.isSkinned() && items.first.isDualQuatSkinned()) { + skinnedDQShapeKeys.push_back(items.first); + } else if (items.first.isSkinned()) { skinnedShapeKeys.push_back(items.first); } else { renderItems(renderContext, items.second); @@ -202,10 +208,21 @@ void DrawHighlightMask::run(const render::RenderContextPointer& renderContext, c } // Reiterate to render the skinned - args->_shapePipeline = maskSkinnedPipeline; - batch.setPipeline(maskSkinnedPipeline->pipeline); - for (const auto& key : skinnedShapeKeys) { - renderItems(renderContext, inShapes.at(key)); + if (skinnedShapeKeys.size() > 0) { + args->_shapePipeline = maskSkinnedPipeline; + batch.setPipeline(maskSkinnedPipeline->pipeline); + for (const auto& key : skinnedShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } + } + + // Reiterate to render the DQ skinned + if (skinnedDQShapeKeys.size() > 0) { + args->_shapePipeline = maskSkinnedDQPipeline; + batch.setPipeline(maskSkinnedDQPipeline->pipeline); + for (const auto& key : skinnedDQShapeKeys) { + renderItems(renderContext, inShapes.at(key)); + } } args->_shapePipeline = nullptr; @@ -488,7 +505,7 @@ void DrawHighlightTask::build(JobModel& task, const render::Varying& inputs, ren state->setDepthTest(true, true, gpu::LESS_EQUAL); state->setColorWriteMask(false, false, false, false); - initMaskPipelines(*shapePlumber, state); + initZPassPipelines(*shapePlumber, state); } auto sharedParameters = std::make_shared(); @@ -548,16 +565,4 @@ const render::Varying DrawHighlightTask::addSelectItemJobs(JobModel& task, const const auto selectedMetasAndOpaques = task.addJob("OpaqueSelection", selectMetaAndOpaqueInput); const auto selectItemInput = SelectItems::Inputs(transparents, selectedMetasAndOpaques, selectionName).asVarying(); return task.addJob("TransparentSelection", selectItemInput); -} - -void DrawHighlightTask::initMaskPipelines(render::ShapePlumber& shapePlumber, gpu::StatePointer state) { - gpu::ShaderPointer modelProgram = gpu::Shader::createProgram(shader::render_utils::program::model_shadow); - shapePlumber.addPipeline( - ShapeKey::Filter::Builder().withoutSkinned(), - modelProgram, state); - - gpu::ShaderPointer skinProgram = gpu::Shader::createProgram(shader::render_utils::program::skin_model_shadow); - shapePlumber.addPipeline( - ShapeKey::Filter::Builder().withSkinned(), - skinProgram, state); -} +} \ No newline at end of file diff --git a/libraries/render-utils/src/HighlightEffect.h b/libraries/render-utils/src/HighlightEffect.h index 64a97a549e..32668c1ab6 100644 --- a/libraries/render-utils/src/HighlightEffect.h +++ b/libraries/render-utils/src/HighlightEffect.h @@ -208,8 +208,6 @@ public: void build(JobModel& task, const render::Varying& inputs, render::Varying& outputs); private: - - static void initMaskPipelines(render::ShapePlumber& plumber, gpu::StatePointer state); static const render::Varying addSelectItemJobs(JobModel& task, const render::Varying& selectionName, const RenderFetchCullSortTask::BucketList& items); }; From a76c3f028d4bff2fd8ce1c0381de7d32169943dc Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Sep 2018 14:46:00 -0700 Subject: [PATCH 36/63] Small bugfixes --- scripts/modules/appUi.js | 3 ++- scripts/system/commerce/wallet.js | 4 +--- scripts/system/marketplaces/marketplaces.js | 11 +++++------ scripts/system/tablet-goto.js | 2 +- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 78e9ed38f0..db395ea778 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -107,7 +107,7 @@ function AppUi(properties) { that.notificationPollCaresAboutSince = false; that.notificationInitialCallbackMade = false; that.notificationDisplayBanner = function (message) { - if (that.isOpen) { + if (!that.isOpen) { Window.displayAnnouncement(message); } }; @@ -120,6 +120,7 @@ function AppUi(properties) { // Set isOpen, wireEventBridge, set buttonActive as appropriate, // and finally call onOpened() or onClosed() IFF defined. that.setCurrentVisibleScreenMetadata(type, url); + if (that.checkIsOpen(type, url)) { that.wireEventBridge(true); if (!that.isOpen) { diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 1fce45bb6f..639c8aa0b3 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -576,9 +576,7 @@ function isReturnedDataEmpty(data) { return historyArray.length === 0; } -// -// Manage the connection between the button and the window. -// + var BUTTON_NAME = "WALLET"; var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; var ui; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 3a239aa774..27fa929390 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -1040,15 +1040,16 @@ function notificationPollCallback(updatesArray) { if (updatesArray.length > 0) { var message; if (!ui.notificationInitialCallbackMade) { - message = updatesArray.length + " of your purchased items have updates available. " + - "Open MARKET to update."; + message = updatesArray.length + " of your purchased items " + + (updatesArray.length === 1 ? "has an update " : "have updates ") + + "available. Open MARKET to update."; ui.notificationDisplayBanner(message); ui.notificationPollCaresAboutSince = true; } else { for (var i = 0; i < updatesArray.length; i++) { message = "Update available for \"" + - updatesArray[i].marketplace_item_name + "\"." + + updatesArray[i].base_item_title + "\"." + "Open MARKET to update."; ui.notificationDisplayBanner(message); } @@ -1061,9 +1062,7 @@ function isReturnedDataEmpty(data) { return historyArray.length === 0; } -// -// Manage the connection between the button and the window. -// + var BUTTON_NAME = "MARKET"; var MARKETPLACE_URL = METAVERSE_SERVER_URL + "/marketplace"; var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page. diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 22a9752db8..0f032ae74d 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -91,7 +91,7 @@ function notificationPollCallback(userStoriesArray) { ui.messagesWaiting(shouldShowDot && !ui.isOpen); if (totalStories > 0 && !ui.isOpen && !ui.notificationInitialCallbackMade) { - message = "There " + (totalStories === 1 ? "is " : "are ") + totalStories + "event" + + message = "There " + (totalStories === 1 ? "is " : "are ") + totalStories + " event" + (totalStories === 1 ? "" : "s") + " to know about. " + "Open GOTO to see " + (totalStories === 1 ? "it" : "them") + "."; ui.notificationDisplayBanner(message); From 72fc686ec1c1172d72947864c948d169ecbb23fc Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Mon, 24 Sep 2018 14:52:13 -0700 Subject: [PATCH 37/63] Fix timer managmement during shutdown --- interface/src/Application.cpp | 6 +++-- libraries/audio-client/src/AudioClient.cpp | 27 ++++++++++++++++--- libraries/audio-client/src/AudioClient.h | 2 ++ .../audio-client/src/AudioPeakValues.cpp | 6 +++++ 4 files changed, 36 insertions(+), 5 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 46cebc1661..4eac967428 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1759,10 +1759,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Make sure we don't time out during slow operations at startup updateHeartbeat(); - QTimer* settingsTimer = new QTimer(); moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{ - connect(qApp, &Application::beforeAboutToQuit, [this, settingsTimer]{ + // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the + // receiver object, otherwise it will run on the application thread and trigger a warning + // about trying to kill the timer on the main thread. + connect(qApp, &Application::beforeAboutToQuit, settingsTimer, [this, settingsTimer]{ // Disconnect the signal from the save settings QObject::disconnect(settingsTimer, &QTimer::timeout, this, &Application::saveSettings); // Stop the settings timer diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 3a33eccc8a..6af74bc8e8 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -101,6 +101,13 @@ QList getAvailableDevices(QAudio::Mode mode) { // now called from a background thread, to keep blocking operations off the audio thread void AudioClient::checkDevices() { + // Make sure we're not shutting down + Lock timerMutex(_checkDevicesMutex); + // If we HAVE shut down after we were queued, but prior to execution, early exit + if (nullptr == _checkDevicesTimer) { + return; + } + auto inputDevices = getAvailableDevices(QAudio::AudioInput); auto outputDevices = getAvailableDevices(QAudio::AudioOutput); @@ -278,8 +285,8 @@ void AudioClient::customDeleter() { _shouldRestartInputSetup = false; #endif stop(); - _checkDevicesTimer->stop(); - _checkPeakValuesTimer->stop(); + //_checkDevicesTimer->stop(); + //_checkPeakValuesTimer->stop(); deleteLater(); } @@ -648,12 +655,26 @@ void AudioClient::start() { } void AudioClient::stop() { - qCDebug(audioclient) << "AudioClient::stop(), requesting switchInputToAudioDevice() to shut down"; switchInputToAudioDevice(QAudioDeviceInfo(), true); qCDebug(audioclient) << "AudioClient::stop(), requesting switchOutputToAudioDevice() to shut down"; switchOutputToAudioDevice(QAudioDeviceInfo(), true); + + // Stop triggering the checks + QObject::disconnect(_checkPeakValuesTimer, &QTimer::timeout, nullptr, nullptr); + QObject::disconnect(_checkDevicesTimer, &QTimer::timeout, nullptr, nullptr); + + // Destruction of the pointers will occur when the parent object (this) is destroyed) + { + Lock lock(_checkDevicesMutex); + _checkDevicesTimer = nullptr; + } + { + Lock lock(_checkPeakValuesMutex); + _checkPeakValuesTimer = nullptr; + } + #if defined(Q_OS_ANDROID) _checkInputTimer.stop(); disconnect(&_checkInputTimer, &QTimer::timeout, 0, 0); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index 4640d7c045..f5fee6184c 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -432,7 +432,9 @@ private: bool _shouldRestartInputSetup { true }; // Should we restart the input device because of an unintended stop? #endif + Mutex _checkDevicesMutex; QTimer* _checkDevicesTimer { nullptr }; + Mutex _checkPeakValuesMutex; QTimer* _checkPeakValuesTimer { nullptr }; bool _isRecording { false }; diff --git a/libraries/audio-client/src/AudioPeakValues.cpp b/libraries/audio-client/src/AudioPeakValues.cpp index 0b8921a117..a50567da7f 100644 --- a/libraries/audio-client/src/AudioPeakValues.cpp +++ b/libraries/audio-client/src/AudioPeakValues.cpp @@ -40,6 +40,12 @@ void release(IAudioClient* audioClient) { } void AudioClient::checkPeakValues() { + // Guard against running during shutdown + Lock timerMutex(_checkPeakValuesMutex); + if (nullptr == _checkPeakValuesTimer) { + return; + } + // prepare the windows environment CoInitialize(NULL); From 69d529936299331669c7913f5a4ca3f13b646be3 Mon Sep 17 00:00:00 2001 From: Wayne Chen Date: Mon, 24 Sep 2018 15:22:46 -0700 Subject: [PATCH 38/63] patching fix for previous commits --- interface/src/Application.cpp | 13 +++--- interface/src/Application.h | 2 +- interface/src/ConnectionMonitor.cpp | 22 +++------- interface/src/ConnectionMonitor.h | 5 +-- .../scripting/WindowScriptingInterface.cpp | 8 ++++ .../src/scripting/WindowScriptingInterface.h | 4 ++ libraries/networking/src/DomainHandler.cpp | 43 ++++++++++++------- libraries/networking/src/DomainHandler.h | 8 +++- 8 files changed, 61 insertions(+), 44 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 46cebc1661..a9aa9077b0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3498,13 +3498,14 @@ bool Application::isServerlessMode() const { } void Application::setIsInterstitialMode(bool interstitialMode) { - Settings settings; - bool enableInterstitial = settings.value("enableIntersitialMode", false).toBool(); - if (_interstitialMode != interstitialMode && enableInterstitial) { - _interstitialMode = interstitialMode; + bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); + if (enableInterstitial) { + if (_interstitialMode != interstitialMode) { + _interstitialMode = interstitialMode; - DependencyManager::get()->setAudioPaused(_interstitialMode); - DependencyManager::get()->setMyAvatarDataPacketsPaused(_interstitialMode); + DependencyManager::get()->setAudioPaused(_interstitialMode); + DependencyManager::get()->setMyAvatarDataPacketsPaused(_interstitialMode); + } } } diff --git a/interface/src/Application.h b/interface/src/Application.h index 3bebc60480..eedbdb7622 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -432,7 +432,7 @@ public slots: void setIsServerlessMode(bool serverlessDomain); void loadServerlessDomain(QUrl domainURL, bool errorDomain = false); - void setIsInterstitialMode(bool interstialMode); + void setIsInterstitialMode(bool interstitialMode); void updateVerboseLogging(); diff --git a/interface/src/ConnectionMonitor.cpp b/interface/src/ConnectionMonitor.cpp index ca90d039f4..e86061b090 100644 --- a/interface/src/ConnectionMonitor.cpp +++ b/interface/src/ConnectionMonitor.cpp @@ -23,8 +23,6 @@ // should be longer to allow the application to initialize. static const int ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 10000; static const int REDIRECT_AFTER_DISCONNECTED_FOR_X_MS = 5000; -static const int ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 10000; -static const int DISPLAY_AFTER_DISCONNECTED_FOR_X_MS = 5000; void ConnectionMonitor::init() { // Connect to domain disconnected message @@ -39,18 +37,15 @@ void ConnectionMonitor::init() { _timer.setSingleShot(true); if (!domainHandler.isConnected()) { - if (_enableInterstitialMode.get()) { - _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); - } else { - _timer.start(ON_INITIAL_LOAD_DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); - } + _timer.start(ON_INITIAL_LOAD_REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } connect(&_timer, &QTimer::timeout, this, [this]() { // set in a timeout error - if (_enableInterstitialMode.get()) { + bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); + if (enableInterstitial) { qDebug() << "ConnectionMonitor: Redirecting to 404 error domain"; - emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, 5); + emit setRedirectErrorState(REDIRECT_HIFI_ADDRESS, "", 5); } else { qDebug() << "ConnectionMonitor: Showing connection failure window"; DependencyManager::get()->setDomainConnectionFailureVisibility(true); @@ -59,16 +54,13 @@ void ConnectionMonitor::init() { } void ConnectionMonitor::startTimer() { - if (_enableInterstitialMode.get()) { - _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); - } else { - _timer.start(DISPLAY_AFTER_DISCONNECTED_FOR_X_MS); - } + _timer.start(REDIRECT_AFTER_DISCONNECTED_FOR_X_MS); } void ConnectionMonitor::stopTimer() { _timer.stop(); - if (!_enableInterstitialMode.get()) { + bool enableInterstitial = DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); + if (!enableInterstitial) { DependencyManager::get()->setDomainConnectionFailureVisibility(false); } } diff --git a/interface/src/ConnectionMonitor.h b/interface/src/ConnectionMonitor.h index f0589a3b8c..2fda6ef7cd 100644 --- a/interface/src/ConnectionMonitor.h +++ b/interface/src/ConnectionMonitor.h @@ -15,8 +15,6 @@ #include #include -#include - class QUrl; class QString; @@ -26,7 +24,7 @@ public: void init(); signals: - void setRedirectErrorState(QUrl errorURL, int reasonCode); + void setRedirectErrorState(QUrl errorURL, QString reasonMessage = "", int reasonCode = -1, const QString& extraInfo = ""); private slots: void startTimer(); @@ -34,7 +32,6 @@ private slots: private: QTimer _timer; - Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false }; }; #endif // hifi_ConnectionMonitor_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index e3ae65aee1..d4eb37e0aa 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -180,6 +180,14 @@ void WindowScriptingInterface::setPreviousBrowseAssetLocation(const QString& loc Setting::Handle(LAST_BROWSE_ASSETS_LOCATION_SETTING).set(location); } +bool WindowScriptingInterface::getInterstitialModeEnabled() const { + return DependencyManager::get()->getDomainHandler().getInterstitialModeEnabled(); +} + +void WindowScriptingInterface::setInterstitialModeEnabled(bool enableInterstitialMode) { + DependencyManager::get()->getDomainHandler().setInterstitialModeEnabled(enableInterstitialMode); +} + bool WindowScriptingInterface::isPointOnDesktopWindow(QVariant point) { auto offscreenUi = DependencyManager::get(); return offscreenUi->isPointOnDesktopWindow(point); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 3827406729..ddd7159f23 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -49,6 +49,7 @@ class WindowScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int innerHeight READ getInnerHeight) Q_PROPERTY(int x READ getX) Q_PROPERTY(int y READ getY) + Q_PROPERTY(bool interstitialModeEnabled READ getInterstitialModeEnabled WRITE setInterstitialModeEnabled) public: WindowScriptingInterface(); @@ -758,6 +759,9 @@ private: QString getPreviousBrowseAssetLocation() const; void setPreviousBrowseAssetLocation(const QString& location); + bool getInterstitialModeEnabled() const; + void setInterstitialModeEnabled(bool enableInterstitialMode); + void ensureReticleVisible() const; int createMessageBox(QString title, QString text, int buttons, int defaultButton); diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index b2f118c5c8..df34a1fb59 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -15,6 +15,10 @@ #include +#include + +#include + #include #include @@ -134,6 +138,18 @@ void DomainHandler::hardReset() { _pendingPath.clear(); } +bool DomainHandler::getInterstitialModeEnabled() const { + return _interstitialModeSettingLock.resultWithReadLock([&] { + return _enableInterstitialMode.get(); + }); +} + +void DomainHandler::setInterstitialModeEnabled(bool enableInterstitialMode) { + _interstitialModeSettingLock.withWriteLock([&] { + _enableInterstitialMode.set(enableInterstitialMode); + }); +} + void DomainHandler::setErrorDomainURL(const QUrl& url) { _errorDomainURL = url; return; @@ -340,11 +356,15 @@ void DomainHandler::loadedErrorDomain(std::map namedPaths) { DependencyManager::get()->goToViewpointForPath(viewpoint, QString()); } -void DomainHandler::setRedirectErrorState(QUrl errorUrl, int reasonCode) { - _errorDomainURL = errorUrl; +void DomainHandler::setRedirectErrorState(QUrl errorUrl, QString reasonMessage, int reasonCode, const QString& extraInfo) { _lastDomainConnectionError = reasonCode; - _isInErrorState = true; - emit redirectToErrorDomainURL(_errorDomainURL); + if (getInterstitialModeEnabled()) { + _errorDomainURL = errorUrl; + _isInErrorState = true; + emit redirectToErrorDomainURL(_errorDomainURL); + } else { + emit domainConnectionRefused(reasonMessage, reasonCode, extraInfo); + } } void DomainHandler::requestDomainSettings() { @@ -486,18 +506,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer(); diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 5fa920a554..e9ec20ba2e 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -19,6 +19,7 @@ #include #include +#include #include #include "HifiSockAddr.h" @@ -85,6 +86,8 @@ public: bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } + bool getInterstitialModeEnabled() const; + void setInterstitialModeEnabled(bool enableInterstitialMode); void connectedToServerless(std::map namedPaths); @@ -173,7 +176,7 @@ public slots: void processDomainServerConnectionDeniedPacket(QSharedPointer message); // sets domain handler in error state. - void setRedirectErrorState(QUrl errorUrl, int reasonCode); + void setRedirectErrorState(QUrl errorUrl, QString reasonMessage = "", int reason = -1, const QString& extraInfo = ""); bool isInErrorState() { return _isInErrorState; } @@ -223,10 +226,11 @@ private: NetworkPeer _icePeer; bool _isConnected { false }; bool _isInErrorState { false }; - Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false }; QJsonObject _settingsObject; QString _pendingPath; QTimer _settingsTimer; + mutable ReadWriteLockable _interstitialModeSettingLock; + Setting::Handle _enableInterstitialMode{ "enableInterstitialMode", false }; QSet _domainConnectionRefusals; bool _hasCheckedForAccessToken { false }; From a3f946dee14617e12b29ecaae590294013cd6005 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Mon, 24 Sep 2018 16:27:37 -0700 Subject: [PATCH 39/63] activate OtherAvarar in physics sim on position change --- interface/src/avatar/AvatarManager.cpp | 2 +- interface/src/avatar/OtherAvatar.cpp | 5 +++-- interface/src/avatar/OtherAvatar.h | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 995faa9894..de54402245 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -238,7 +238,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { if (avatar->getSkeletonModel()->isLoaded()) { // remove the orb if it is there avatar->removeOrb(); - if (avatar->needsPhysicsShapeUpdate()) { + if (avatar->needsPhysicsUpdate()) { _avatarsToChangeInPhysics.insert(avatar); } } else { diff --git a/interface/src/avatar/OtherAvatar.cpp b/interface/src/avatar/OtherAvatar.cpp index 4979ce3309..625998eb95 100644 --- a/interface/src/avatar/OtherAvatar.cpp +++ b/interface/src/avatar/OtherAvatar.cpp @@ -119,8 +119,9 @@ bool OtherAvatar::shouldBeInPhysicsSimulation() const { return (_workloadRegion < workload::Region::R3 && !isDead()); } -bool OtherAvatar::needsPhysicsShapeUpdate() const { - return (_motionState && (_motionState->getIncomingDirtyFlags() & (Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS))); +bool OtherAvatar::needsPhysicsUpdate() const { + constexpr uint32_t FLAGS_OF_INTEREST = Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS | Simulation::DIRTY_POSITION; + return (_motionState && (bool)(_motionState->getIncomingDirtyFlags() & FLAGS_OF_INTEREST)); } void OtherAvatar::rebuildCollisionShape() { diff --git a/interface/src/avatar/OtherAvatar.h b/interface/src/avatar/OtherAvatar.h index aaa12be43b..5b72815757 100644 --- a/interface/src/avatar/OtherAvatar.h +++ b/interface/src/avatar/OtherAvatar.h @@ -43,7 +43,7 @@ public: void setWorkloadRegion(uint8_t region); bool shouldBeInPhysicsSimulation() const; - bool needsPhysicsShapeUpdate() const; + bool needsPhysicsUpdate() const; friend AvatarManager; From 697f556a60211c48797f4bc5f685589e0a0ec229 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Mon, 24 Sep 2018 16:30:49 -0700 Subject: [PATCH 40/63] Persist list of marketplace items in test The list of resources between runs of the marketplace item tester now persist between runs of the tester. --- .../MarketplaceItemTester.qml | 54 +++++++++++++----- .../marketplaceItemTester/spinner.gif | Bin 0 -> 46135 bytes scripts/system/marketplaces/marketplaces.js | 52 ++++++++++++++++- 3 files changed, 91 insertions(+), 15 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 559b6cb29c..f2dbcc3f80 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -29,10 +29,35 @@ Rectangle { signal sendToScript(var message) HifiStylesUit.HifiConstants { id: hifi } - ListModel { id: resourceListModel } + ListModel { + id: resourceListModel + property var nextId: 0 + } color: hifi.colors.white + AnimatedImage { + id: spinner; + source: "spinner.gif" + width: 74; + height: width; + anchors.verticalCenter: parent.verticalCenter; + anchors.horizontalCenter: parent.horizontalCenter; + } + + function fromScript(message) { + switch (message.method) { + case "newResourceObjectInTest": + var resourceObject = message.resourceObject; + resourceListModel.append(resourceObject); + spinner.visible = false; + break; + case "marketplaceTestBackendIsAlive": + spinner.visible = false; + break; + } + } + function buildResourceObj(resource) { resource = resource.trim(); var assetType = (resource.match(/\.app\.json$/) ? "application" : @@ -40,14 +65,14 @@ Rectangle { resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); - return { "resource": resource, "assetType": assetType }; + return { "id": resourceListModel.nextId++, + "resource": resource, + "assetType": assetType }; } function installResourceObj(resourceObj) { if ("application" == resourceObj["assetType"]) { Commerce.installApp(resourceObj["resource"]); - } else { - print("Cannot install resource object type " + resourceObj["assetType"]); } } @@ -72,11 +97,6 @@ Rectangle { itemType: entityType}); } - Component.onCompleted: { - // On startup, list includes all tester-installed assets. - addAllInstalledAppsToList(); - } - ListView { anchors.fill: parent anchors.leftMargin: 12 @@ -157,7 +177,13 @@ Rectangle { currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType) Component.onCompleted: { - onActivated.connect(function() { assetType = currentText; }); + onCurrentIndexChanged.connect(function() { + assetType = model[currentIndex]; + sendToScript({ + method: 'tester_updateResourceObjectAssetType', + objectId: resourceListModel.get(index)["id"], + assetType: assetType }); + }); } } @@ -220,17 +246,17 @@ Rectangle { // ahead as though we are sure the present signal is one // we expect. if ("load file" == currentAction) { - print("disconnecting load file"); Window.browseChanged.disconnect(onResourceSelected); } else if ("load url" == currentAction) { - print("disconnecting load url"); Window.promptTextChanged.disconnect(onResourceSelected); } if (resource) { var resourceObj = buildResourceObj(resource); installResourceObj(resourceObj); - resourceListModel.append(resourceObj); - } + sendToScript({ + method: 'tester_newResourceObject', + resourceObject: resourceObj }); + } } Repeater { diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif new file mode 100644 index 0000000000000000000000000000000000000000..00f75ae62fa59b28a7813ca00c4185390e3e51da GIT binary patch literal 46135 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 V-}`%i@9+J+zxVh4-rv9A{u}xRWtji~ literal 0 HcmV?d00001 diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 01c21044d2..a066143d45 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -800,6 +800,18 @@ var selectionDisplay = null; // for gridTool.js to ignore }, 150); } + function signalNewResourceObjectInTest(resourceObject) { + sendToQml({ + method: "newResourceObjectInTest", + resourceObject: resourceObject }); + } + + var resourceObjectsInTest = []; + function storeResourceObjectInTest(resourceObject) { + resourceObjectsInTest.push(resourceObject); + signalNewResourceObjectInTest(resourceObject); + } + // Function Name: fromQml() // // Description: @@ -856,9 +868,22 @@ var selectionDisplay = null; // for gridTool.js to ignore case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': - print("marketplace.js going to rez"); rezEntity(message.itemHref, message.itemType); break; + case 'tester_newResourceObject': + storeResourceObjectInTest(message.resourceObject); + break; + case 'tester_updateResourceObjectAssetType': + var objectId = message.objectId; + for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) { + if (i in resourceObjectsInTest && + objectId === resourceObjectsInTest[i]["id"] + ) { + resourceObjectsInTest[i]["assetType"] = message.assetType; + break; + } + } + break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': tablet.gotoWebScreen(message.referrerURL, MARKETPLACES_INJECT_SCRIPT_URL); @@ -1027,6 +1052,22 @@ var selectionDisplay = null; // for gridTool.js to ignore } } + function pushResourceObjectsInTest() { + var isQmlSignaled = false; + for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) { + if (i in resourceObjectsInTest) { + signalNewResourceObjectInTest(resourceObjectsInTest[i]); + isQmlSignaled = true; + } + } + // Be sure that the QML has heard from us, at least so that it + // can indicate to the user that all of the resoruce objects in + // test have been transmitted to it. + if (!isQmlSignaled) { + sendToQml({ method: "marketplaceTestBackendIsAlive" }); + } + } + // Function Name: onTabletScreenChanged() // // Description: @@ -1070,6 +1111,9 @@ var selectionDisplay = null; // for gridTool.js to ignore onMarketplaceItemTesterScreen); if (url === MARKETPLACE_PURCHASES_QML_PATH) { + // FIXME: There is a race condition here. The event bridge + // may not be up yet. Suggest Script.setTimeout(..., 750) to + // help avoid the condition. sendToQml({ method: 'updatePurchases', referrerURL: referrerURL, @@ -1103,6 +1147,12 @@ var selectionDisplay = null; // for gridTool.js to ignore method: 'inspectionCertificate_resetCert' }); } + + if (onMarketplaceItemTesterScreen) { + // Why? The QML event bridge, wired above, needs time to + // come up. + Script.setTimeout(pushResourceObjectsInTest, 750); + } } // From 7f189e4d10f87f7f6ae4bdbc259683928356a41b Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 24 Sep 2018 17:15:56 -0700 Subject: [PATCH 41/63] Make visualPickResult used by Pointers a private copy --- libraries/pointers/src/Pointer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 031baece5f..bdf1250a8d 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -68,7 +68,7 @@ void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { auto pickResult = getPrevPickResult(); - auto visualPickResult = getVisualPickResult(pickResult); + auto visualPickResult = getVisualPickResult(std::make_shared(*pickResult.get())); updateVisuals(visualPickResult); generatePointerEvents(pointerID, visualPickResult); }); From be304ea97585de71f2217029cc72487b3ec98c13 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Sep 2018 17:32:30 -0700 Subject: [PATCH 42/63] Small bugfixes again --- scripts/modules/appUi.js | 2 +- scripts/system/tablet-goto.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index db395ea778..12ba115815 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -170,7 +170,7 @@ function AppUi(properties) { var currentTimestamp = new Date().getTime(); var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp); if (that.notificationPollCaresAboutSince) { - url = url + "&since=" + lastPollTimestamp; + url = url + "&since=" + lastPollTimestamp/1000; } Settings.setValue(settingsKey, currentTimestamp); diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 0f032ae74d..902e1b7fef 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -18,7 +18,8 @@ var AppUi = Script.require('appUi'); function gotoOpened() { - ui.messagesWaiting(false); + shouldShowDot = false; + ui.messagesWaiting(shouldShowDot); } function notificationDataProcessPage(data) { From 5dbebd4aae4652fa0109d3da9e11f93cf85151a1 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Mon, 24 Sep 2018 17:36:24 -0700 Subject: [PATCH 43/63] Add comment explaining why visualPickResult is a copy --- libraries/pointers/src/Pointer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index bdf1250a8d..852a83c192 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -68,6 +68,7 @@ void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { auto pickResult = getPrevPickResult(); + // Pointer needs its own PickResult object so it doesn't modify the cached pick result auto visualPickResult = getVisualPickResult(std::make_shared(*pickResult.get())); updateVisuals(visualPickResult); generatePointerEvents(pointerID, visualPickResult); From 1ac2800e181b87ff229ed5d3fe88958ea04b6b69 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 24 Sep 2018 17:49:52 -0700 Subject: [PATCH 44/63] Fix wallet dot logic --- scripts/system/commerce/wallet.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 639c8aa0b3..a9ebe37feb 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -488,7 +488,8 @@ function walletOpened() { Controller.mouseMoveEvent.connect(handleMouseMoveEvent); triggerMapping.enable(); triggerPressMapping.enable(); - ui.messagesWaiting(false); + shouldShowDot = false; + ui.messagesWaiting(shouldShowDot); } function walletClosed() { From 0a43c17ce0d97458e57b972a92107806d5837be6 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Mon, 24 Sep 2018 21:15:08 -0700 Subject: [PATCH 45/63] Use entity-specific glyphs in marketplace item tester --- .../MarketplaceItemTester.qml | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index f2dbcc3f80..40e4f55f2c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -188,13 +188,22 @@ Rectangle { } Repeater { - model: [ - { "name": "forward", "glyph": hifi.glyphs.forward, "size": 30 }, - { "name": "trash", "glyph": hifi.glyphs.trash, "size": 22} - ] + model: [ "forward", "trash" ] + HifiStylesUit.HiFiGlyphs { - text: modelData.glyph - size: modelData.size + property var glyphs: { + "application": hifi.glyphs.install, + "avatar": hifi.glyphs.avatar, + "content set": hifi.glyphs.globe, + "entity": hifi.glyphs.wand, + "trash": hifi.glyphs.trash, + "unknown": hifi.glyphs.circleSlash, + "wearable": hifi.glyphs.hat, + } + text: (("trash" == modelData) ? + glyphs.trash : + glyphs[comboBox.model[comboBox.currentIndex]]) + size: ("trash" == modelData) ? 22 : 30 color: hifi.colors.black horizontalAlignment: Text.AlignHCenter MouseArea { From ca1da9c830d8c3bab0a5ea65dda5cc4c3d1c3f32 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Mon, 24 Sep 2018 23:07:44 -0700 Subject: [PATCH 46/63] Implement delete-from-list functionality in marketplace item tester --- .../MarketplaceItemTester.qml | 8 +++++-- scripts/system/marketplaces/marketplaces.js | 24 +++++++------------ 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 0a852780b4..c962c84c31 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -136,6 +136,9 @@ Rectangle { if ("application" == assetType) { Commerce.uninstallApp(resource); } + sendToScript({ + method: "tester_deleteResourceObject", + objectId: resourceListModel.get(index).id}); resourceListModel.remove(index); } } @@ -174,13 +177,14 @@ Rectangle { "unknown" ] - currentIndex: ("entity or wearable" == assetType) ? model.indexOf("unknown") : model.indexOf(assetType) + currentIndex: (("entity or wearable" == assetType) ? + model.indexOf("unknown") : model.indexOf(assetType)) Component.onCompleted: { onCurrentIndexChanged.connect(function() { assetType = model[currentIndex]; sendToScript({ - method: 'tester_updateResourceObjectAssetType', + method: "tester_updateResourceObjectAssetType", objectId: resourceListModel.get(index)["id"], assetType: assetType }); }); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index c279e76b16..aba4ba5f7d 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -770,18 +770,13 @@ function maybeEnableHMDPreview() { }, UI_FADE_TIMEOUT_MS); } +var resourceObjectsInTest = []; function signalNewResourceObjectInTest(resourceObject) { ui.tablet.sendToQml({ method: "newResourceObjectInTest", resourceObject: resourceObject }); } -var resourceObjectsInTest = []; -function storeResourceObjectInTest(resourceObject) { - resourceObjectsInTest.push(resourceObject); - signalNewResourceObjectInTest(resourceObject); -} - var onQmlMessageReceived = function onQmlMessageReceived(message) { if (message.messageSrc === "HTML") { return; @@ -835,18 +830,15 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { rezEntity(message.itemHref, message.itemType); break; case 'tester_newResourceObject': - storeResourceObjectInTest(message.resourceObject); + var resourceObject = message.resourceObject; + resourceObjectsInTest[resourceObject.id] = resourceObject; + signalNewResourceObjectInTest(resourceObject); break; case 'tester_updateResourceObjectAssetType': - var objectId = message.objectId; - for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) { - if (i in resourceObjectsInTest && - objectId === resourceObjectsInTest[i]["id"] - ) { - resourceObjectsInTest[i]["assetType"] = message.assetType; - break; - } - } + resourceObjectsInTest[message.objectId].assetType = message.assetType; + break; + case 'tester_deleteResourceObject': + delete resourceObjectsInTest[message.objectId]; break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': From ff83713365b4b4fa5c69f733506d656f2745a826 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Mon, 24 Sep 2018 23:57:48 -0700 Subject: [PATCH 47/63] Use persistent object id counter --- .../MarketplaceItemTester.qml | 17 +++++++-------- scripts/system/marketplaces/marketplaces.js | 21 ++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index c962c84c31..a0ccf91baa 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -26,13 +26,11 @@ Rectangle { id: root property string installedApps + property var nextResourceObjectId: 0 signal sendToScript(var message) HifiStylesUit.HifiConstants { id: hifi } - ListModel { - id: resourceListModel - property var nextId: 0 - } + ListModel { id: resourceListModel } color: hifi.colors.white @@ -52,7 +50,8 @@ Rectangle { resourceListModel.append(resourceObject); spinner.visible = false; break; - case "marketplaceTestBackendIsAlive": + case "nextObjectIdInTest": + nextResourceObjectId = message.id; spinner.visible = false; break; } @@ -65,14 +64,14 @@ Rectangle { resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); - return { "id": resourceListModel.nextId++, + return { "id": nextResourceObjectId++, "resource": resource, "assetType": assetType }; } function installResourceObj(resourceObj) { - if ("application" == resourceObj["assetType"]) { - Commerce.installApp(resourceObj["resource"]); + if ("application" == resourceObj.assetType) { + Commerce.installApp(resourceObj.resource); } } @@ -269,7 +268,7 @@ Rectangle { sendToScript({ method: 'tester_newResourceObject', resourceObject: resourceObj }); - } + } } Repeater { diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index aba4ba5f7d..2e0c05be43 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -980,19 +980,16 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { }; function pushResourceObjectsInTest() { - var isQmlSignaled = false; - for (var i = 0, size = resourceObjectsInTest.length; i < size; ++i) { - if (i in resourceObjectsInTest) { - signalNewResourceObjectInTest(resourceObjectsInTest[i]); - isQmlSignaled = true; - } - } - // Be sure that the QML has heard from us, at least so that it - // can indicate to the user that all of the resoruce objects in - // test have been transmitted to it. - if (!isQmlSignaled) { - ui.tablet.sendToQml({ method: "marketplaceTestBackendIsAlive" }); + var maxObjectId = -1; + for (var objectId in resourceObjectsInTest) { + signalNewResourceObjectInTest(resourceObjectsInTest[objectId]); + maxObjectId = (maxObjectId < objectId) ? parseInt(objectId) : maxObjectId; } + // N.B. Thinking about removing the following sendToQml? Be sure + // that the marketplace item tester QML has heard from us, at least + // so that it can indicate to the user that all of the resoruce + // objects in test have been transmitted to it. + ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxObjectId + 1 }); } // Function Name: onTabletScreenChanged() From eca31e7a994a5a0c7841e82eda987f99f972ba5c Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 25 Sep 2018 09:20:57 -0700 Subject: [PATCH 48/63] Fix instantiating abstract class when creating visual pick result for pointers --- interface/src/raypick/LaserPointer.cpp | 5 +++++ interface/src/raypick/LaserPointer.h | 2 ++ interface/src/raypick/ParabolaPointer.cpp | 5 +++++ interface/src/raypick/ParabolaPointer.h | 2 ++ interface/src/raypick/StylusPointer.cpp | 5 +++++ interface/src/raypick/StylusPointer.h | 1 + libraries/pointers/src/Pointer.cpp | 2 +- libraries/pointers/src/Pointer.h | 1 + 8 files changed, 22 insertions(+), 1 deletion(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 5fbe3a90b5..64faf5f9bf 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -35,6 +35,11 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } } +PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto rayPickResult = std::static_pointer_cast(pickResult); + return std::make_shared(*rayPickResult.get()); +} + QVariantMap LaserPointer::toVariantMap() const { QVariantMap qVariantMap; diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index c0ac3259d9..b391f60f85 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -47,6 +47,8 @@ public: static std::shared_ptr buildRenderState(const QVariantMap& propMap); protected: + PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override; + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 888b3ddbe8..e7f54d2e97 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -30,6 +30,11 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& { } +PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto stylusPickResult = std::static_pointer_cast(pickResult); + return std::make_shared(*stylusPickResult.get()); +} + void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { auto renderState = std::static_pointer_cast(_renderStates[state]); if (renderState) { diff --git a/interface/src/raypick/ParabolaPointer.h b/interface/src/raypick/ParabolaPointer.h index 526abe3b0d..8fb864c07b 100644 --- a/interface/src/raypick/ParabolaPointer.h +++ b/interface/src/raypick/ParabolaPointer.h @@ -102,6 +102,8 @@ public: static std::shared_ptr buildRenderState(const QVariantMap& propMap); protected: + virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override; + void editRenderStatePath(const std::string& state, const QVariant& pathProps) override; glm::vec3 getPickOrigin(const PickResultPointer& pickResult) const override; diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 06e3e52d21..7f05a706a4 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -147,6 +147,11 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { return false; } +PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + auto stylusPickResult = std::static_pointer_cast(pickResult); + return std::make_shared(*stylusPickResult.get()); +} + Pointer::PickedObject StylusPointer::getHoveredObject(const PickResultPointer& pickResult) { auto stylusPickResult = std::static_pointer_cast(pickResult); if (!stylusPickResult) { diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h index 4095acb529..ff60fd78e5 100644 --- a/interface/src/raypick/StylusPointer.h +++ b/interface/src/raypick/StylusPointer.h @@ -42,6 +42,7 @@ protected: Buttons getPressedButtons(const PickResultPointer& pickResult) override; bool shouldHover(const PickResultPointer& pickResult) override; bool shouldTrigger(const PickResultPointer& pickResult) override; + virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const override; PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult, const std::string& button = "", bool hover = true) override; diff --git a/libraries/pointers/src/Pointer.cpp b/libraries/pointers/src/Pointer.cpp index 852a83c192..26460cbdd7 100644 --- a/libraries/pointers/src/Pointer.cpp +++ b/libraries/pointers/src/Pointer.cpp @@ -69,7 +69,7 @@ void Pointer::update(unsigned int pointerID) { withReadLock([&] { auto pickResult = getPrevPickResult(); // Pointer needs its own PickResult object so it doesn't modify the cached pick result - auto visualPickResult = getVisualPickResult(std::make_shared(*pickResult.get())); + auto visualPickResult = getVisualPickResult(getPickResultCopy(pickResult)); updateVisuals(visualPickResult); generatePointerEvents(pointerID, visualPickResult); }); diff --git a/libraries/pointers/src/Pointer.h b/libraries/pointers/src/Pointer.h index 4264a60079..173163374f 100644 --- a/libraries/pointers/src/Pointer.h +++ b/libraries/pointers/src/Pointer.h @@ -91,6 +91,7 @@ protected: virtual bool shouldHover(const PickResultPointer& pickResult) { return true; } virtual bool shouldTrigger(const PickResultPointer& pickResult) { return true; } + virtual PickResultPointer getPickResultCopy(const PickResultPointer& pickResult) const = 0; virtual PickResultPointer getVisualPickResult(const PickResultPointer& pickResult) { return pickResult; }; static const float POINTER_MOVE_DELAY; From 660bdf36d01b83996aacc93e9997eac41b70f463 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 25 Sep 2018 09:56:11 -0700 Subject: [PATCH 49/63] Check if pick result is null when building visual pick result --- interface/src/raypick/LaserPointer.cpp | 3 +++ interface/src/raypick/ParabolaPointer.cpp | 3 +++ interface/src/raypick/StylusPointer.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 64faf5f9bf..79bca0449f 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -36,6 +36,9 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + if (!pickResult) { + std::make_shared(); + } auto rayPickResult = std::static_pointer_cast(pickResult); return std::make_shared(*rayPickResult.get()); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 4614b81cbb..ec4222d5f6 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -31,6 +31,9 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& } PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + if (!pickResult) { + std::make_shared(); + } auto stylusPickResult = std::static_pointer_cast(pickResult); return std::make_shared(*stylusPickResult.get()); } diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 7f05a706a4..2742c68d1d 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -148,6 +148,9 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { } PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { + if (!pickResult) { + std::make_shared(); + } auto stylusPickResult = std::static_pointer_cast(pickResult); return std::make_shared(*stylusPickResult.get()); } From 6e83e96d1cff5c000fe30f22e052ec4815749c60 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 25 Sep 2018 12:05:47 -0700 Subject: [PATCH 50/63] Check type in Pointer::getPickResultCopy --- interface/src/raypick/LaserPointer.cpp | 4 ++-- interface/src/raypick/ParabolaPointer.cpp | 6 +++--- interface/src/raypick/StylusPointer.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 79bca0449f..577978cc88 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -36,10 +36,10 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& } PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { - if (!pickResult) { + auto rayPickResult = std::dynamic_pointer_cast(pickResult); + if (!rayPickResult) { std::make_shared(); } - auto rayPickResult = std::static_pointer_cast(pickResult); return std::make_shared(*rayPickResult.get()); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index ec4222d5f6..92b82fff7f 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -31,11 +31,11 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& } PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { - if (!pickResult) { + auto parabolaPickResult = std::dynamic_pointer_cast(pickResult); + if (!parabolaPickResult) { std::make_shared(); } - auto stylusPickResult = std::static_pointer_cast(pickResult); - return std::make_shared(*stylusPickResult.get()); + return std::make_shared(*parabolaPickResult.get()); } void ParabolaPointer::editRenderStatePath(const std::string& state, const QVariant& pathProps) { diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 2742c68d1d..0b44b2705d 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -148,10 +148,10 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { } PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { - if (!pickResult) { + auto stylusPickResult = std::dynamic_pointer_cast(pickResult); + if (!stylusPickResult) { std::make_shared(); } - auto stylusPickResult = std::static_pointer_cast(pickResult); return std::make_shared(*stylusPickResult.get()); } From 21cd3948aeaaa721b8c56c8056609fe18e377309 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Tue, 25 Sep 2018 11:58:58 -0700 Subject: [PATCH 51/63] improve blender queuing --- libraries/render-utils/src/Model.cpp | 352 ++++++++++++++------------- libraries/render-utils/src/Model.h | 3 +- 2 files changed, 179 insertions(+), 176 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index ab6507b29c..be78a69b4c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -1281,92 +1281,6 @@ QStringList Model::getJointNames() const { return isActive() ? getFBXGeometry().getJointNames() : QStringList(); } -class Blender : public QRunnable { -public: - - Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& blendshapeCoefficients); - - virtual void run() override; - -private: - - ModelPointer _model; - int _blendNumber; - Geometry::WeakPointer _geometry; - QVector _blendshapeCoefficients; -}; - -Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& blendshapeCoefficients) : - _model(model), - _blendNumber(blendNumber), - _geometry(geometry), - _blendshapeCoefficients(blendshapeCoefficients) { -} - -void Blender::run() { - QVector vertices; - QVector normalsAndTangents; - if (_model && _model->isLoaded()) { - DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); - int offset = 0; - int normalsAndTangentsOffset = 0; - auto meshes = _model->getFBXGeometry().meshes; - int meshIndex = 0; - foreach (const FBXMesh& mesh, meshes) { - auto modelMeshNormalsAndTangents = _model->_normalsAndTangents.find(meshIndex++); - if (mesh.blendshapes.isEmpty() || modelMeshNormalsAndTangents == _model->_normalsAndTangents.end()) { - continue; - } - - vertices += mesh.vertices; - normalsAndTangents += modelMeshNormalsAndTangents->second; - glm::vec3* meshVertices = vertices.data() + offset; - NormalType* meshNormalsAndTangents = normalsAndTangents.data() + normalsAndTangentsOffset; - offset += mesh.vertices.size(); - normalsAndTangentsOffset += modelMeshNormalsAndTangents->second.size(); - const float NORMAL_COEFFICIENT_SCALE = 0.01f; - for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { - float vertexCoefficient = _blendshapeCoefficients.at(i); - const float EPSILON = 0.0001f; - if (vertexCoefficient < EPSILON) { - continue; - } - float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; - const FBXBlendshape& blendshape = mesh.blendshapes.at(i); - tbb::parallel_for(tbb::blocked_range(0, blendshape.indices.size()), [&](const tbb::blocked_range& range) { - for (auto j = range.begin(); j < range.end(); j++) { - int index = blendshape.indices.at(j); - meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; - - glm::vec3 normal = mesh.normals.at(index) + blendshape.normals.at(j) * normalCoefficient; - glm::vec3 tangent; - if (index < mesh.tangents.size()) { - tangent = mesh.tangents.at(index); - if ((int)j < blendshape.tangents.size()) { - tangent += blendshape.tangents.at(j) * normalCoefficient; - } - } -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(normal, tangent, finalNormal, finalTangent); -#else - const auto& finalNormal = normal; - const auto& finalTangent = tangent; -#endif - meshNormalsAndTangents[2 * index] = finalNormal; - meshNormalsAndTangents[2 * index + 1] = finalTangent; - } - }); - } - } - } - // post the result to the ModelBlender, which will dispatch to the model if still alive - QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", - Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(QVector, vertices), - Q_ARG(QVector, normalsAndTangents)); -} - void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale) { if (forceRescale || _scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) { _scaleToFit = scaleToFit; @@ -1531,44 +1445,6 @@ void Model::updateClusterMatrices() { } } -bool Model::maybeStartBlender() { - if (isLoaded()) { - const FBXGeometry& fbxGeometry = getFBXGeometry(); - if (fbxGeometry.hasBlendedMeshes()) { - QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, _blendshapeCoefficients)); - return true; - } - } - return false; -} - -void Model::setBlendedVertices(int blendNumber, const QVector& vertices, const QVector& normalsAndTangents) { - if (!isLoaded() || blendNumber < _appliedBlendNumber || !_blendedVertexBuffersInitialized) { - return; - } - _appliedBlendNumber = blendNumber; - const FBXGeometry& fbxGeometry = getFBXGeometry(); - int index = 0; - int normalAndTangentIndex = 0; - for (int i = 0; i < fbxGeometry.meshes.size(); i++) { - const FBXMesh& mesh = fbxGeometry.meshes.at(i); - auto meshNormalsAndTangents = _normalsAndTangents.find(i); - const auto& buffer = _blendedVertexBuffers.find(i); - if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end() || buffer == _blendedVertexBuffers.end()) { - continue; - } - - const auto vertexCount = mesh.vertices.size(); - const auto verticesSize = vertexCount * sizeof(glm::vec3); - buffer->second->resize(mesh.vertices.size() * sizeof(glm::vec3) + meshNormalsAndTangents->second.size() * sizeof(NormalType)); - buffer->second->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); - buffer->second->setSubData(verticesSize, meshNormalsAndTangents->second.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); - - index += vertexCount; - normalAndTangentIndex += meshNormalsAndTangents->second.size(); - } -} - void Model::deleteGeometry() { _deleteGeometryCounter++; _blendedVertexBuffers.clear(); @@ -1605,42 +1481,6 @@ const render::ItemIDs& Model::fetchRenderItemIDs() const { return _modelMeshRenderItemIDs; } -void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { - _blendedVertexBuffers[index] = std::make_shared(); - QVector normalsAndTangents; - normalsAndTangents.resize(2 * mesh.normals.size()); - - // Interleave normals and tangents - // Parallel version for performance - tbb::parallel_for(tbb::blocked_range(0, mesh.normals.size()), [&](const tbb::blocked_range& range) { - auto normalsRange = std::make_pair(mesh.normals.begin() + range.begin(), mesh.normals.begin() + range.end()); - auto tangentsRange = std::make_pair(mesh.tangents.begin() + range.begin(), mesh.tangents.begin() + range.end()); - auto normalsAndTangentsIt = normalsAndTangents.begin() + 2 * range.begin(); - - for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; - normalIt != normalsRange.second; - ++normalIt, ++tangentIt) { -#if FBX_PACK_NORMALS - glm::uint32 finalNormal; - glm::uint32 finalTangent; - buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); -#else - const auto& finalNormal = *normalIt; - const auto& finalTangent = *tangentIt; -#endif - *normalsAndTangentsIt = finalNormal; - ++normalsAndTangentsIt; - *normalsAndTangentsIt = finalTangent; - ++normalsAndTangentsIt; - } - }); - const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); - _blendedVertexBuffers[index]->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType)); - _blendedVertexBuffers[index]->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData()); - _blendedVertexBuffers[index]->setSubData(verticesSize, normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); - _normalsAndTangents[index] = normalsAndTangents; -} - void Model::createRenderItemSet() { assert(isLoaded()); const auto& meshes = _renderGeometry->getMeshes(); @@ -1774,6 +1614,164 @@ public: } }; + +class Blender : public QRunnable { +public: + + Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& blendshapeCoefficients); + + virtual void run() override; + +private: + + ModelPointer _model; + int _blendNumber; + Geometry::WeakPointer _geometry; + QVector _blendshapeCoefficients; +}; + +Blender::Blender(ModelPointer model, int blendNumber, const Geometry::WeakPointer& geometry, const QVector& blendshapeCoefficients) : + _model(model), + _blendNumber(blendNumber), + _geometry(geometry), + _blendshapeCoefficients(blendshapeCoefficients) { +} + +void Blender::run() { + QVector vertices; + QVector normalsAndTangents; + if (_model && _model->isLoaded()) { + DETAILED_PROFILE_RANGE_EX(simulation_animation, __FUNCTION__, 0xFFFF0000, 0, { { "url", _model->getURL().toString() } }); + int offset = 0; + int normalsAndTangentsOffset = 0; + auto meshes = _model->getFBXGeometry().meshes; + int meshIndex = 0; + foreach(const FBXMesh& mesh, meshes) { + auto modelMeshNormalsAndTangents = _model->_normalsAndTangents.find(meshIndex++); + if (mesh.blendshapes.isEmpty() || modelMeshNormalsAndTangents == _model->_normalsAndTangents.end()) { + continue; + } + + vertices += mesh.vertices; + normalsAndTangents += modelMeshNormalsAndTangents->second; + glm::vec3* meshVertices = vertices.data() + offset; + NormalType* meshNormalsAndTangents = normalsAndTangents.data() + normalsAndTangentsOffset; + offset += mesh.vertices.size(); + normalsAndTangentsOffset += modelMeshNormalsAndTangents->second.size(); + const float NORMAL_COEFFICIENT_SCALE = 0.01f; + for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) { + float vertexCoefficient = _blendshapeCoefficients.at(i); + const float EPSILON = 0.0001f; + if (vertexCoefficient < EPSILON) { + continue; + } + float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE; + const FBXBlendshape& blendshape = mesh.blendshapes.at(i); + tbb::parallel_for(tbb::blocked_range(0, blendshape.indices.size()), [&](const tbb::blocked_range& range) { + for (auto j = range.begin(); j < range.end(); j++) { + int index = blendshape.indices.at(j); + meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient; + + glm::vec3 normal = mesh.normals.at(index) + blendshape.normals.at(j) * normalCoefficient; + glm::vec3 tangent; + if (index < mesh.tangents.size()) { + tangent = mesh.tangents.at(index); + if ((int)j < blendshape.tangents.size()) { + tangent += blendshape.tangents.at(j) * normalCoefficient; + } + } +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + buffer_helpers::packNormalAndTangent(normal, tangent, finalNormal, finalTangent); +#else + const auto& finalNormal = normal; + const auto& finalTangent = tangent; +#endif + meshNormalsAndTangents[2 * index] = finalNormal; + meshNormalsAndTangents[2 * index + 1] = finalTangent; + } + }); + } + } + } + // post the result to the ModelBlender, which will dispatch to the model if still alive + QMetaObject::invokeMethod(DependencyManager::get().data(), "setBlendedVertices", + Q_ARG(ModelPointer, _model), Q_ARG(int, _blendNumber), Q_ARG(QVector, vertices), + Q_ARG(QVector, normalsAndTangents)); +} + +bool Model::maybeStartBlender() { + if (isLoaded()) { + QThreadPool::globalInstance()->start(new Blender(getThisPointer(), ++_blendNumber, _renderGeometry, _blendshapeCoefficients)); + return true; + } + return false; +} + +void Model::setBlendedVertices(int blendNumber, const QVector& vertices, const QVector& normalsAndTangents) { + if (!isLoaded() || blendNumber < _appliedBlendNumber || !_blendedVertexBuffersInitialized) { + return; + } + _appliedBlendNumber = blendNumber; + const FBXGeometry& fbxGeometry = getFBXGeometry(); + int index = 0; + int normalAndTangentIndex = 0; + for (int i = 0; i < fbxGeometry.meshes.size(); i++) { + const FBXMesh& mesh = fbxGeometry.meshes.at(i); + auto meshNormalsAndTangents = _normalsAndTangents.find(i); + const auto& buffer = _blendedVertexBuffers.find(i); + if (mesh.blendshapes.isEmpty() || meshNormalsAndTangents == _normalsAndTangents.end() || buffer == _blendedVertexBuffers.end()) { + continue; + } + + const auto vertexCount = mesh.vertices.size(); + const auto verticesSize = vertexCount * sizeof(glm::vec3); + buffer->second->resize(mesh.vertices.size() * sizeof(glm::vec3) + meshNormalsAndTangents->second.size() * sizeof(NormalType)); + buffer->second->setSubData(0, verticesSize, (gpu::Byte*) vertices.constData() + index * sizeof(glm::vec3)); + buffer->second->setSubData(verticesSize, meshNormalsAndTangents->second.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data() + normalAndTangentIndex * sizeof(NormalType)); + + index += vertexCount; + normalAndTangentIndex += meshNormalsAndTangents->second.size(); + } +} + +void Model::initializeBlendshapes(const FBXMesh& mesh, int index) { + _blendedVertexBuffers[index] = std::make_shared(); + QVector normalsAndTangents; + normalsAndTangents.resize(2 * mesh.normals.size()); + + // Interleave normals and tangents + // Parallel version for performance + tbb::parallel_for(tbb::blocked_range(0, mesh.normals.size()), [&](const tbb::blocked_range& range) { + auto normalsRange = std::make_pair(mesh.normals.begin() + range.begin(), mesh.normals.begin() + range.end()); + auto tangentsRange = std::make_pair(mesh.tangents.begin() + range.begin(), mesh.tangents.begin() + range.end()); + auto normalsAndTangentsIt = normalsAndTangents.begin() + 2 * range.begin(); + + for (auto normalIt = normalsRange.first, tangentIt = tangentsRange.first; + normalIt != normalsRange.second; + ++normalIt, ++tangentIt) { +#if FBX_PACK_NORMALS + glm::uint32 finalNormal; + glm::uint32 finalTangent; + buffer_helpers::packNormalAndTangent(*normalIt, *tangentIt, finalNormal, finalTangent); +#else + const auto& finalNormal = *normalIt; + const auto& finalTangent = *tangentIt; +#endif + *normalsAndTangentsIt = finalNormal; + ++normalsAndTangentsIt; + *normalsAndTangentsIt = finalTangent; + ++normalsAndTangentsIt; + } + }); + const auto verticesSize = mesh.vertices.size() * sizeof(glm::vec3); + _blendedVertexBuffers[index]->resize(mesh.vertices.size() * sizeof(glm::vec3) + normalsAndTangents.size() * sizeof(NormalType)); + _blendedVertexBuffers[index]->setSubData(0, verticesSize, (const gpu::Byte*) mesh.vertices.constData()); + _blendedVertexBuffers[index]->setSubData(verticesSize, normalsAndTangents.size() * sizeof(NormalType), (const gpu::Byte*) normalsAndTangents.data()); + _normalsAndTangents[index] = normalsAndTangents; +} + ModelBlender::ModelBlender() : _pendingBlenders(0) { } @@ -1783,14 +1781,23 @@ ModelBlender::~ModelBlender() { void ModelBlender::noteRequiresBlend(ModelPointer model) { Lock lock(_mutex); - if (_pendingBlenders < QThread::idealThreadCount()) { - if (model->maybeStartBlender()) { - _pendingBlenders++; - return; - } + if (_modelsRequiringBlendsSet.find(model) == _modelsRequiringBlendsSet.end()) { + _modelsRequiringBlendsQueue.push(model); + _modelsRequiringBlendsSet.insert(model); } - _modelsRequiringBlends.insert(model); + if (_pendingBlenders < QThread::idealThreadCount()) { + while (!_modelsRequiringBlendsQueue.empty()) { + auto weakPtr = _modelsRequiringBlendsQueue.front(); + _modelsRequiringBlendsQueue.pop(); + _modelsRequiringBlendsSet.erase(weakPtr); + ModelPointer nextModel = weakPtr.lock(); + if (nextModel && nextModel->maybeStartBlender()) { + _pendingBlenders++; + return; + } + } + } } void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, QVector vertices, QVector normalsAndTangents) { @@ -1800,20 +1807,15 @@ void ModelBlender::setBlendedVertices(ModelPointer model, int blendNumber, QVect { Lock lock(_mutex); _pendingBlenders--; - _modelsRequiringBlends.erase(model); - std::set> modelsToErase; - for (auto i = _modelsRequiringBlends.begin(); i != _modelsRequiringBlends.end(); i++) { - auto weakPtr = *i; + while (!_modelsRequiringBlendsQueue.empty()) { + auto weakPtr = _modelsRequiringBlendsQueue.front(); + _modelsRequiringBlendsQueue.pop(); + _modelsRequiringBlendsSet.erase(weakPtr); ModelPointer nextModel = weakPtr.lock(); if (nextModel && nextModel->maybeStartBlender()) { _pendingBlenders++; break; - } else { - modelsToErase.insert(weakPtr); } } - for (auto& weakPtr : modelsToErase) { - _modelsRequiringBlends.erase(weakPtr); - } } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 447f75dd9d..c763197bc6 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -530,7 +530,8 @@ private: ModelBlender(); virtual ~ModelBlender(); - std::set> _modelsRequiringBlends; + std::queue _modelsRequiringBlendsQueue; + std::set> _modelsRequiringBlendsSet; int _pendingBlenders; Mutex _mutex; From 55ab0a394b81706e7d5776cf253258e688d014a8 Mon Sep 17 00:00:00 2001 From: sabrina-shanman Date: Tue, 25 Sep 2018 12:21:00 -0700 Subject: [PATCH 52/63] Return std::make_shared --- interface/src/raypick/LaserPointer.cpp | 2 +- interface/src/raypick/ParabolaPointer.cpp | 2 +- interface/src/raypick/StylusPointer.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 577978cc88..3c66923b4e 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -38,7 +38,7 @@ void LaserPointer::editRenderStatePath(const std::string& state, const QVariant& PickResultPointer LaserPointer::getPickResultCopy(const PickResultPointer& pickResult) const { auto rayPickResult = std::dynamic_pointer_cast(pickResult); if (!rayPickResult) { - std::make_shared(); + return std::make_shared(); } return std::make_shared(*rayPickResult.get()); } diff --git a/interface/src/raypick/ParabolaPointer.cpp b/interface/src/raypick/ParabolaPointer.cpp index 92b82fff7f..33fa8738d9 100644 --- a/interface/src/raypick/ParabolaPointer.cpp +++ b/interface/src/raypick/ParabolaPointer.cpp @@ -33,7 +33,7 @@ ParabolaPointer::ParabolaPointer(const QVariant& rayProps, const RenderStateMap& PickResultPointer ParabolaPointer::getPickResultCopy(const PickResultPointer& pickResult) const { auto parabolaPickResult = std::dynamic_pointer_cast(pickResult); if (!parabolaPickResult) { - std::make_shared(); + return std::make_shared(); } return std::make_shared(*parabolaPickResult.get()); } diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp index 0b44b2705d..b648e125bf 100644 --- a/interface/src/raypick/StylusPointer.cpp +++ b/interface/src/raypick/StylusPointer.cpp @@ -150,7 +150,7 @@ bool StylusPointer::shouldTrigger(const PickResultPointer& pickResult) { PickResultPointer StylusPointer::getPickResultCopy(const PickResultPointer& pickResult) const { auto stylusPickResult = std::dynamic_pointer_cast(pickResult); if (!stylusPickResult) { - std::make_shared(); + return std::make_shared(); } return std::make_shared(*stylusPickResult.get()); } From 5adc4c2290905a37b9668447ee0cbb2cb092022f Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 25 Sep 2018 13:17:49 -0700 Subject: [PATCH 53/63] Protect against null pointer usage in sound scripting wrapper --- libraries/audio/src/Sound.cpp | 7 +++++-- libraries/audio/src/Sound.h | 8 ++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 67f9952771..da284f19a3 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -43,8 +43,11 @@ void soundSharedPointerFromScriptValue(const QScriptValue& object, SharedSoundPo } } -SoundScriptingInterface::SoundScriptingInterface(SharedSoundPointer sound) : _sound(sound) { - QObject::connect(sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready); +SoundScriptingInterface::SoundScriptingInterface(const SharedSoundPointer& sound) : _sound(sound) { + // During shutdown we can sometimes get an empty sound pointer back + if (_sound) { + QObject::connect(_sound.data(), &Sound::ready, this, &SoundScriptingInterface::ready); + } } Sound::Sound(const QUrl& url, bool isStereo, bool isAmbisonic) : diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 348600e4ae..a0544870d0 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -105,11 +105,11 @@ class SoundScriptingInterface : public QObject { Q_PROPERTY(float duration READ getDuration) public: - SoundScriptingInterface(SharedSoundPointer sound); - SharedSoundPointer getSound() { return _sound; } + SoundScriptingInterface(const SharedSoundPointer& sound); + const SharedSoundPointer& getSound() { return _sound; } - bool isReady() const { return _sound->isReady(); } - float getDuration() { return _sound->getDuration(); } + bool isReady() const { return _sound ? _sound->isReady() : false; } + float getDuration() { return _sound ? _sound->getDuration() : 0.0f; } /**jsdoc * Triggered when the sound has been downloaded and is ready to be played. From 15de0590e8ceaa44419781b4177b493d7851bfef Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Tue, 25 Sep 2018 13:18:49 -0700 Subject: [PATCH 54/63] Cleaning up commented out code --- libraries/audio-client/src/AudioClient.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 6af74bc8e8..68a68633e6 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -285,9 +285,6 @@ void AudioClient::customDeleter() { _shouldRestartInputSetup = false; #endif stop(); - //_checkDevicesTimer->stop(); - //_checkPeakValuesTimer->stop(); - deleteLater(); } From 4e7b03883f2731160c036ae59fbb0c98daa22612 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Tue, 25 Sep 2018 15:13:26 -0700 Subject: [PATCH 55/63] Use triple equals --- .../marketplaceItemTester/MarketplaceItemTester.qml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index a0ccf91baa..8dfb277197 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -132,7 +132,7 @@ Rectangle { } }, "trash": function(){ - if ("application" == assetType) { + if ("application" === assetType) { Commerce.uninstallApp(resource); } sendToScript({ @@ -176,7 +176,7 @@ Rectangle { "unknown" ] - currentIndex: (("entity or wearable" == assetType) ? + currentIndex: (("entity or wearable" === assetType) ? model.indexOf("unknown") : model.indexOf(assetType)) Component.onCompleted: { @@ -203,10 +203,10 @@ Rectangle { "unknown": hifi.glyphs.circleSlash, "wearable": hifi.glyphs.hat, } - text: (("trash" == modelData) ? + text: (("trash" === modelData) ? glyphs.trash : glyphs[comboBox.model[comboBox.currentIndex]]) - size: ("trash" == modelData) ? 22 : 30 + size: ("trash" === modelData) ? 22 : 30 color: hifi.colors.black horizontalAlignment: Text.AlignHCenter MouseArea { @@ -257,7 +257,7 @@ Rectangle { // Alas, there is nothing we can do about that so charge // ahead as though we are sure the present signal is one // we expect. - if ("load file" == currentAction) { + if ("load file" === currentAction) { Window.browseChanged.disconnect(onResourceSelected); } else if ("load url" == currentAction) { Window.promptTextChanged.disconnect(onResourceSelected); From 276125937c3ab37b8eec7905fd0678f302c50d60 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Tue, 25 Sep 2018 15:18:12 -0700 Subject: [PATCH 56/63] Eliminate double equals --- .../marketplaceItemTester/MarketplaceItemTester.qml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 8dfb277197..8f391f24c0 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -70,7 +70,7 @@ Rectangle { } function installResourceObj(resourceObj) { - if ("application" == resourceObj.assetType) { + if ("application" === resourceObj.assetType) { Commerce.installApp(resourceObj.resource); } } @@ -257,10 +257,13 @@ Rectangle { // Alas, there is nothing we can do about that so charge // ahead as though we are sure the present signal is one // we expect. - if ("load file" === currentAction) { - Window.browseChanged.disconnect(onResourceSelected); - } else if ("load url" == currentAction) { - Window.promptTextChanged.disconnect(onResourceSelected); + switch(currentAction) { + case "load file": + Window.browseChanged.disconnect(onResourceSelected); + break + case "load url": + Window.promptTextChanged.disconnect(onResourceSelected); + break; } if (resource) { var resourceObj = buildResourceObj(resource); From e6249e690e2a5ff951c02b76f2fb8ee102258fcb Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Tue, 25 Sep 2018 15:52:26 -0700 Subject: [PATCH 57/63] Remove duplicate line --- scripts/system/marketplaces/marketplaces.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 2e0c05be43..cc5ff99673 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -23,7 +23,6 @@ Script.include("/~/system/libraries/connectionUtils.js"); var MARKETPLACE_CHECKOUT_QML_PATH = "hifi/commerce/checkout/Checkout.qml"; var MARKETPLACE_INSPECTIONCERTIFICATE_QML_PATH = "hifi/commerce/inspectionCertificate/InspectionCertificate.qml"; var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; -var MARKETPLACE_ITEM_TESTER_QML_PATH = "hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml"; var MARKETPLACE_PURCHASES_QML_PATH = "hifi/commerce/purchases/Purchases.qml"; var MARKETPLACE_WALLET_QML_PATH = "hifi/commerce/wallet/Wallet.qml"; var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js"); From 82b08f1a939bfd6ad93285a5e86a1fea8d0370a2 Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 25 Sep 2018 16:21:05 -0700 Subject: [PATCH 58/63] Wait until skeleton is loaded to locate the avatar --- interface/src/avatar/MyAvatar.cpp | 54 ++++++++----------------------- interface/src/avatar/MyAvatar.h | 2 ++ 2 files changed, 15 insertions(+), 41 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index df7ec93b6a..49ba763485 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -118,6 +118,7 @@ MyAvatar::MyAvatar(QThread* thread) : _goToSafe(true), _goToPosition(), _goToOrientation(), + _goToFeetAjustment(false), _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _hmdAtRestDetector(glm::vec3(0), glm::quat()), @@ -498,7 +499,7 @@ void MyAvatar::update(float deltaTime) { setCurrentStandingHeight(computeStandingHeightMode(getControllerPoseInAvatarFrame(controller::Action::HEAD))); setAverageHeadRotation(computeAverageHeadRotation(getControllerPoseInAvatarFrame(controller::Action::HEAD))); - if (_drawAverageFacingEnabled) { + if (_drawAverageFacingEnabled) { auto sensorHeadPose = getControllerPoseInSensorFrame(controller::Action::HEAD); glm::vec3 worldHeadPos = transformPoint(getSensorToWorldMatrix(), sensorHeadPose.getTranslation()); glm::vec3 worldFacingAverage = transformVectorFast(getSensorToWorldMatrix(), glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y)); @@ -526,6 +527,11 @@ void MyAvatar::update(float deltaTime) { _physicsSafetyPending = getCollisionsEnabled(); _characterController.recomputeFlying(); // In case we've gone to into the sky. } + if (_goToFeetAjustment && _skeletonModelLoaded) { + auto feetAjustment = getWorldPosition() - getWorldFeetPosition(); + goToLocation(getWorldPosition() + feetAjustment); + _goToFeetAjustment = false; + } if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) { // When needed and ready, arrange to check and fix. _physicsSafetyPending = false; @@ -1728,6 +1734,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _headBoneSet.clear(); _cauterizationNeedsUpdate = true; + _skeletonModelLoaded = false; std::shared_ptr skeletonConnection = std::make_shared(); *skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() { @@ -1745,6 +1752,7 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModel->setCauterizeBoneSet(_headBoneSet); _fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl(); initAnimGraph(); + _skeletonModelLoaded = true; } QObject::disconnect(*skeletonConnection); }); @@ -2945,46 +2953,10 @@ void MyAvatar::goToLocation(const QVariant& propertiesVar) { } void MyAvatar::goToFeetLocation(const glm::vec3& newPosition, - bool hasOrientation, const glm::quat& newOrientation, - bool shouldFaceLocation) { - - qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - moving to " << newPosition.x << ", " - << newPosition.y << ", " << newPosition.z; - - ShapeInfo shapeInfo; - computeShapeInfo(shapeInfo); - glm::vec3 halfExtents = shapeInfo.getHalfExtents(); - glm::vec3 localFeetPos = shapeInfo.getOffset() - glm::vec3(0.0f, halfExtents.y + halfExtents.x, 0.0f); - glm::mat4 localFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, localFeetPos); - - glm::mat4 worldFeet = createMatFromQuatAndPos(Quaternions::IDENTITY, newPosition); - - glm::mat4 avatarMat = worldFeet * glm::inverse(localFeet); - - glm::vec3 adjustedPosition = extractTranslation(avatarMat); - - _goToPending = true; - _goToPosition = adjustedPosition; - _goToOrientation = getWorldOrientation(); - if (hasOrientation) { - qCDebug(interfaceapp).nospace() << "MyAvatar goToFeetLocation - new orientation is " - << newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w; - - // orient the user to face the target - glm::quat quatOrientation = cancelOutRollAndPitch(newOrientation); - - if (shouldFaceLocation) { - quatOrientation = newOrientation * glm::angleAxis(PI, Vectors::UP); - - // move the user a couple units away - const float DISTANCE_TO_USER = 2.0f; - _goToPosition = adjustedPosition - quatOrientation * IDENTITY_FORWARD * DISTANCE_TO_USER; - } - - _goToOrientation = quatOrientation; - } - - emit transformChanged(); + bool hasOrientation, const glm::quat& newOrientation, + bool shouldFaceLocation) { + _goToFeetAjustment = true; + goToLocation(newPosition, hasOrientation, newOrientation, shouldFaceLocation); } void MyAvatar::goToLocation(const glm::vec3& newPosition, diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 1dc0b3cd40..d7379a18c4 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1732,6 +1732,7 @@ private: bool _goToPending { false }; bool _physicsSafetyPending { false }; bool _goToSafe { true }; + bool _goToFeetAjustment { false }; glm::vec3 _goToPosition; glm::quat _goToOrientation; @@ -1807,6 +1808,7 @@ private: bool _haveReceivedHeightLimitsFromDomain { false }; int _disableHandTouchCount { 0 }; + bool _skeletonModelLoaded { false }; Setting::Handle _dominantHandSetting; Setting::Handle _headPitchSetting; From 754653b093d2af5e5fe56e5caa33d1b2252e088d Mon Sep 17 00:00:00 2001 From: luiscuenca Date: Tue, 25 Sep 2018 17:31:18 -0700 Subject: [PATCH 59/63] Fix warnings --- interface/src/avatar/MyAvatar.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 49ba763485..f2e6b68a0f 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -116,9 +116,9 @@ MyAvatar::MyAvatar(QThread* thread) : _bodySensorMatrix(), _goToPending(false), _goToSafe(true), + _goToFeetAjustment(false), _goToPosition(), _goToOrientation(), - _goToFeetAjustment(false), _prevShouldDrawHead(true), _audioListenerMode(FROM_HEAD), _hmdAtRestDetector(glm::vec3(0), glm::quat()), From 28ec98be9e274d40edb8b83e0da89f6e1a92f10e Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Tue, 25 Sep 2018 19:24:46 -0700 Subject: [PATCH 60/63] Uninstall marketplace item tester on wallet shutdown --- scripts/system/commerce/wallet.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 2c64b35b00..b12191b00c 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -521,6 +521,14 @@ function installMarketplaceItemTester() { }); } +function uninstallMarketplaceItemTester() { + if (Menu.menuExists(DEVELOPER_MENU) && + Menu.menuItemExists(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL) + ) { + Menu.removeMenuItem(DEVELOPER_MENU, MARKETPLACE_ITEM_TESTER_LABEL); + } +} + var BUTTON_NAME = "WALLET"; var WALLET_QML_SOURCE = "hifi/commerce/wallet/Wallet.qml"; var ui; @@ -555,6 +563,7 @@ function off() { function shutdown() { GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); deleteSendMoneyParticleEffect(); + uninstallMarketplaceItemTester(); off(); } From 7257a994140f4b7e4d018a4b813c35cb6e4fe45a Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Wed, 26 Sep 2018 10:46:19 -0700 Subject: [PATCH 61/63] Checkpoint sysTray Installer --- cmake/macros/SetPackagingParameters.cmake | 10 +- cmake/templates/CPackProperties.cmake.in | 2 +- cmake/templates/NSIS.template.in | 173 +++++++++++----------- server-console/CMakeLists.txt | 4 +- server-console/src/main.js | 76 +++++----- server-console/src/modules/hf-app.js | 2 +- 6 files changed, 127 insertions(+), 140 deletions(-) diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index 0f8975e9b5..164d326b20 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -18,7 +18,7 @@ macro(SET_PACKAGING_PARAMETERS) set(BUILD_GLOBAL_SERVICES "DEVELOPMENT") set(USE_STABLE_GLOBAL_SERVICES 0) set(BUILD_NUMBER 0) - set(APP_USER_MODEL_ID "com.highfidelity.sandbox-dev") + set(APP_USER_MODEL_ID "com.highfidelity.console") set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV") set_from_env(RELEASE_NUMBER RELEASE_NUMBER "") @@ -176,15 +176,15 @@ macro(SET_PACKAGING_PARAMETERS) # shortcut names if (PRODUCTION_BUILD) set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface") - set(CONSOLE_SHORTCUT_NAME "Sandbox") - set(APP_USER_MODEL_ID "com.highfidelity.sandbox") + set(CONSOLE_SHORTCUT_NAME "High Fidelity Console") + set(APP_USER_MODEL_ID "com.highfidelity.console") else () set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION_NO_SHA}") - set(CONSOLE_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION_NO_SHA}") + set(CONSOLE_SHORTCUT_NAME "High Fidelity Console - ${BUILD_VERSION_NO_SHA}") endif () set(INTERFACE_HF_SHORTCUT_NAME "${INTERFACE_SHORTCUT_NAME}") - set(CONSOLE_HF_SHORTCUT_NAME "High Fidelity ${CONSOLE_SHORTCUT_NAME}") + set(CONSOLE_HF_SHORTCUT_NAME "${CONSOLE_SHORTCUT_NAME}") set(PRE_SANDBOX_INTERFACE_SHORTCUT_NAME "High Fidelity") set(PRE_SANDBOX_CONSOLE_SHORTCUT_NAME "Server Console") diff --git a/cmake/templates/CPackProperties.cmake.in b/cmake/templates/CPackProperties.cmake.in index 1d7effd18f..0a56181138 100644 --- a/cmake/templates/CPackProperties.cmake.in +++ b/cmake/templates/CPackProperties.cmake.in @@ -13,7 +13,7 @@ set(INTERFACE_DISPLAY_NAME "Interface") set(INTERFACE_SHORTCUT_NAME "@INTERFACE_SHORTCUT_NAME@") set(INTERFACE_HF_SHORTCUT_NAME "@INTERFACE_HF_SHORTCUT_NAME@") set(INTERFACE_WIN_EXEC_NAME "@INTERFACE_EXEC_PREFIX@.exe") -set(CONSOLE_DISPLAY_NAME "Sandbox") +set(CONSOLE_DISPLAY_NAME "Console") set(CONSOLE_INSTALL_SUBDIR "@CONSOLE_INSTALL_DIR@") set(CONSOLE_SHORTCUT_NAME "@CONSOLE_SHORTCUT_NAME@") set(CONSOLE_HF_SHORTCUT_NAME "@CONSOLE_HF_SHORTCUT_NAME@") diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 7f6884f478..b7564f45e5 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -405,6 +405,14 @@ Var GAClientID Section "-Previous Install Cleanup" ; Remove the resources folder so we don't end up including removed QML files RMDir /r "$INSTDIR\resources" + + ; delete old assignment-client and domain-server so they're no longer present + ; in client only installs. + Delete "$INSTDIR\@DS_EXEC_NAME@" + Delete "$INSTDIR\@AC_EXEC_NAME@" + + ; delete interface so it's not there for server-only installs + Delete "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@" SectionEnd @CPACK_NSIS_INSTALLATION_TYPES@ @@ -532,9 +540,9 @@ SectionEnd Var PostInstallDialog Var DesktopClientCheckbox -Var DesktopServerCheckbox -Var ServerStartupCheckbox -Var LaunchServerNowCheckbox +Var DesktopConsoleCheckbox +Var ConsoleStartupCheckbox +Var LaunchConsoleNowCheckbox Var LaunchClientNowCheckbox Var CleanInstallCheckbox Var CurrentOffset @@ -746,28 +754,8 @@ Function PostInstallOptionsPage !insertmacro SetInstallOption $DesktopClientCheckbox @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ ${BST_CHECKED} ${EndIf} - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" - Pop $DesktopServerCheckbox - IntOp $CurrentOffset $CurrentOffset + 15 - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetInstallOption $DesktopServerCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} - ${EndIf} - - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ after install" - Pop $LaunchServerNowCheckbox - - ; set the checkbox state depending on what is present in the registry - !insertmacro SetInstallOption $LaunchServerNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} - ${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE - ${IfNot} $substringResult == "" - ${NSD_SetState} $LaunchServerNowCheckbox ${BST_UNCHECKED} - ${EndIf} - - IntOp $CurrentOffset $CurrentOffset + 15 - ${EndIf} + ; set the checkbox state depending on what is present in the registry + !insertmacro SetInstallOption $DesktopConsoleCheckbox @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ ${BST_UNCHECKED} ${If} @CLIENT_COMPONENT_CONDITIONAL@ ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @INTERFACE_HF_SHORTCUT_NAME@ after install" @@ -782,28 +770,42 @@ Function PostInstallOptionsPage ${EndIf} ${EndIf} - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" - Pop $ServerStartupCheckbox - IntOp $CurrentOffset $CurrentOffset + 15 + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Create a desktop shortcut for @CONSOLE_HF_SHORTCUT_NAME@" + Pop $DesktopConsoleCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 - ; set the checkbox state depending on what is present in the registry - !insertmacro SetInstallOption $ServerStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ after install" + Pop $LaunchConsoleNowCheckbox + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetInstallOption $LaunchConsoleNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} + ${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE + ${IfNot} $substringResult == "" + ${NSD_SetState} $LaunchConsoleNowCheckbox ${BST_UNCHECKED} ${EndIf} + IntOp $CurrentOffset $CurrentOffset + 15 + + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Launch @CONSOLE_HF_SHORTCUT_NAME@ on startup" + Pop $ConsoleStartupCheckbox + IntOp $CurrentOffset $CurrentOffset + 15 + + ; set the checkbox state depending on what is present in the registry + !insertmacro SetInstallOption $ConsoleStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} + ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" Pop $CleanInstallCheckbox IntOp $CurrentOffset $CurrentOffset + 15 ${If} @PR_BUILD@ == 1 - ; a PR build defaults all install options expect LaunchServerNowCheckbox, LaunchClientNowCheckbox and the settings copy to unchecked + ; a PR build defaults all install options expect LaunchConsoleNowCheckbox, LaunchClientNowCheckbox and the settings copy to unchecked ${If} @CLIENT_COMPONENT_CONDITIONAL@ ${NSD_SetState} $DesktopClientCheckbox ${BST_UNCHECKED} ${EndIf} ${If} @SERVER_COMPONENT_CONDITIONAL@ - ${NSD_SetState} $DesktopServerCheckbox ${BST_UNCHECKED} - ${NSD_SetState} $ServerStartupCheckbox ${BST_UNCHECKED} + ${NSD_SetState} $DesktopConsoleCheckbox ${BST_UNCHECKED} + ${NSD_SetState} $ConsoleStartupCheckbox ${BST_UNCHECKED} ${EndIf} ; push the offset @@ -824,9 +826,9 @@ FunctionEnd !macroend Var DesktopClientState -Var DesktopServerState -Var ServerStartupState -Var LaunchServerNowState +Var DesktopConsoleState +Var ConsoleStartupState +Var LaunchConsoleNowState Var LaunchClientNowState Var CopyFromProductionState Var CleanInstallState @@ -842,11 +844,11 @@ Function ReadInstallTypes StrCpy $Express "1" StrCpy $DesktopClientState ${BST_CHECKED} - StrCpy $ServerStartupState ${BST_CHECKED} - StrCpy $LaunchServerNowState ${BST_CHECKED} + StrCpy $ConsoleStartupState ${BST_CHECKED} + StrCpy $LaunchConsoleNowState ${BST_CHECKED} StrCpy $LaunchClientNowState ${BST_CHECKED} StrCpy $CleanInstallState ${BST_UNCHECKED} - StrCpy $DesktopServerState ${BST_UNCHECKED} + StrCpy $DesktopConsoleState ${BST_UNCHECKED} ${If} @PR_BUILD@ == 1 StrCpy $CopyFromProductionState ${BST_UNCHECKED} @@ -860,28 +862,25 @@ Function ReadInstallTypes FunctionEnd Function ReadPostInstallOptions + + ; check if the user asked for a desktop shortcut to console + ${NSD_GetState} $DesktopConsoleCheckbox $DesktopConsoleState + + ; check if the user asked to have console launched every startup + ${NSD_GetState} $ConsoleStartupCheckbox $ConsoleStartupState + ${If} @CLIENT_COMPONENT_CONDITIONAL@ ; check if the user asked for a desktop shortcut to High Fidelity ${NSD_GetState} $DesktopClientCheckbox $DesktopClientState ${EndIf} - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ; check if the user asked for a desktop shortcut to Sandbox - ${NSD_GetState} $DesktopServerCheckbox $DesktopServerState - - ; check if the user asked to have Sandbox launched every startup - ${NSD_GetState} $ServerStartupCheckbox $ServerStartupState - ${EndIf} - ${If} @PR_BUILD@ == 1 ; check if we need to copy settings/content from production for this PR build ${NSD_GetState} $CopyFromProductionCheckbox $CopyFromProductionState ${EndIf} - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ; check if we need to launch the server post-install - ${NSD_GetState} $LaunchServerNowCheckbox $LaunchServerNowState - ${EndIf} + ; check if we need to launch the console post-install + ${NSD_GetState} $LaunchConsoleNowCheckbox $LaunchConsoleNowState ${If} @CLIENT_COMPONENT_CONDITIONAL@ ; check if we need to launch the client post-install @@ -893,6 +892,17 @@ Function ReadPostInstallOptions FunctionEnd Function HandlePostInstallOptions + + ; check if the user asked for a desktop shortcut to the console + ${If} $DesktopConsoleState == ${BST_CHECKED} + CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES + ; Set appUserModelId + ApplicationID::Set "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@" + ${Else} + !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO + ${EndIf} + ${If} @CLIENT_COMPONENT_CONDITIONAL@ ; check if the user asked for a desktop shortcut to High Fidelity ${If} $DesktopClientState == ${BST_CHECKED} @@ -901,38 +911,24 @@ Function HandlePostInstallOptions ${Else} !insertmacro WriteInstallOption @CLIENT_DESKTOP_SHORTCUT_REG_KEY@ NO ${EndIf} - ${EndIf} - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ; check if the user asked for a desktop shortcut to Sandbox - ${If} $DesktopServerState == ${BST_CHECKED} - CreateShortCut "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ YES - ; Set appUserModelId - ApplicationID::Set "$DESKTOP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@" - ${Else} - !insertmacro WriteInstallOption @CONSOLE_DESKTOP_SHORTCUT_REG_KEY@ NO - ${EndIf} + ; check if the user asked to have Console launched every startup + ${If} $ConsoleStartupState == ${BST_CHECKED} + ; in case we added a shortcut in the global context, pull that now + SetShellVarContext all + Delete "$SMSTARTUP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk" + ; make a startup shortcut in this user's current context + SetShellVarContext current + CreateShortCut "$SMSTARTUP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - ; check if the user asked to have Sandbox launched every startup - ${If} $ServerStartupState == ${BST_CHECKED} - ; in case we added a shortcut in the global context, pull that now - SetShellVarContext all - Delete "$SMSTARTUP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk" + ; reset the shell var context back + SetShellVarContext all - ; make a startup shortcut in this user's current context - SetShellVarContext current - CreateShortCut "$SMSTARTUP\@CONSOLE_HF_SHORTCUT_NAME@.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - - ; reset the shell var context back - SetShellVarContext all - - !insertmacro WriteInstallOption @CONSOLE_STARTUP_REG_KEY@ YES - ${Else} - !insertmacro WriteInstallOption @CONSOLE_STARTUP_REG_KEY@ NO - ${EndIf} + !insertmacro WriteInstallOption @CONSOLE_STARTUP_REG_KEY@ YES + ${Else} + !insertmacro WriteInstallOption @CONSOLE_STARTUP_REG_KEY@ NO ${EndIf} ; check if the user asked for a clean install @@ -982,16 +978,15 @@ Function HandlePostInstallOptions ${EndIf} ${EndIf} - ${If} @SERVER_COMPONENT_CONDITIONAL@ - ${AndIf} $LaunchServerNowState == ${BST_CHECKED} + ${If} $LaunchConsoleNowState == ${BST_CHECKED} !insertmacro WriteInstallOption @SERVER_LAUNCH_NOW_REG_KEY@ YES ; both launches use the explorer trick in case the user has elevated permissions for the installer ${If} $LaunchClientNowState == ${BST_CHECKED} !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ YES ; create shortcut with ARGUMENTS - CreateShortCut "$TEMP\SandboxShortcut.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" "-- --launchInterface" - Exec '"$WINDIR\explorer.exe" "$TEMP\SandboxShortcut.lnk"' + CreateShortCut "$TEMP\ConsoleShortcut.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" "-- --launchInterface" + Exec '"$WINDIR\explorer.exe" "$TEMP\ConsoleShortcut.lnk"' ${Else} !insertmacro WriteInstallOption @CLIENT_LAUNCH_NOW_REG_KEY@ NO Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"' @@ -1164,13 +1159,11 @@ Section "-Core installation" ${EndIf} - ; Conditional handling for server console shortcut - ${If} @SERVER_COMPONENT_CONDITIONAL@ - CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \ - "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" - ; Set appUserModelId - ApplicationID::Set "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@" - ${EndIf} + ; handling for server console shortcut + CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" \ + "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" + ; Set appUserModelId + ApplicationID::Set "$SMPROGRAMS\$STARTMENU_FOLDER\@CONSOLE_SHORTCUT_NAME@.lnk" "@APP_USER_MODEL_ID@" CreateShortCut "$SMPROGRAMS\$STARTMENU_FOLDER\Uninstall.lnk" "$INSTDIR\@UNINSTALLER_NAME@" diff --git a/server-console/CMakeLists.txt b/server-console/CMakeLists.txt index 1c6e40c582..bdcefda5d8 100644 --- a/server-console/CMakeLists.txt +++ b/server-console/CMakeLists.txt @@ -19,7 +19,7 @@ set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Server Console") set_target_properties(${TARGET_NAME}-npm-install PROPERTIES FOLDER "hidden/Server Console") # add a dependency from the package target to the server components -add_dependencies(${TARGET_NAME} assignment-client domain-server) +add_dependencies(${TARGET_NAME} assignment-client domain-server interface) # set the packaged console folder depending on platform, so we can copy it if (APPLE) @@ -36,6 +36,7 @@ if (APPLE) PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGED_CONSOLE_FOLDER}" DESTINATION ${CONSOLE_INSTALL_DIR} COMPONENT ${SERVER_COMPONENT} + COMPONENT ${CLIENT_COMPONENT} ) elseif (WIN32) set(CONSOLE_DESTINATION "${CMAKE_CURRENT_BINARY_DIR}/${PACKAGED_CONSOLE_FOLDER}") @@ -44,6 +45,7 @@ elseif (WIN32) DIRECTORY "${CONSOLE_DESTINATION}/" DESTINATION ${CONSOLE_INSTALL_DIR} COMPONENT ${SERVER_COMPONENT} + COMPONENT ${CLIENT_COMPONENT} ) # sign the copied server console executable after install diff --git a/server-console/src/main.js b/server-console/src/main.js index 95b5935255..08692fbd50 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -104,12 +104,12 @@ userConfig.load(configPath); const ipcMain = electron.ipcMain; -function isServerInstalled() { - return interfacePath && userConfig.get("serverInstalled", true); +function isInterfaceInstalled() { + return interfacePath; } -function isInterfaceInstalled() { - return dsPath && acPath && userConfig.get("interfaceInstalled", true); +function isServerInstalled() { + return dsPath && acPath; } var isShuttingDown = false; @@ -263,6 +263,10 @@ interfacePath = pathFinder.discoveredPath("Interface", binaryType, buildInfo.rel dsPath = pathFinder.discoveredPath("domain-server", binaryType, buildInfo.releaseType); acPath = pathFinder.discoveredPath("assignment-client", binaryType, buildInfo.releaseType); +console.log("Domain Server Path: " + dsPath); +console.log("Assignment Client Path: " + acPath); +console.log("Interface Path: " + interfacePath); + function binaryMissingMessage(displayName, executableName, required) { var message = "The " + displayName + " executable was not found.\n"; @@ -286,18 +290,6 @@ function binaryMissingMessage(displayName, executableName, required) { return message; } -// if at this point any of the paths are null, we're missing something we wanted to find - -if (!dsPath) { - dialog.showErrorBox("Domain Server Not Found", binaryMissingMessage("domain-server", "domain-server", true)); - app.exit(0); -} - -if (!acPath) { - dialog.showErrorBox("Assignment Client Not Found", binaryMissingMessage("assignment-client", "assignment-client", true)); - app.exit(0); -} - function openFileBrowser(path) { // Add quotes around path path = '"' + path + '"'; @@ -815,33 +807,33 @@ function onContentLoaded() { // Disable splash window for now. // maybeShowSplash(); - if (buildInfo.releaseType == 'PRODUCTION' && !argv.noUpdater) { - - const CHECK_FOR_UPDATES_INTERVAL_SECONDS = 60 * 30; - var hasShownUpdateNotification = false; - const updateChecker = new updater.UpdateChecker(buildInfo, CHECK_FOR_UPDATES_INTERVAL_SECONDS); - updateChecker.on('update-available', function(latestVersion, url) { - if (!hasShownUpdateNotification) { - notifier.notify({ - icon: notificationIcon, - title: 'An update is available!', - message: 'High Fidelity version ' + latestVersion + ' is available', - wait: true, - appID: buildInfo.appUserModelId, - url: url - }); - hasShownUpdateNotification = true; - } - }); - notifier.on('click', function(notifierObject, options) { - log.debug("Got click", options.url); - shell.openExternal(options.url); - }); - } - - deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX); - if (isServerInstalled()) { + if (buildInfo.releaseType == 'PRODUCTION' && !argv.noUpdater) { + + const CHECK_FOR_UPDATES_INTERVAL_SECONDS = 60 * 30; + var hasShownUpdateNotification = false; + const updateChecker = new updater.UpdateChecker(buildInfo, CHECK_FOR_UPDATES_INTERVAL_SECONDS); + updateChecker.on('update-available', function(latestVersion, url) { + if (!hasShownUpdateNotification) { + notifier.notify({ + icon: notificationIcon, + title: 'An update is available!', + message: 'High Fidelity version ' + latestVersion + ' is available', + wait: true, + appID: buildInfo.appUserModelId, + url: url + }); + hasShownUpdateNotification = true; + } + }); + notifier.on('click', function(notifierObject, options) { + log.debug("Got click", options.url); + shell.openExternal(options.url); + }); + } + + deleteOldFiles(logPath, DELETE_LOG_FILES_OLDER_THAN_X_SECONDS, LOG_FILE_REGEX); + var dsArguments = ['--get-temp-name', '--parent-pid', process.pid]; domainServer = new Process('domain-server', dsPath, dsArguments, logPath); diff --git a/server-console/src/modules/hf-app.js b/server-console/src/modules/hf-app.js index 625715b392..1b1171baef 100644 --- a/server-console/src/modules/hf-app.js +++ b/server-console/src/modules/hf-app.js @@ -34,7 +34,7 @@ exports.getBuildInfo = function() { buildNumber: "0", stableBuild: "0", organization: "High Fidelity - dev", - appUserModelId: "com.highfidelity.sandbox-dev" + appUserModelId: "com.highfidelity.console" }; var buildInfo = DEFAULT_BUILD_INFO; From 6877af04f118edf56562113d60ed28b951603c28 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 26 Sep 2018 14:57:32 -0700 Subject: [PATCH 62/63] Fix GOTO notifs; fix WALLET notifs (pending backend change) --- scripts/system/commerce/wallet.js | 54 ++----------------------------- scripts/system/tablet-goto.js | 8 ++--- 2 files changed, 7 insertions(+), 55 deletions(-) diff --git a/scripts/system/commerce/wallet.js b/scripts/system/commerce/wallet.js index 639c8aa0b3..c0e044c84b 100644 --- a/scripts/system/commerce/wallet.js +++ b/scripts/system/commerce/wallet.js @@ -501,41 +501,8 @@ function notificationDataProcessPage(data) { var shouldShowDot = false; function notificationPollCallback(historyArray) { - var i; - var someoneElsePurchasedArray = []; - var proofIssuedArray = []; - var moneyReceivedArray = []; - var giftReceivedArray = []; - for (i = 0; i < historyArray.length; i++) { - var currentHistoryTxn = historyArray[i]; - - if (currentHistoryTxn.sent_certs <= 0 && - currentHistoryTxn.received_certs <= 0) { - // This is an HFC transfer. - if (currentHistoryTxn.received_money > 0) { - if (currentHistoryTxn.sender_name === "marketplace") { - someoneElsePurchasedArray.push(currentHistoryTxn); - } else { - moneyReceivedArray.push(currentHistoryTxn); - } - } - } else if (currentHistoryTxn.sent_money <= 0 && - currentHistoryTxn.received_money <= 0 && - currentHistoryTxn.received_certs > 0) { - // This is a non-HFC asset transfer. - if (currentHistoryTxn.sender_name === "marketplace") { - proofIssuedArray.push(currentHistoryTxn); - } else { - giftReceivedArray.push(currentHistoryTxn); - } - } - } - if (!ui.isOpen) { - var notificationCount = someoneElsePurchasedArray.length + - proofIssuedArray.length + - moneyReceivedArray.length + - giftReceivedArray.length; + var notificationCount = historyArray.length; shouldShowDot = shouldShowDot || notificationCount > 0; ui.messagesWaiting(shouldShowDot); @@ -546,23 +513,8 @@ function notificationPollCallback(historyArray) { "transaction" + (notificationCount === 1 ? "" : "s") + ". Open WALLET to see all activity."; ui.notificationDisplayBanner(message); } else { - for (i = 0; i < someoneElsePurchasedArray.length; i++) { - message = '"' + (someoneElsePurchasedArray[i].message) + '" ' + - "Open WALLET to see all activity."; - ui.notificationDisplayBanner(message); - } - for (i = 0; i < proofIssuedArray.length; i++) { - message = '"' + (proofIssuedArray[i].message) + '" ' + - "Open WALLET to see all activity."; - ui.notificationDisplayBanner(message); - } - for (i = 0; i < moneyReceivedArray.length; i++) { - message = '"' + (moneyReceivedArray[i].message) + '" ' + - "Open WALLET to see all activity."; - ui.notificationDisplayBanner(message); - } - for (i = 0; i < giftReceivedArray.length; i++) { - message = '"' + (giftReceivedArray[i].message) + '" ' + + for (var i = 0; i < notificationCount; i++) { + message = '"' + (historyArray[i].message) + '" ' + "Open WALLET to see all activity."; ui.notificationDisplayBanner(message); } diff --git a/scripts/system/tablet-goto.js b/scripts/system/tablet-goto.js index 902e1b7fef..6d8ba3a927 100644 --- a/scripts/system/tablet-goto.js +++ b/scripts/system/tablet-goto.js @@ -57,16 +57,16 @@ function notificationPollCallback(userStoriesArray) { storedAnnouncements[story.id] = story; if (shouldNotifyIndividually) { - message = storedAnnouncements[key].username + " says something is happening in " + - storedAnnouncements[key].place_name + ". Open GOTO to join them."; + message = story.username + " says something is happening in " + + story.place_name + ". Open GOTO to join them."; ui.notificationDisplayBanner(message); } } else if (story.audience === "for_feed") { storedFeaturedStories[story.id] = story; if (shouldNotifyIndividually) { - message = storedFeaturedStories[key].username + " invites you to an event in " + - storedFeaturedStories[key].place_name + ". Open GOTO to join them."; + message = story.username + " invites you to an event in " + + story.place_name + ". Open GOTO to join them."; ui.notificationDisplayBanner(message); } } From 330789471d1b40a72f41f29e195de24eabd811b0 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Thu, 27 Sep 2018 12:03:37 -0700 Subject: [PATCH 63/63] Fix merge conflict and set proper polling times --- scripts/modules/appUi.js | 28 +++++++++++++------ .../src/modules/hf-notifications.js | 8 +++--- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/scripts/modules/appUi.js b/scripts/modules/appUi.js index 0e7461c5f1..83d99cd42b 100644 --- a/scripts/modules/appUi.js +++ b/scripts/modules/appUi.js @@ -107,7 +107,9 @@ function AppUi(properties) { that.notificationPollCaresAboutSince = false; that.notificationInitialCallbackMade = false; that.notificationDisplayBanner = function (message) { - Window.displayAnnouncement(message); + if (!that.isOpen) { + Window.displayAnnouncement(message); + } }; // // END Notification Handling Defaults @@ -118,6 +120,7 @@ function AppUi(properties) { // Set isOpen, wireEventBridge, set buttonActive as appropriate, // and finally call onOpened() or onClosed() IFF defined. that.setCurrentVisibleScreenMetadata(type, url); + if (that.checkIsOpen(type, url)) { that.wireEventBridge(true); if (!that.isOpen) { @@ -155,17 +158,21 @@ function AppUi(properties) { return; } - // User is "appearing offline" - if (GlobalServices.findableBy === "none") { + // User is "appearing offline" or is offline + if (GlobalServices.findableBy === "none" || Account.username === "") { that.notificationPollTimeout = Script.setTimeout(that.notificationPoll, that.notificationPollTimeoutMs); return; } var url = METAVERSE_BASE + that.notificationPollEndpoint; + var settingsKey = "notifications/" + that.buttonName + "/lastPoll"; + var currentTimestamp = new Date().getTime(); + var lastPollTimestamp = Settings.getValue(settingsKey, currentTimestamp); if (that.notificationPollCaresAboutSince) { - url = url + "&since=" + (new Date().getTime()); + url = url + "&since=" + lastPollTimestamp/1000; } + Settings.setValue(settingsKey, currentTimestamp); console.debug(that.buttonName, 'polling for notifications at endpoint', url); @@ -193,17 +200,18 @@ function AppUi(properties) { } else { concatenatedServerResponse = concatenatedServerResponse.concat(that.notificationDataProcessPage(response)); currentDataPageToRetrieve++; - request({ uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback); + request({ json: true, uri: (url + "&page=" + currentDataPageToRetrieve) }, requestCallback); } } - request({ uri: url }, requestCallback); + request({ json: true, uri: url }, requestCallback); }; // This won't do anything if there isn't a notification endpoint set that.notificationPoll(); - function availabilityChanged() { + function restartNotificationPoll() { + that.notificationInitialCallbackMade = false; if (that.notificationPollTimeout) { Script.clearTimeout(that.notificationPollTimeout); that.notificationPollTimeout = false; @@ -303,7 +311,8 @@ function AppUi(properties) { } : that.ignore; that.onScriptEnding = function onScriptEnding() { // Close if necessary, clean up any remaining handlers, and remove the button. - GlobalServices.findableByChanged.disconnect(availabilityChanged); + GlobalServices.myUsernameChanged.disconnect(restartNotificationPoll); + GlobalServices.findableByChanged.disconnect(restartNotificationPoll); if (that.isOpen) { that.close(); } @@ -323,7 +332,8 @@ function AppUi(properties) { that.tablet.screenChanged.connect(that.onScreenChanged); that.button.clicked.connect(that.onClicked); Script.scriptEnding.connect(that.onScriptEnding); - GlobalServices.findableByChanged.connect(availabilityChanged); + GlobalServices.findableByChanged.connect(restartNotificationPoll); + GlobalServices.myUsernameChanged.connect(restartNotificationPoll); if (that.buttonName == Settings.getValue("startUpApp")) { Settings.setValue("startUpApp", ""); Script.setTimeout(function () { diff --git a/server-console/src/modules/hf-notifications.js b/server-console/src/modules/hf-notifications.js index a9ee2489a9..281ca1cb53 100644 --- a/server-console/src/modules/hf-notifications.js +++ b/server-console/src/modules/hf-notifications.js @@ -9,10 +9,10 @@ const GetBuildInfo = hfApp.getBuildInfo; const buildInfo = GetBuildInfo(); const notificationIcon = path.join(__dirname, '../../resources/console-notification.png'); -const STORIES_NOTIFICATION_POLL_TIME_MS = 15 * 1000; // 120 * 1000; -const PEOPLE_NOTIFICATION_POLL_TIME_MS = 15 * 1000; // 120 * 1000; -const WALLET_NOTIFICATION_POLL_TIME_MS = 15 * 1000; // 600 * 1000; -const MARKETPLACE_NOTIFICATION_POLL_TIME_MS = 15 * 1000; // 600 * 1000; +const STORIES_NOTIFICATION_POLL_TIME_MS = 120 * 1000; +const PEOPLE_NOTIFICATION_POLL_TIME_MS = 120 * 1000; +const WALLET_NOTIFICATION_POLL_TIME_MS = 600 * 1000; +const MARKETPLACE_NOTIFICATION_POLL_TIME_MS = 600 * 1000; const METAVERSE_SERVER_URL= process.env.HIFI_METAVERSE_URL ? process.env.HIFI_METAVERSE_URL : 'https://metaverse.highfidelity.com' const STORIES_URL= '/api/v1/user_stories';