From 3b15548ea3dec0e62cbb9e6ad7acf0f90c8cc536 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Wed, 26 Sep 2018 14:32:25 -0700 Subject: [PATCH 01/16] Display resource-access events in marketplace item tester Includes refactoring marketplace item tester into a single-page app. --- assignment-client/src/Agent.cpp | 5 +- .../MarketplaceItemTester.qml | 364 ++++++++++++------ interface/src/Application.cpp | 16 +- interface/src/Application.h | 2 +- interface/src/assets/ATPAssetMigrator.cpp | 17 +- interface/src/commerce/QmlCommerce.cpp | 3 +- .../scripting/ClipboardScriptingInterface.cpp | 10 +- .../scripting/ClipboardScriptingInterface.h | 6 +- interface/src/ui/overlays/Web3DOverlay.cpp | 7 +- libraries/avatars/src/AvatarData.cpp | 27 +- libraries/entities/src/EntityEditFilters.cpp | 37 +- libraries/entities/src/EntityTree.cpp | 16 +- libraries/entities/src/EntityTree.h | 10 +- libraries/fbx/src/GLTFReader.cpp | 193 +++++----- libraries/fbx/src/OBJReader.cpp | 29 +- .../src/model-networking/TextureCache.cpp | 6 +- .../networking/src/AssetResourceRequest.cpp | 12 +- .../networking/src/AssetResourceRequest.h | 6 +- libraries/networking/src/AtpReply.cpp | 3 +- .../networking/src/FileResourceRequest.h | 11 +- .../networking/src/HTTPResourceRequest.h | 10 +- .../networking/src/NetworkAccessManager.cpp | 2 +- libraries/networking/src/ResourceCache.cpp | 70 ++-- libraries/networking/src/ResourceManager.cpp | 19 +- libraries/networking/src/ResourceManager.h | 7 +- libraries/networking/src/ResourceRequest.cpp | 7 +- libraries/networking/src/ResourceRequest.h | 23 +- libraries/octree/src/Octree.cpp | 39 +- libraries/octree/src/Octree.h | 11 +- libraries/qml/src/qml/OffscreenSurface.cpp | 5 + .../src/FileScriptingInterface.cpp | 7 +- libraries/script-engine/src/ScriptCache.cpp | 6 +- .../script-engine/src/XMLHttpRequestClass.cpp | 7 +- .../EntityItemWeakPointerWithUrlAncestry.h | 31 ++ libraries/shared/src/QUrlAncestry.cpp | 35 ++ libraries/shared/src/QUrlAncestry.h | 32 ++ .../shared/src/ResourceRequestObserver.cpp | 38 ++ .../shared/src/ResourceRequestObserver.h | 31 ++ scripts/system/marketplaces/marketplaces.js | 59 ++- 39 files changed, 841 insertions(+), 378 deletions(-) create mode 100644 libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h create mode 100644 libraries/shared/src/QUrlAncestry.cpp create mode 100644 libraries/shared/src/QUrlAncestry.h create mode 100644 libraries/shared/src/ResourceRequestObserver.cpp create mode 100644 libraries/shared/src/ResourceRequestObserver.h diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 639e9f924b..5f1e1ca74a 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -222,7 +222,8 @@ void Agent::requestScript() { return; } - auto request = DependencyManager::get()->createResourceRequest(this, scriptURL); + auto request = DependencyManager::get()->createResourceRequest( + this, scriptURL, true, -1, "Agent::requestScript"); if (!request) { qWarning() << "Could not create ResourceRequest for Agent script at" << scriptURL.toString(); @@ -896,7 +897,7 @@ void Agent::aboutToFinish() { { DependencyManager::get()->shutdownScripting(); } - + DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index c3d87ca2f5..98b0355e81 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -4,7 +4,7 @@ // // Load items not in the marketplace for testing purposes // -// Created by Zach Fox on 2018-09-05 +// Created by Kerry Ivan Kurian on 2018-09-05 // Copyright 2018 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. @@ -26,7 +26,9 @@ Rectangle { id: root property string installedApps + property string resourceAccessEventText property var nextResourceObjectId: 0 + property var startDate signal sendToScript(var message) HifiStylesUit.HifiConstants { id: hifi } @@ -51,9 +53,28 @@ Rectangle { spinner.visible = false; break; case "nextObjectIdInTest": + print("!!!! message from script! " + JSON.stringify(message)); nextResourceObjectId = message.id; spinner.visible = false; break; + case "resourceRequestEvent": + try { + var date = new Date(JSON.parse(message.data.date)); + } catch(err) { + print("!!!!! Date conversion failed: " + JSON.stringify(message.data)); + } + // XXX Eventually this date check goes away b/c we will + // be able to match up resouce access events to resource + // object ids, ignoring those that have -1 resource + // object ids. + if (date >= startDate) { + resourceAccessEventText += ( + message.data.callerId + " " + message.data.extra + + " " + message.data.url + + " [" + date.toISOString() + "]\n" + ); + } + break; } } @@ -64,7 +85,7 @@ Rectangle { resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); - return { "id": nextResourceObjectId++, + return { "resourceObjectId": nextResourceObjectId++, "resource": resource, "assetType": assetType }; } @@ -89,153 +110,240 @@ Rectangle { return httpPattern.test(resource) ? resource : "file:///" + resource; } - function rezEntity(resource, entityType) { + function rezEntity(resource, entityType, resourceObjectId) { + print("!!!! tester_rezClicked"); sendToScript({ method: 'tester_rezClicked', itemHref: toUrl(resource), - itemType: entityType}); + itemType: entityType, + itemId: resourceObjectId }); } - ListView { - anchors.fill: parent - anchors.leftMargin: 12 - anchors.bottomMargin: 40 - anchors.rightMargin: 12 - model: resourceListModel - spacing: 5 - interactive: false + Component.onCompleted: startDate = new Date() - delegate: RowLayout { + ColumnLayout { + id: rootColumn + spacing: 30 + + HifiStylesUit.RalewayRegular { + id: rootHeader + text: "Marketplace Item Tester" + height: 40 + width: paintedWidth + size: 22 + color: hifi.colors.black + anchors.top: parent.top + anchors.topMargin: 20 anchors.left: parent.left - width: parent.width - spacing: 5 + anchors.leftMargin: 12 + } - property var actions: { - "forward": function(resource, assetType){ - switch(assetType) { - case "application": - Commerce.openApp(resource); - break; - case "avatar": - MyAvatar.useFullAvatarURL(resource); - break; - case "content set": - urlHandler.handleUrl("hifi://localhost/0,0,0"); - Commerce.replaceContentSet(toUrl(resource), ""); - break; - case "entity": - case "wearable": - rezEntity(resource, assetType); - break; - default: - print("Marketplace item tester unsupported assetType " + assetType); - } - }, - "trash": function(resource, assetType){ - if ("application" === assetType) { - Commerce.uninstallApp(resource); - } - sendToScript({ - method: "tester_deleteResourceObject", - objectId: resourceListModel.get(index).id}); - resourceListModel.remove(index); - } - } + Rectangle { + height: root.height - 100 + width: root.width + anchors.left: parent.left - Column { - Layout.preferredWidth: root.width * .6 - 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 - } - } + ScrollView { + id: scrollView + anchors.fill: parent + anchors.rightMargin: 12 + anchors.bottom: parent.top + anchors.bottomMargin: 20 + anchors.leftMargin: 12 + verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn - ComboBox { - id: comboBox + frameVisible: false - Layout.preferredWidth: root.width * .2 + contentItem: ListView { + spacing: 20 + height: 200 + model: resourceListModel + interactive: false - model: [ - "application", - "avatar", - "content set", - "entity", - "wearable", - "unknown" - ] + delegate: Column { + spacing: 8 - currentIndex: (("entity or wearable" === assetType) ? - model.indexOf("unknown") : model.indexOf(assetType)) + RowLayout { + id: listRow + width: scrollView.width - 20 + anchors.rightMargin: scrollView.rightMargin + spacing: 5 - Component.onCompleted: { - onCurrentIndexChanged.connect(function() { - assetType = model[currentIndex]; - sendToScript({ - method: "tester_updateResourceObjectAssetType", - objectId: resourceListModel.get(index)["id"], - assetType: assetType }); - }); - } - } + property var actions: { + "forward": function(resource, assetType, resourceObjectId){ + switch(assetType) { + case "application": + Commerce.openApp(resource); + break; + case "avatar": + MyAvatar.useFullAvatarURL(resource); + break; + case "content set": + urlHandler.handleUrl("hifi://localhost/0,0,0"); + Commerce.replaceContentSet(toUrl(resource), ""); + break; + case "entity": + case "wearable": + rezEntity(resource, assetType, resourceObjectId); + break; + default: + print("Marketplace item tester unsupported assetType " + assetType); + } + }, + "trash": function(resource, assetType){ + if ("application" === assetType) { + Commerce.uninstallApp(resource); + } + sendToScript({ + method: "tester_deleteResourceObject", + objectId: resourceListModel.get(index).id}); + resourceListModel.remove(index); + } + } - Repeater { - model: [ "forward", "trash" ] + Column { + Layout.preferredWidth: scrollView.width * .6 + spacing: 5 + Text { + width: listRow.width * .6 + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + font.pointSize: 12 + horizontalAlignment: Text.AlignBottom + wrapMode: Text.WrapAnywhere + } + Text { + width: listRow.width * .6 + text: resource + font.pointSize: 8 + horizontalAlignment: Text.AlignBottom + wrapMode: Text.WrapAnywhere + } + } - HifiStylesUit.HiFiGlyphs { - 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 { - anchors.fill: parent - onClicked: { - actions[modelData](resource, comboBox.currentText); + ComboBox { + id: comboBox + + Layout.preferredWidth: listRow.width * .2 + + model: [ + "application", + "avatar", + "content set", + "entity", + "wearable", + "unknown" + ] + + currentIndex: (("entity or wearable" === assetType) ? + model.indexOf("unknown") : model.indexOf(assetType)) + + Component.onCompleted: { + onCurrentIndexChanged.connect(function() { + assetType = model[currentIndex]; + sendToScript({ + method: "tester_updateResourceObjectAssetType", + objectId: resourceListModel.get(index)["resourceObjectId"], + assetType: assetType }); + }); + } + } + + Repeater { + model: [ "forward", "trash" ] + + HifiStylesUit.HiFiGlyphs { + 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 { + anchors.fill: parent + onClicked: { + listRow.actions[modelData](resource, comboBox.currentText, resourceObjectId); + } + } + } + } + } + + Rectangle { + id: detailsContainer + + width: scrollView.width - 20 + height: resourceDetails.isOpen ? 300 : 20 + anchors.left: parent.left + + HifiStylesUit.HiFiGlyphs { + id: detailsToggle + anchors.top: parent.top + text: resourceDetails.isOpen ? hifi.glyphs.minimize : hifi.glyphs.maximize + color: hifi.colors.black + size: 22 + verticalAlignment: Text.AlignBottom + MouseArea { + anchors.fill: parent + onClicked: resourceDetails.isOpen = !resourceDetails.isOpen + } + } + + TextArea { + id: resourceDetails + + property var isOpen: false + + width: detailsContainer.width - 20 + height: detailsContainer.height + anchors.top: parent.top + anchors.left: detailsToggle.left + anchors.leftMargin: 20 + verticalScrollBarPolicy: isOpen ? Qt.ScrollBarAsNeeded : Qt.ScrollBarAlwaysOff + frameVisible: isOpen + readOnly: true + + text: { + if (isOpen) { + return resourceAccessEventText + } else { + return (resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." + } + } + font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) + wrapMode: TextEdit.NoWrap + } + } + + Rectangle { + width: listRow.width + height: 1 + color: hifi.colors.black } } } } } - 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 { + Row { id: rootActions spacing: 20 - anchors.horizontalCenter: parent.horizontalCenter + + anchors.left: parent.left + anchors.leftMargin: root.width / 6 - 10 + anchors.bottomMargin: 40 + anchors.bottom: parent.bottom property string currentAction property var actions: { @@ -257,6 +365,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. + print("!!!! resource selected"); switch(currentAction) { case "load file": Window.browseChanged.disconnect(onResourceSelected); @@ -266,8 +375,11 @@ Rectangle { break; } if (resource) { + print("!!!! building resource object"); var resourceObj = buildResourceObj(resource); + print("!!!! installing resource object"); installResourceObj(resourceObj); + print("!!!! notifying script of resource object"); sendToScript({ method: 'tester_newResourceObject', resourceObject: resourceObj }); @@ -282,7 +394,7 @@ Rectangle { text: modelData width: root.width / 3 height: 40 - onClicked: actions[text]() + onClicked: rootActions.actions[text]() } } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2dc3645be0..4c580d7de7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -170,6 +170,7 @@ #include "ModelPackager.h" #include "scripting/Audio.h" #include "networking/CloseEventSender.h" +#include "QUrlAncestry.h" #include "scripting/TestScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" @@ -226,6 +227,7 @@ #include "commerce/Ledger.h" #include "commerce/Wallet.h" #include "commerce/QmlCommerce.h" +#include "ResourceRequestObserver.h" #include "webbrowser/WebBrowserSuggestionsEngine.h" #include @@ -945,8 +947,8 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); + DependencyManager::set(); return previousSessionCrashed; } @@ -1781,7 +1783,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateHeartbeat(); QTimer* settingsTimer = new QTimer(); moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{ - // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the + // 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]{ @@ -3129,6 +3131,7 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get().data()); surfaceContext->setContextProperty("Wallet", DependencyManager::get().data()); surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); + surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get())); @@ -5021,12 +5024,11 @@ void Application::saveSettings() const { PluginManager::getInstance()->saveSettings(); } -bool Application::importEntities(const QString& urlOrFilename) { +bool Application::importEntities(const QString& urlOrFilename, const bool isObservable, const qint64 callerId) { bool success = false; _entityClipboard->withWriteLock([&] { _entityClipboard->eraseAllOctreeElements(); - - success = _entityClipboard->readFromURL(urlOrFilename); + success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId, QUrlAncestry()); if (success) { _entityClipboard->reaverageOctreeElements(); } @@ -6810,6 +6812,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGlobalObject("Wallet", DependencyManager::get().data()); scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get().data()); scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance()); + scriptEngine->registerGlobalObject("ResourceRequestObserver", DependencyManager::get().data()); qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue); @@ -7196,7 +7199,8 @@ void Application::addAssetToWorldFromURL(QString url) { addAssetToWorldInfo(filename, "Downloading model file " + filename + "."); - auto request = DependencyManager::get()->createResourceRequest(nullptr, QUrl(url)); + auto request = DependencyManager::get()->createResourceRequest( + nullptr, QUrl(url), true, -1, "Application::addAssetToWorldFromURL"); connect(request, &ResourceRequest::finished, this, &Application::addAssetToWorldFromURLRequestFinished); request->send(); } diff --git a/interface/src/Application.h b/interface/src/Application.h index 75260b910f..d9f9591086 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -340,7 +340,7 @@ public slots: QVector pasteEntities(float x, float y, float z); bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr); bool exportEntities(const QString& filename, float x, float y, float z, float scale); - bool importEntities(const QString& url); + bool importEntities(const QString& url, const bool isObservable = true, const qint64 callerId = -1); void updateThreadPoolCount() const; void updateSystemTabletMode(); void goToErrorDomainURL(QUrl errorDomainURL); diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index 45ac80b054..6912c69db8 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -53,7 +53,8 @@ void ATPAssetMigrator::loadEntityServerFile() { auto migrateResources = [=](QUrl migrationURL, QJsonValueRef jsonValue, bool isModelURL) { auto request = - DependencyManager::get()->createResourceRequest(this, migrationURL); + DependencyManager::get()->createResourceRequest( + this, migrationURL, true, -1, "ATPAssetMigrator::loadEntityServerFile"); if (request) { qCDebug(asset_migrator) << "Requesting" << migrationURL << "for ATP asset migration"; @@ -202,7 +203,7 @@ void ATPAssetMigrator::loadEntityServerFile() { void ATPAssetMigrator::migrateResource(ResourceRequest* request) { // use an asset client to upload the asset auto assetClient = DependencyManager::get(); - + auto upload = assetClient->createUpload(request->getData()); // add this URL to our hash of AssetUpload to original URL @@ -242,7 +243,7 @@ void ATPAssetMigrator::assetUploadFinished(AssetUpload *upload, const QString& h } checkIfFinished(); - + upload->deleteLater(); } @@ -298,24 +299,24 @@ void ATPAssetMigrator::checkIfFinished() { bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) { static bool hasAskedForCompleteMigration { false }; static bool wantsCompleteMigration { false }; - + if (!hasAskedForCompleteMigration) { // this is the first resource migration - ask the user if they just want to migrate everything static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n"\ "Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n"\ "Select \"No\" to be prompted for each discovered asset." }; - + auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - + if (button == QMessageBox::Yes) { wantsCompleteMigration = true; } - + hasAskedForCompleteMigration = true; } - + if (wantsCompleteMigration) { return true; } else { diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index aa39fdc1b9..83907df103 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -325,7 +325,8 @@ bool QmlCommerce::installApp(const QString& itemHref) { QUrl appHref(itemHref); - auto request = DependencyManager::get()->createResourceRequest(this, appHref); + auto request = DependencyManager::get()->createResourceRequest( + this, appHref, true, -1, "QmlCommerce::installApp"); if (!request) { qCDebug(commerce) << "Couldn't create resource request for app."; diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index c2d2b69883..c14f4ea895 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -46,11 +46,17 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float return retVal; } -bool ClipboardScriptingInterface::importEntities(const QString& filename) { +bool ClipboardScriptingInterface::importEntities( + const QString& filename, + const bool isObservable, + const qint64 callerId +) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "importEntities", Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename)); + Q_ARG(const QString&, filename), + Q_ARG(const bool, isObservable), + Q_ARG(const qint64, callerId)); return retVal; } diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 32b8c64a7d..535ccfd5ab 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -50,9 +50,11 @@ public: * You can generate a JSON file using {@link Clipboard.exportEntities}. * @function Clipboard.importEntities * @param {string} filename Path and name of file to import. + * @param {boolean} does the ResourceRequestObserver observe this request? + * @param {number} optional internal id of object causing this import. * @returns {boolean} true if the import was successful, otherwise false. */ - Q_INVOKABLE bool importEntities(const QString& filename); + Q_INVOKABLE bool importEntities(const QString& filename, const bool isObservable = true, const qint64 callerId = -1); /**jsdoc * Export the entities specified to a JSON file. @@ -62,7 +64,7 @@ public: * @returns {boolean} true if the export was successful, otherwise false. */ Q_INVOKABLE bool exportEntities(const QString& filename, const QVector& entityIDs); - + /**jsdoc * Export the entities with centers within a cube to a JSON file. * @function Clipboard.exportEntities diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 9d55c91ef3..53505c2013 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -59,6 +59,8 @@ #include "raypick/PointerScriptingInterface.h" #include #include "AboutUtil.h" +#include "ResourceRequestObserver.h" + static int MAX_WINDOW_SIZE = 4096; static const float METERS_TO_INCHES = 39.3701f; @@ -269,6 +271,7 @@ void Web3DOverlay::setupQmlSurface(bool isTablet) { _webSurface->getSurfaceContext()->setContextProperty("Window", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface()); _webSurface->getSurfaceContext()->setContextProperty("HiFiAbout", AboutUtil::getInstance()); + _webSurface->getSurfaceContext()->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); // Override min fps for tablet UI, for silky smooth scrolling setMaxFPS(90); @@ -536,7 +539,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. * * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. @@ -561,7 +564,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {string} url - The URL of the Web page to display. * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. * @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay. - * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: + * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: * scale, size. * @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second. * @property {boolean} showKeyboardFocusHighlight=true - If true, the Web overlay is highlighted when it has diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index a7cd5ccb5e..aae4c3bbe8 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -44,6 +44,7 @@ #include "AvatarLogging.h" #include "AvatarTraits.h" #include "ClientTraitsHandler.h" +#include "ResourceRequestObserver.h" //#define WANT_DEBUG @@ -379,7 +380,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } else { AVATAR_MEMCPY(_globalPosition); } - + int numBytes = destinationBuffer - startSection; @@ -647,7 +648,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (!data.translationIsDefaultPose) { if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { - + validity |= (1 << validityBit); #ifdef WANT_DEBUG translationSentCount++; @@ -1054,7 +1055,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); - + bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); @@ -1526,7 +1527,7 @@ glm::vec3 AvatarData::getJointTranslation(int index) const { } glm::vec3 AvatarData::getJointTranslation(const QString& name) const { - // Can't do this, because the lock needs to cover the entire set of logic. In theory, the joints could change + // Can't do this, because the lock needs to cover the entire set of logic. In theory, the joints could change // on another thread in between the call to getJointIndex and getJointTranslation // return getJointTranslation(getJointIndex(name)); return readLockWithNamedJointIndex(name, [this](int index) { @@ -1607,7 +1608,7 @@ bool AvatarData::isJointDataValid(const QString& name) const { // return isJointDataValid(getJointIndex(name)); return readLockWithNamedJointIndex(name, false, [&](int index) { - // This is technically superfluous.... the lambda is only called if index is a valid + // This is technically superfluous.... the lambda is only called if index is a valid // offset for _jointData. Nevertheless, it would be confusing to leave the lamdba as // `return true` return index < _jointData.size(); @@ -1826,7 +1827,7 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { bytesWritten += destination.writePrimitive(traitVersion); } - + AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size(); bytesWritten += destination.writePrimitive(encodedURLSize); @@ -1935,7 +1936,7 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { if (expanded == _skeletonModelURL) { return; } - + _skeletonModelURL = expanded; qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString(); @@ -2161,11 +2162,21 @@ void AvatarData::updateJointMappings() { } if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { + //// + // TODO: Should we rely upon HTTPResourceRequest instead? + // HTTPResourceRequest::doSend() covers all of the following and + // then some. It doesn't cover the connect() call, so we may + // want to add a HTTPResourceRequest::doSend() method that does + // connects. QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + DependencyManager::get()->update( + _skeletonModelURL, -1, "AvatarData::updateJointMappings"); QNetworkReply* networkReply = networkAccessManager.get(networkRequest); + // + //// connect(networkReply, &QNetworkReply::finished, this, &AvatarData::setJointMappingsFromNetworkReply); } } @@ -2391,7 +2402,7 @@ QJsonObject AvatarData::toJson() const { for (auto entityID : _avatarEntityData.keys()) { QVariantMap entityData; QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID; - entityData.insert("id", newId); + entityData.insert("id", newId); entityData.insert("properties", _avatarEntityData.value(entityID).toBase64()); avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); } diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index 94df7eb465..83e105c0ad 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -24,7 +24,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { for (auto id : zoneIDs) { if (!id.isInvalidID()) { // for now, look it up in the tree (soon we need to cache or similar?) - EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); + EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); auto zone = std::dynamic_pointer_cast(itemPtr); if (!zone) { // TODO: maybe remove later? @@ -33,7 +33,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { zones.append(id); } } else { - // the null id is the global filter we put in the domain server's + // the null id is the global filter we put in the domain server's // advanced entity server settings zones.append(id); } @@ -43,7 +43,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType, EntityItemID& itemID, EntityItemPointer& existingEntity) { - + // get the ids of all the zones (plus the global entity edit filter) that the position // lies within auto zoneIDs = getZonesByPosition(position); @@ -51,12 +51,12 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper if (!itemID.isInvalidID() && id == itemID) { continue; } - - // get the filter pair, etc... + + // get the filter pair, etc... _lock.lockForRead(); FilterData filterData = _filterDataMap.value(id); _lock.unlock(); - + if (filterData.valid()) { if (filterData.rejectAll) { return false; @@ -153,13 +153,13 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // otherwise, assume it wants to pass all properties propertiesOut = propertiesIn; wasChanged = false; - + } else { return false; } } } - // if we made it here, + // if we made it here, return true; } @@ -175,23 +175,23 @@ void EntityEditFilters::removeFilter(EntityItemID entityID) { void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { QUrl scriptURL(filterURL); - - // setting it to an empty string is same as removing + + // setting it to an empty string is same as removing if (filterURL.size() == 0) { removeFilter(entityID); return; } - + // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; scriptRequestFinished(entityID); return; } - + // first remove any existing info for this entity removeFilter(entityID); - + // reject all edits until we load the script FilterData filterData; filterData.rejectAll = true; @@ -199,8 +199,9 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { _lock.lockForWrite(); _filterDataMap.insert(entityID, filterData); _lock.unlock(); - - auto scriptRequest = DependencyManager::get()->createResourceRequest(this, scriptURL); + + auto scriptRequest = DependencyManager::get()->createResourceRequest( + this, scriptURL, true, -1, "EntityEditFilters::addFilter"); if (!scriptRequest) { qWarning() << "Could not create ResourceRequest for Entity Edit filter script at" << scriptURL.toString(); scriptRequestFinished(entityID); @@ -264,7 +265,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { FilterData filterData; filterData.engine = engine; filterData.rejectAll = false; - + // define the uncaughtException function QScriptEngine& engineRef = *engine; filterData.uncaughtExceptions = [&engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; @@ -368,11 +369,11 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { _lock.unlock(); qDebug() << "script request filter processed for entity id " << entityID; - + emit filterAdded(entityID, true); return; } - } + } } else if (scriptRequest) { const QString urlString = scriptRequest->getUrl().toString(); qCritical() << "Failed to download script at" << urlString; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 0b3b8abba2..8992157681 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -38,6 +38,8 @@ #include "LogHandler.h" #include "EntityEditFilters.h" #include "EntityDynamicFactoryInterface.h" +#include "QUrlAncestry.h" + static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour @@ -98,7 +100,7 @@ EntityTree::~EntityTree() { eraseAllOctreeElements(false); } -void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { +void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { _entityScriptSourceWhitelist = entityScriptSourceWhitelist.split(',', QString::SkipEmptyParts); } @@ -860,7 +862,7 @@ float findRayIntersectionSortingOp(const OctreeElementPointer& element, void* ex EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, + bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { @@ -1351,7 +1353,7 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity key = sent.second; } - QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----\n"; + QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----\n"; QByteArray hashedActualNonce = QCryptographicHash::hash(QByteArray(actualNonce.toUtf8()), QCryptographicHash::Sha256); bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8())); @@ -1795,7 +1797,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c if (newEntity) { newEntity->markAsChangedOnServer(); notifyNewlyCreatedEntity(*newEntity, senderNode); - + startLogging = usecTimestampNow(); if (wantEditLogging()) { qCDebug(entities) << "User [" << senderNode->getUUID() << "] added entity. ID:" @@ -1820,7 +1822,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } } else { HIFI_FCDEBUG(entities(), "Edit failed. [" << message.getType() <<"] " << - "entity id:" << entityItemID << + "entity id:" << entityItemID << "existingEntity pointer:" << existingEntity.get()); } } @@ -2041,7 +2043,7 @@ bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) { if (hasSomethingNewer) { int elapsed = usecTimestampNow() - considerEntitiesSince; int difference = considerEntitiesSince - sinceTime; - qCDebug(entities) << "EntityTree::hasEntitiesDeletedSince() sinceTime:" << sinceTime + qCDebug(entities) << "EntityTree::hasEntitiesDeletedSince() sinceTime:" << sinceTime << "considerEntitiesSince:" << considerEntitiesSince << "elapsed:" << elapsed << "difference:" << difference; } #endif @@ -2493,7 +2495,7 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer return true; } -bool EntityTree::readFromMap(QVariantMap& map) { +bool EntityTree::readFromMap(QVariantMap& map, const QUrlAncestry& ancestry) { // These are needed to deal with older content (before adding inheritance modes) int contentVersion = map["Version"].toInt(); bool needsConversion = (contentVersion < (int)EntityVersion::ZoneLightInheritModes); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 2f971b8566..8c787f8eb8 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -22,6 +22,8 @@ #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" #include "MovingEntitiesOperator.h" +#include "QUrlAncestry.h" + class EntityTree; using EntityTreePointer = std::shared_ptr; @@ -94,7 +96,7 @@ public: virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, + bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); @@ -170,7 +172,7 @@ public: void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); - bool hasAnyDeletedEntities() const { + bool hasAnyDeletedEntities() const { QReadLocker locker(&_recentlyDeletedEntitiesLock); return _recentlyDeletedEntityItemIDs.size() > 0; } @@ -178,7 +180,7 @@ public: bool hasEntitiesDeletedSince(quint64 sinceTime); static quint64 getAdjustedConsiderSince(quint64 sinceTime); - QMultiMap getRecentlyDeletedEntityIDs() const { + QMultiMap getRecentlyDeletedEntityIDs() const { QReadLocker locker(&_recentlyDeletedEntitiesLock); return _recentlyDeletedEntityItemIDs; } @@ -223,7 +225,7 @@ public: virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) override; - virtual bool readFromMap(QVariantMap& entityDescription) override; + virtual bool readFromMap(QVariantMap& entityDescription, const QUrlAncestry& ancestry = {}) override; glm::vec3 getContentsDimensions(); float getContentsLargestDimension(); diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index 317342b886..89592c399c 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -40,7 +40,7 @@ GLTFReader::GLTFReader() { } -bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldname, QString& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isString()); if (_defined) { @@ -60,7 +60,7 @@ bool GLTFReader::getBoolVal(const QJsonObject& object, const QString& fieldname, return _defined; } -bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, int& value, QMap& defined) { bool _defined = (object.contains(fieldname) && !object[fieldname].isNull()); if (_defined) { @@ -70,7 +70,7 @@ bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, return _defined; } -bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldname, double& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isDouble()); if (_defined) { @@ -79,7 +79,7 @@ bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldnam defined.insert(fieldname, _defined); return _defined; } -bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldname, QJsonObject& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isObject()); if (_defined) { @@ -89,7 +89,7 @@ bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldnam return _defined; } -bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldname, QVector& values, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -104,7 +104,7 @@ bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldn return _defined; } -bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, QVector& values, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -119,7 +119,7 @@ bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fie return _defined; } -bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, QJsonArray& objects, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -229,7 +229,7 @@ bool GLTFReader::setAsset(const QJsonObject& object) { QJsonObject jsAsset; bool isAssetDefined = getObjectVal(object, "asset", jsAsset, _file.defined); if (isAssetDefined) { - if (!getStringVal(jsAsset, "version", _file.asset.version, + if (!getStringVal(jsAsset, "version", _file.asset.version, _file.asset.defined) || _file.asset.version != "2.0") { return false; } @@ -241,7 +241,7 @@ bool GLTFReader::setAsset(const QJsonObject& object) { bool GLTFReader::addAccessor(const QJsonObject& object) { GLTFAccessor accessor; - + getIntVal(object, "bufferView", accessor.bufferView, accessor.defined); getIntVal(object, "byteOffset", accessor.byteOffset, accessor.defined); getIntVal(object, "componentType", accessor.componentType, accessor.defined); @@ -261,7 +261,7 @@ bool GLTFReader::addAccessor(const QJsonObject& object) { bool GLTFReader::addAnimation(const QJsonObject& object) { GLTFAnimation animation; - + QJsonArray channels; if (getObjectArrayVal(object, "channels", channels, animation.defined)) { foreach(const QJsonValue & v, channels) { @@ -272,7 +272,7 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { if (getObjectVal(v.toObject(), "target", jsChannel, channel.defined)) { getIntVal(jsChannel, "node", channel.target.node, channel.target.defined); getIntVal(jsChannel, "path", channel.target.path, channel.target.defined); - } + } } } } @@ -291,7 +291,7 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { } } } - + _file.animations.push_back(animation); return true; @@ -299,20 +299,20 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { bool GLTFReader::addBufferView(const QJsonObject& object) { GLTFBufferView bufferview; - + getIntVal(object, "buffer", bufferview.buffer, bufferview.defined); getIntVal(object, "byteLength", bufferview.byteLength, bufferview.defined); getIntVal(object, "byteOffset", bufferview.byteOffset, bufferview.defined); getIntVal(object, "target", bufferview.target, bufferview.defined); - + _file.bufferviews.push_back(bufferview); - + return true; } bool GLTFReader::addBuffer(const QJsonObject& object) { GLTFBuffer buffer; - + getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); if (getStringVal(object, "uri", buffer.uri, buffer.defined)) { if (!readBinary(buffer.uri, buffer.blob)) { @@ -320,13 +320,13 @@ bool GLTFReader::addBuffer(const QJsonObject& object) { } } _file.buffers.push_back(buffer); - + return true; } bool GLTFReader::addCamera(const QJsonObject& object) { GLTFCamera camera; - + QJsonObject jsPerspective; QJsonObject jsOrthographic; QString type; @@ -346,28 +346,28 @@ bool GLTFReader::addCamera(const QJsonObject& object) { } else if (getStringVal(object, "type", type, camera.defined)) { camera.type = getCameraType(type); } - + _file.cameras.push_back(camera); - + return true; } bool GLTFReader::addImage(const QJsonObject& object) { GLTFImage image; - + QString mime; getStringVal(object, "uri", image.uri, image.defined); if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); } getIntVal(object, "bufferView", image.bufferView, image.defined); - + _file.images.push_back(image); return true; } -bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& field, +bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& field, int& outidx, QMap& defined) { QJsonObject subobject; if (getObjectVal(object, field, subobject, defined)) { @@ -393,20 +393,20 @@ bool GLTFReader::addMaterial(const QJsonObject& object) { getDoubleVal(object, "alphaCutoff", material.alphaCutoff, material.defined); QJsonObject jsMetallicRoughness; if (getObjectVal(object, "pbrMetallicRoughness", jsMetallicRoughness, material.defined)) { - getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", - material.pbrMetallicRoughness.baseColorFactor, + getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", + material.pbrMetallicRoughness.baseColorFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "baseColorTexture", - material.pbrMetallicRoughness.baseColorTexture, + getIndexFromObject(jsMetallicRoughness, "baseColorTexture", + material.pbrMetallicRoughness.baseColorTexture, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "metallicFactor", - material.pbrMetallicRoughness.metallicFactor, + getDoubleVal(jsMetallicRoughness, "metallicFactor", + material.pbrMetallicRoughness.metallicFactor, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "roughnessFactor", - material.pbrMetallicRoughness.roughnessFactor, + getDoubleVal(jsMetallicRoughness, "roughnessFactor", + material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", - material.pbrMetallicRoughness.metallicRoughnessTexture, + getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", + material.pbrMetallicRoughness.metallicRoughnessTexture, material.pbrMetallicRoughness.defined); } _file.materials.push_back(material); @@ -428,7 +428,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { getIntVal(jsPrimitive, "mode", primitive.mode, primitive.defined); getIntVal(jsPrimitive, "indices", primitive.indices, primitive.defined); getIntVal(jsPrimitive, "material", primitive.material, primitive.defined); - + QJsonObject jsAttributes; if (getObjectVal(jsPrimitive, "attributes", jsAttributes, primitive.defined)) { QStringList attrKeys = jsAttributes.keys(); @@ -455,7 +455,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { primitive.targets.push_back(target); } } - } + } mesh.primitives.push_back(primitive); } } @@ -469,7 +469,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { bool GLTFReader::addNode(const QJsonObject& object) { GLTFNode node; - + getStringVal(object, "name", node.name, node.defined); getIntVal(object, "camera", node.camera, node.defined); getIntVal(object, "mesh", node.mesh, node.defined); @@ -524,10 +524,10 @@ bool GLTFReader::addSkin(const QJsonObject& object) { } bool GLTFReader::addTexture(const QJsonObject& object) { - GLTFTexture texture; + GLTFTexture texture; getIntVal(object, "sampler", texture.sampler, texture.defined); getIntVal(object, "source", texture.source, texture.defined); - + _file.textures.push_back(texture); return true; @@ -535,7 +535,7 @@ bool GLTFReader::addTexture(const QJsonObject& object) { bool GLTFReader::parseGLTF(const QByteArray& model) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); - + QJsonDocument d = QJsonDocument::fromJson(model); QJsonObject jsFile = d.object(); @@ -707,25 +707,25 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { foreach(int child, node.children) nodeDependencies[child].push_back(nodecount); nodecount++; } - + nodecount = 0; foreach(auto &node, _file.nodes) { // collect node transform - _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); + _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); if (nodeDependencies[nodecount].size() == 1) { int parentidx = nodeDependencies[nodecount][0]; while (true) { // iterate parents // collect parents transforms - _file.nodes[nodecount].transforms.push_back(getModelTransform(_file.nodes[parentidx])); + _file.nodes[nodecount].transforms.push_back(getModelTransform(_file.nodes[parentidx])); if (nodeDependencies[parentidx].size() == 1) { parentidx = nodeDependencies[parentidx][0]; } else break; } } - + nodecount++; } - + //Build default joints geometry.joints.resize(1); geometry.joints[0].isFree = false; @@ -756,7 +756,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { setFBXMaterial(fbxMaterial, _file.materials[i]); } - + nodecount = 0; // Build meshes @@ -789,11 +789,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { QVector raw_vertices; QVector raw_normals; - bool success = addArrayOfType(indicesBuffer.blob, - indicesBufferview.byteOffset + indicesAccBoffset, - indicesAccessor.count, - part.triangleIndices, - indicesAccessor.type, + bool success = addArrayOfType(indicesBuffer.blob, + indicesBufferview.byteOffset + indicesAccBoffset, + indicesAccessor.count, + part.triangleIndices, + indicesAccessor.type, indicesAccessor.componentType); if (!success) { @@ -813,10 +813,10 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0; if (key == "POSITION") { QVector vertices; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, vertices, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, vertices, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; @@ -827,11 +827,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "NORMAL") { QVector normals; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - normals, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + normals, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; @@ -842,11 +842,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "TEXCOORD_0") { QVector texcoords; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - texcoords, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + texcoords, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; @@ -857,11 +857,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "TEXCOORD_1") { QVector texcoords; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - texcoords, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + texcoords, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; @@ -888,8 +888,8 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { mesh.meshExtents.addPoint(vertex); geometry.meshExtents.addPoint(vertex); } - - // since mesh.modelTransform seems to not have any effect I apply the transformation the model + + // since mesh.modelTransform seems to not have any effect I apply the transformation the model for (int h = 0; h < mesh.vertices.size(); h++) { glm::vec4 ver = glm::vec4(mesh.vertices[h], 1); if (node.transforms.size() > 0) { @@ -901,18 +901,18 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { mesh.meshIndex = geometry.meshes.size(); FBXReader::buildModelMesh(mesh, url.toString()); } - + } nodecount++; } - + return true; } -FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, +FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) { - + _url = url; // Normalize url for local files @@ -928,10 +928,10 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping FBXGeometry& geometry = *geometryPtr; buildGeometry(geometry, url); - + //fbxDebugDump(geometry); return geometryPtr; - + } bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { @@ -940,7 +940,7 @@ bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url; bool success; std::tie(success, outdata) = requestData(binaryUrl); - + return success; } @@ -953,7 +953,8 @@ bool GLTFReader::doesResourceExist(const QString& url) { } std::tuple GLTFReader::requestData(QUrl& url) { - auto request = DependencyManager::get()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest( + nullptr, url, true, -1, "GLTFReader::requestData"); if (!request) { return std::make_tuple(false, QByteArray()); @@ -999,7 +1000,7 @@ QNetworkReply* GLTFReader::request(QUrl& url, bool isTest) { FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { FBXTexture fbxtex = FBXTexture(); fbxtex.texcoordSet = 0; - + if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; QString fname = QUrl(url).fileName(); @@ -1019,10 +1020,10 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.defined["name"]) { fbxmat.name = fbxmat.materialID = material.name; } - + if (material.defined["emissiveFactor"] && material.emissiveFactor.size() == 3) { - glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], - material.emissiveFactor[1], + glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], + material.emissiveFactor[1], material.emissiveFactor[2]); fbxmat._material->setEmissive(emissive); } @@ -1031,12 +1032,12 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia fbxmat.emissiveTexture = getFBXTexture(_file.textures[material.emissiveTexture]); fbxmat.useEmissiveMap = true; } - + if (material.defined["normalTexture"]) { fbxmat.normalTexture = getFBXTexture(_file.textures[material.normalTexture]); fbxmat.useNormalMap = true; } - + if (material.defined["occlusionTexture"]) { fbxmat.occlusionTexture = getFBXTexture(_file.textures[material.occlusionTexture]); fbxmat.useOcclusionMap = true; @@ -1044,7 +1045,7 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.defined["pbrMetallicRoughness"]) { fbxmat.isPBSMaterial = true; - + if (material.pbrMetallicRoughness.defined["metallicFactor"]) { fbxmat.metallic = material.pbrMetallicRoughness.metallicFactor; } @@ -1062,23 +1063,23 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { fbxmat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor); } - if (material.pbrMetallicRoughness.defined["baseColorFactor"] && + if (material.pbrMetallicRoughness.defined["baseColorFactor"] && material.pbrMetallicRoughness.baseColorFactor.size() == 4) { - glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], - material.pbrMetallicRoughness.baseColorFactor[1], + glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], + material.pbrMetallicRoughness.baseColorFactor[1], material.pbrMetallicRoughness.baseColorFactor[2]); fbxmat.diffuseColor = dcolor; fbxmat._material->setAlbedo(dcolor); fbxmat._material->setOpacity(material.pbrMetallicRoughness.baseColorFactor[3]); - } + } } } template -bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, +bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType) { - + QDataStream blobstream(bin); blobstream.setByteOrder(QDataStream::LittleEndian); blobstream.setVersion(QDataStream::Qt_5_9); @@ -1133,9 +1134,9 @@ bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, return true; } template -bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count, +bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType, int componentType) { - + switch (componentType) { case GLTFAccessorComponentType::BYTE: {} case GLTFAccessorComponentType::UNSIGNED_BYTE: { @@ -1157,8 +1158,8 @@ bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count return false; } -void GLTFReader::retriangulate(const QVector& inIndices, const QVector& in_vertices, - const QVector& in_normals, QVector& outIndices, +void GLTFReader::retriangulate(const QVector& inIndices, const QVector& in_vertices, + const QVector& in_normals, QVector& outIndices, QVector& out_vertices, QVector& out_normals) { for (int i = 0; i < inIndices.size(); i = i + 3) { diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index c46a1e234c..00109e9030 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -15,7 +15,7 @@ #include "OBJReader.h" #include // .obj files are not locale-specific. The C/ASCII charset applies. -#include +#include #include #include @@ -263,16 +263,16 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << + qCDebug(modelformat) << "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << - " shininess:" << currentMaterial.shininess << + " shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << - " diffuse color:" << currentMaterial.diffuseColor << - " specular color:" << currentMaterial.specularColor << - " emissive color:" << currentMaterial.emissiveColor << - " diffuse texture:" << currentMaterial.diffuseTextureFilename << - " specular texture:" << currentMaterial.specularTextureFilename << - " emissive texture:" << currentMaterial.emissiveTextureFilename << + " diffuse color:" << currentMaterial.diffuseColor << + " specular color:" << currentMaterial.specularColor << + " emissive color:" << currentMaterial.emissiveColor << + " diffuse texture:" << currentMaterial.diffuseTextureFilename << + " specular texture:" << currentMaterial.specularTextureFilename << + " emissive texture:" << currentMaterial.emissiveTextureFilename << " bump texture:" << currentMaterial.bumpTextureFilename << " opacity texture:" << currentMaterial.opacityTextureFilename; #endif @@ -352,7 +352,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } } } -} +} void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions) { // Texture options reference http://paulbourke.net/dataformats/mtl/ @@ -443,7 +443,8 @@ void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& file } std::tuple requestData(QUrl& url) { - auto request = DependencyManager::get()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest( + nullptr, url, true, -1, "(OBJReader) requestData"); if (!request) { return std::make_tuple(false, QByteArray()); @@ -793,7 +794,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m n0 = checked_at(normals, face.normalIndices[0]); n1 = checked_at(normals, face.normalIndices[1]); n2 = checked_at(normals, face.normalIndices[2]); - } else { + } else { // generate normals from triangle plane if not provided n0 = n1 = n2 = glm::cross(v1 - v0, v2 - v0); } @@ -923,7 +924,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m bool applyNonMetallic = false; bool fresnelOn = false; - // Illumination model reference http://paulbourke.net/dataformats/mtl/ + // Illumination model reference http://paulbourke.net/dataformats/mtl/ switch (objMaterial.illuminationModel) { case 0: // Color on and Ambient off // We don't support ambient = do nothing? @@ -967,7 +968,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m case 10: // Casts shadows onto invisible surfaces // Do nothing? break; - } + } if (applyTransparency) { fbxMaterial.opacity = std::max(fbxMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); diff --git a/libraries/model-networking/src/model-networking/TextureCache.cpp b/libraries/model-networking/src/model-networking/TextureCache.cpp index e8aec5e60e..740af44591 100644 --- a/libraries/model-networking/src/model-networking/TextureCache.cpp +++ b/libraries/model-networking/src/model-networking/TextureCache.cpp @@ -456,7 +456,8 @@ void NetworkTexture::makeRequest() { // Add a fragment to the base url so we can identify the section of the ktx being requested when debugging // The actual requested url is _activeUrl and will not contain the fragment _url.setFragment("head"); - _ktxHeaderRequest = DependencyManager::get()->createResourceRequest(this, _activeUrl); + _ktxHeaderRequest = DependencyManager::get()->createResourceRequest( + this, _activeUrl, true, -1, "NetworkTexture::makeRequest"); if (!_ktxHeaderRequest) { qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); @@ -617,7 +618,8 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) { bool isHighMipRequest = low == NULL_MIP_LEVEL && high == NULL_MIP_LEVEL; - _ktxMipRequest = DependencyManager::get()->createResourceRequest(this, _activeUrl); + _ktxMipRequest = DependencyManager::get()->createResourceRequest( + this, _activeUrl, true, -1, "NetworkTexture::startMipRangeRequest"); if (!_ktxMipRequest) { qCWarning(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 6d5bbb3ac5..23ab1548a0 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -24,8 +24,12 @@ static const int DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS = 5; -AssetResourceRequest::AssetResourceRequest(const QUrl& url) : - ResourceRequest(url) +AssetResourceRequest::AssetResourceRequest( + const QUrl& url, + const bool isObservable, + const qint64 callerId, + const QString& extra) : + ResourceRequest(url, isObservable, callerId, extra) { _lastProgressDebug = p_high_resolution_clock::now() - std::chrono::seconds(DOWNLOAD_PROGRESS_LOG_INTERVAL_SECONDS); } @@ -35,7 +39,7 @@ AssetResourceRequest::~AssetResourceRequest() { if (_assetMappingRequest) { _assetMappingRequest->deleteLater(); } - + if (_assetRequest) { _assetRequest->deleteLater(); } @@ -78,7 +82,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetUtils::AssetPath& pa // make sure we'll hear about the result of the get mapping request connect(_assetMappingRequest, &GetMappingRequest::finished, this, [this, path](GetMappingRequest* request){ auto statTracker = DependencyManager::get(); - + Q_ASSERT(_state == InProgress); Q_ASSERT(request == _assetMappingRequest); diff --git a/libraries/networking/src/AssetResourceRequest.h b/libraries/networking/src/AssetResourceRequest.h index 2fe79040ca..07baca5416 100644 --- a/libraries/networking/src/AssetResourceRequest.h +++ b/libraries/networking/src/AssetResourceRequest.h @@ -22,7 +22,11 @@ class AssetResourceRequest : public ResourceRequest { Q_OBJECT public: - AssetResourceRequest(const QUrl& url); + AssetResourceRequest( + const QUrl& url, + const bool isObservable = true, + const qint64 callerId = -1, + const QString& extra = ""); virtual ~AssetResourceRequest() override; protected: diff --git a/libraries/networking/src/AtpReply.cpp b/libraries/networking/src/AtpReply.cpp index b2b7e8bee7..3ec9b23f5f 100644 --- a/libraries/networking/src/AtpReply.cpp +++ b/libraries/networking/src/AtpReply.cpp @@ -14,7 +14,8 @@ #include "ResourceManager.h" AtpReply::AtpReply(const QUrl& url, QObject* parent) : - _resourceRequest(DependencyManager::get()->createResourceRequest(parent, url)) { + _resourceRequest(DependencyManager::get()->createResourceRequest( + parent, url, true, -1, "AtpReply::AtpReply")) { setOperation(QNetworkAccessManager::GetOperation); connect(_resourceRequest, &AssetResourceRequest::progress, this, &AtpReply::downloadProgress); diff --git a/libraries/networking/src/FileResourceRequest.h b/libraries/networking/src/FileResourceRequest.h index 547b754cb5..12b5b7d72e 100644 --- a/libraries/networking/src/FileResourceRequest.h +++ b/libraries/networking/src/FileResourceRequest.h @@ -12,14 +12,19 @@ #ifndef hifi_FileResourceRequest_h #define hifi_FileResourceRequest_h -#include - #include "ResourceRequest.h" +#include "QUrlAncestry.h" + class FileResourceRequest : public ResourceRequest { Q_OBJECT public: - FileResourceRequest(const QUrl& url) : ResourceRequest(url) { } + FileResourceRequest( + const QUrlAncestry& urlAncestry, + const bool isObservable = true, + const qint64 callerId = -1, + const QString& extra = "" + ) : ResourceRequest(urlAncestry, isObservable, callerId, extra) { } protected: virtual void doSend() override; diff --git a/libraries/networking/src/HTTPResourceRequest.h b/libraries/networking/src/HTTPResourceRequest.h index cc628d8855..41f605e856 100644 --- a/libraries/networking/src/HTTPResourceRequest.h +++ b/libraries/networking/src/HTTPResourceRequest.h @@ -13,15 +13,21 @@ #define hifi_HTTPResourceRequest_h #include -#include #include #include "ResourceRequest.h" +#include "QUrlAncestry.h" + class HTTPResourceRequest : public ResourceRequest { Q_OBJECT public: - HTTPResourceRequest(const QUrl& url) : ResourceRequest(url) { } + HTTPResourceRequest( + const QUrlAncestry& urlAncestry, + const bool isObservable = true, + const qint64 callerId = -1, + const QString& = "" + ) : ResourceRequest(urlAncestry, isObservable, callerId) { } ~HTTPResourceRequest(); protected: diff --git a/libraries/networking/src/NetworkAccessManager.cpp b/libraries/networking/src/NetworkAccessManager.cpp index f73243e675..c5229d65ac 100644 --- a/libraries/networking/src/NetworkAccessManager.cpp +++ b/libraries/networking/src/NetworkAccessManager.cpp @@ -22,7 +22,7 @@ QNetworkAccessManager& NetworkAccessManager::getInstance() { if (!networkAccessManagers.hasLocalData()) { networkAccessManagers.setLocalData(new QNetworkAccessManager()); } - + return *networkAccessManagers.localData(); } diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 076db44ea6..3cf12cb824 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -10,6 +10,7 @@ // #include "ResourceCache.h" +#include "ResourceRequestObserver.h" #include #include @@ -319,7 +320,7 @@ QVariantList ResourceCache::getResourceList() { return list; } - + void ResourceCache::setRequestLimit(uint32_t limit) { auto sharedItems = DependencyManager::get(); sharedItems->setRequestLimit(limit); @@ -336,30 +337,33 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& QReadLocker locker(&_resourcesLock); resource = _resources.value(url).lock(); } + if (resource) { removeUnusedResource(resource); - return resource; } - if (!url.isValid() && !url.isEmpty() && fallback.isValid()) { - return getResource(fallback, QUrl()); + if (!resource && !url.isValid() && !url.isEmpty() && fallback.isValid()) { + resource = getResource(fallback, QUrl()); } - resource = createResource( - url, - fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer(), - extra); - resource->setSelf(resource); - resource->setCache(this); - resource->moveToThread(qApp->thread()); - connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); - { - QWriteLocker locker(&_resourcesLock); - _resources.insert(url, resource); + if (!resource) { + resource = createResource( + url, + fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer(), + extra); resource->setSelf(resource); + resource->setCache(this); + resource->moveToThread(qApp->thread()); + connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize); + { + QWriteLocker locker(&_resourcesLock); + _resources.insert(url, resource); + } + removeUnusedResource(resource); + resource->ensureLoading(); } - removeUnusedResource(resource); - resource->ensureLoading(); + DependencyManager::get()->update( + resource->getURL(), -1, "ResourceCache::getResource"); return resource; } @@ -378,7 +382,7 @@ void ResourceCache::addUnusedResource(const QSharedPointer& resource) return; } reserveUnusedResource(resource->getBytes()); - + resource->setLRUKey(++_lastLRUKey); { @@ -407,7 +411,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { _unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) { // unload the oldest resource QMap >::iterator it = _unusedResources.begin(); - + it.value()->setCache(nullptr); auto size = it.value()->getBytes(); @@ -473,7 +477,7 @@ void ResourceCache::updateTotalSize(const qint64& deltaSize) { emit dirty(); } - + QList> ResourceCache::getLoadingRequests() { return DependencyManager::get()->getLoadingRequests(); } @@ -494,7 +498,15 @@ bool ResourceCache::attemptRequest(QSharedPointer resource) { resource->makeRequest(); return true; } +<<<<<<< HEAD return false; +======= + + ++_requestsActive; + sharedItems->appendActiveRequest(resource); + resource->makeRequest(); + return true; +>>>>>>> ac26e68e78... Display resource-access events in marketplace item tester } void ResourceCache::requestCompleted(QWeakPointer resource) { @@ -588,7 +600,7 @@ void Resource::refresh() { _request = nullptr; ResourceCache::requestCompleted(_self); } - + _activeUrl = _url; init(); ensureLoading(); @@ -602,8 +614,13 @@ void Resource::allReferencesCleared() { } if (_cache && isCacheable()) { - // create and reinsert new shared pointer +<<<<<<< HEAD + // create and reinsert new shared pointer QSharedPointer self(this, &Resource::deleter); +======= + // create and reinsert new shared pointer + QSharedPointer self(this, &Resource::allReferencesCleared); +>>>>>>> ac26e68e78... Display resource-access events in marketplace item tester setSelf(self); reinsert(); @@ -627,10 +644,10 @@ void Resource::init(bool resetLoaded) { _loaded = false; } _attempts = 0; - + if (_url.isEmpty()) { _startedLoading = _loaded = true; - + } else if (!(_url.isValid())) { _startedLoading = _failedToLoad = true; } @@ -682,7 +699,8 @@ void Resource::makeRequest() { PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } }); - _request = DependencyManager::get()->createResourceRequest(this, _activeUrl); + _request = DependencyManager::get()->createResourceRequest( + this, _activeUrl, true, -1, "Resource::makeRequest"); if (!_request) { qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString(); @@ -751,7 +769,7 @@ void Resource::handleReplyFinished() { } else { handleFailedRequest(result); } - + _request->disconnect(this); _request->deleteLater(); _request = nullptr; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index 553f0d0a61..d9774e3437 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -26,6 +26,7 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" + ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(atpSupportEnabled) { _thread.setObjectName("Resource Manager Thread"); @@ -112,22 +113,29 @@ void ResourceManager::cleanup() { _thread.wait(); } -ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) { +ResourceRequest* ResourceManager::createResourceRequest( + QObject* parent, + const QUrl& url, + const bool isObservable, + const qint64 callerId, + const QString& extra +) { auto normalizedURL = normalizeURL(url); auto scheme = normalizedURL.scheme(); ResourceRequest* request = nullptr; + qDebug() << "!!!! in createResourceRequest " << callerId; if (scheme == URL_SCHEME_FILE || scheme == URL_SCHEME_QRC) { - request = new FileResourceRequest(normalizedURL); + request = new FileResourceRequest(normalizedURL, isObservable, callerId, extra); } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { - request = new HTTPResourceRequest(normalizedURL); + request = new HTTPResourceRequest(normalizedURL, isObservable, callerId, extra); } else if (scheme == URL_SCHEME_ATP) { if (!_atpSupportEnabled) { qCDebug(networking) << "ATP support not enabled, unable to create request for URL: " << url.url(); return nullptr; } - request = new AssetResourceRequest(normalizedURL); + request = new AssetResourceRequest(normalizedURL, isObservable, callerId, extra); } else { qCDebug(networking) << "Unknown scheme (" << scheme << ") for URL: " << url.url(); return nullptr; @@ -138,6 +146,7 @@ ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const Q QObject::connect(parent, &QObject::destroyed, request, &QObject::deleteLater); } request->moveToThread(&_thread); + return request; } @@ -163,7 +172,7 @@ bool ResourceManager::resourceExists(const QUrl& url) { return reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200; } else if (scheme == URL_SCHEME_ATP && _atpSupportEnabled) { - auto request = new AssetResourceRequest(url); + auto request = new AssetResourceRequest(url, ResourceRequest::IS_NOT_OBSERVABLE); ByteRange range; range.fromInclusive = 1; range.toExclusive = 1; diff --git a/libraries/networking/src/ResourceManager.h b/libraries/networking/src/ResourceManager.h index a79222d2d8..adaa1cf886 100644 --- a/libraries/networking/src/ResourceManager.h +++ b/libraries/networking/src/ResourceManager.h @@ -34,7 +34,12 @@ public: QString normalizeURL(const QString& urlString); QUrl normalizeURL(const QUrl& url); - ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url); + ResourceRequest* createResourceRequest( + QObject* parent, + const QUrl& url, + const bool isObservable = true, + const qint64 callerId = -1, + const QString& extra = ""); void init(); void cleanup(); diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index 115e665b77..a651e9a2b6 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -10,15 +10,20 @@ // #include "ResourceRequest.h" +#include "ResourceRequestObserver.h" #include #include #include -ResourceRequest::ResourceRequest(const QUrl& url) : _url(url) { } void ResourceRequest::send() { + if (_isObservable) { + DependencyManager::get()->update( + _urlAncestry, _callerId, _extra + " => ResourceRequest::send" ); + } + if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection); return; diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index 8dd09ccc49..3ce1a9da2b 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -18,6 +18,8 @@ #include #include "ByteRange.h" +#include "QUrlAncestry.h" + const QString STAT_ATP_REQUEST_STARTED = "StartedATPRequest"; const QString STAT_HTTP_REQUEST_STARTED = "StartedHTTPRequest"; @@ -40,7 +42,21 @@ const QString STAT_FILE_RESOURCE_TOTAL_BYTES = "FILEBytesDownloaded"; class ResourceRequest : public QObject { Q_OBJECT public: - ResourceRequest(const QUrl& url); + static const bool IS_OBSERVABLE = true; + static const bool IS_NOT_OBSERVABLE = false; + + ResourceRequest( + const QUrlAncestry& urlAncestry, + const bool isObservable = IS_OBSERVABLE, + const qint64 callerId = -1, + const QString& extra = "" + ) : _urlAncestry(urlAncestry), + _isObservable(isObservable), + _callerId(callerId), + _extra(extra), + _url(urlAncestry.last()) + { } + virtual ~ResourceRequest() = default; enum State { @@ -87,6 +103,7 @@ protected: virtual void doSend() = 0; void recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived); + QUrl _url; QUrl _relativePathURL; State _state { NotStarted }; @@ -99,6 +116,10 @@ protected: bool _rangeRequestSuccessful { false }; uint64_t _totalSizeOfResource { 0 }; int64_t _lastRecordedBytesDownloaded { 0 }; + bool _isObservable; + qint64 _callerId; + QString _extra; + QUrlAncestry _urlAncestry; }; #endif diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 3d387e0956..a50438dd54 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -669,7 +669,7 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc return args.element; } -bool Octree::readFromFile(const char* fileName) { +bool Octree::readFromFile(const char* fileName, const QUrlAncestry& urlAncestry) { QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS); if (qFileName.endsWith(".json.gz")) { @@ -689,7 +689,7 @@ bool Octree::readFromFile(const char* fileName) { qCDebug(octree) << "Loading file" << qFileName << "..."; - bool success = readFromStream(fileLength, fileInputStream); + bool success = readFromStream(fileLength, fileInputStream, "", urlAncestry); file.close(); @@ -734,11 +734,18 @@ QString getMarketplaceID(const QString& urlString) { return QString(); } -bool Octree::readFromURL(const QString& urlString) { +bool Octree::readFromURL( + const QString& urlString, + const bool isObservable, + const qint64 callerId, + const QUrlAncestry& urlAncestry +) { QString trimmedUrl = urlString.trimmed(); QString marketplaceID = getMarketplaceID(trimmedUrl); - auto request = - std::unique_ptr(DependencyManager::get()->createResourceRequest(this, trimmedUrl)); + qDebug() << "!!!!! going to createResourceRequest " << callerId; + auto request = std::unique_ptr( + DependencyManager::get()->createResourceRequest( + this, trimmedUrl, isObservable, callerId, "Octree::readFromURL")); if (!request) { return false; @@ -760,15 +767,20 @@ bool Octree::readFromURL(const QString& urlString) { if (wasCompressed) { QDataStream inputStream(uncompressedJsonData); - return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID); + return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID, urlAncestry); } QDataStream inputStream(data); - return readFromStream(data.size(), inputStream, marketplaceID); + return readFromStream(data.size(), inputStream, marketplaceID, urlAncestry); } -bool Octree::readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID) { +bool Octree::readFromStream( + uint64_t streamLength, + QDataStream& inputStream, + const QString& marketplaceID, + const QUrlAncestry& urlAncestry +) { // decide if this is binary SVO or JSON-formatted SVO QIODevice *device = inputStream.device(); char firstChar; @@ -780,7 +792,7 @@ bool Octree::readFromStream(uint64_t streamLength, QDataStream& inputStream, con return false; } else { qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength; - return readJSONFromStream(streamLength, inputStream, marketplaceID); + return readJSONFromStream(streamLength, inputStream, marketplaceID, urlAncestry); } } @@ -809,7 +821,12 @@ QJsonDocument addMarketplaceIDToDocumentEntities(QJsonDocument& doc, const QStri const int READ_JSON_BUFFER_SIZE = 2048; -bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID /*=""*/) { +bool Octree::readJSONFromStream( + uint64_t streamLength, + QDataStream& inputStream, + const QString& marketplaceID, /*=""*/ + const QUrlAncestry& urlAncestry +) { // if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until // we get an eof. Leave streamLength parameter for consistency. @@ -834,7 +851,7 @@ bool Octree::readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, } QVariant asVariant = asDocument.toVariant(); QVariantMap asMap = asVariant.toMap(); - bool success = readFromMap(asMap); + bool success = readFromMap(asMap, urlAncestry); delete[] rawData; return success; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index a2b2f227cb..53acbc5a60 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -29,6 +29,7 @@ #include "OctreePacketData.h" #include "OctreeSceneStats.h" #include "OctreeUtils.h" +#include "QUrlAncestry.h" class ReadBitstreamToTreeParams; class Octree; @@ -209,13 +210,13 @@ public: bool skipThoseWithBadParents) = 0; // Octree importers - bool readFromFile(const char* filename); - bool readFromURL(const QString& url); // will support file urls as well... - bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); + bool readFromFile(const char* filename, const QUrlAncestry& urlAncestry = {}); + bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1, const QUrlAncestry& urlAncestry = {}); // will support file urls as well... + bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const QUrlAncestry& urlAncestry = {}); bool readSVOFromStream(uint64_t streamLength, QDataStream& inputStream); - bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); + bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const QUrlAncestry& urlAncestry = {}); bool readJSONFromGzippedFile(QString qFileName); - virtual bool readFromMap(QVariantMap& entityDescription) = 0; + virtual bool readFromMap(QVariantMap& entityDescription, const QUrlAncestry& urlAncestry = {}) = 0; uint64_t getOctreeElementsCount(); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index cbcafe9c7d..eccb812f09 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -258,24 +258,29 @@ void OffscreenSurface::setMaxFps(uint8_t maxFps) { } void OffscreenSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { + qDebug() << "Here 1"; loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) { QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem)); }); } void OffscreenSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& callback) { + qDebug() << "Here 2"; loadInternal(qmlSource, createNewContext, nullptr, callback); } void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback, const QmlContextCallback& contextCallback) { + qDebug() << "Here 3"; loadInternal(qmlSource, true, nullptr, callback, contextCallback); } void OffscreenSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { + qDebug() << "Here 4"; load(qmlSource, false, callback); } void OffscreenSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& callback) { + qDebug() << "Here 5"; return load(QUrl(qmlSourceFile), callback); } diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index 3bf044fd8b..103ed6d232 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -50,7 +50,7 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool tempDir = zipTemp.path(); path.remove("file:///"); } - + qCDebug(scriptengine) << "Temporary directory at: " + tempDir; if (!isTempDir(tempDir)) { qCDebug(scriptengine) << "Temporary directory mismatch; risk of losing files"; @@ -58,7 +58,7 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool } QStringList fileList = unzipFile(path, tempDir); - + if (!fileList.isEmpty()) { qCDebug(scriptengine) << "First file to upload: " + fileList.first(); } else { @@ -138,7 +138,8 @@ QString FileScriptingInterface::convertUrlToPath(QUrl url) { // this function is not in use void FileScriptingInterface::downloadZip(QString path, const QString link) { QUrl url = QUrl(link); - auto request = DependencyManager::get()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest( + nullptr, url, true, -1, "FileScriptingInterface::downloadZip"); connect(request, &ResourceRequest::finished, this, [this, path]{ unzipFile(path, ""); // so intellisense isn't mad }); diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index dba2db0458..8acf88a7ce 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -109,7 +109,8 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "about to call: ResourceManager::createResourceRequest(this, url); on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; #endif - auto request = DependencyManager::get()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest( + nullptr, url, true, -1, "ScriptCache::getScriptContents"); Q_ASSERT(request); request->setCacheEnabled(!forceDownload); connect(request, &ResourceRequest::finished, this, [=]{ scriptContentAvailable(maxRetries); }); @@ -166,7 +167,8 @@ void ScriptCache::scriptContentAvailable(int maxRetries) { qCDebug(scriptengine) << QString("Retrying script request [%1 / %2]: %3") .arg(attempt).arg(maxRetries).arg(url.toString()); - auto request = DependencyManager::get()->createResourceRequest(nullptr, url); + auto request = DependencyManager::get()->createResourceRequest( + nullptr, url, true, -1, "ScriptCache::scriptContentAvailable"); Q_ASSERT(request); // We've already made a request, so the cache must be disabled or it wasn't there, so enabling diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index ebc459b2d1..297d3bb924 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -21,6 +21,7 @@ #include #include +#include "ResourceRequestObserver.h" #include "ScriptEngine.h" const QString METAVERSE_API_URL = NetworkingConstants::METAVERSE_SERVER_URL().toString() + "/api/"; @@ -62,7 +63,7 @@ QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEn QScriptValue XMLHttpRequestClass::getStatus() const { if (_reply) { return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); - } + } return QScriptValue(0); } @@ -143,7 +144,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) { auto accountManager = DependencyManager::get(); - + if (accountManager->hasValidAccessToken()) { static const QString HTTP_AUTHORIZATION_HEADER = "Authorization"; QString bearerString = "Bearer " + accountManager->getAccountInfo().getAccessToken().token; @@ -189,7 +190,7 @@ void XMLHttpRequestClass::send(const QScriptValue& data) { } void XMLHttpRequestClass::doSend() { - + DependencyManager::get()->update(_url, -1, "XMLHttpRequestClass::doSend"); _reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData); connectToReply(_reply); diff --git a/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h b/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h new file mode 100644 index 0000000000..fd3647b19e --- /dev/null +++ b/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h @@ -0,0 +1,31 @@ +// +// EntityItemWeakPointerWithUrlAncestry.h +// libraries/shared/src/ +// +// Created by Kerry Ivan Kurian 10/15/18 +// 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 +// + +#ifndef hifi_EntityItemWeakPointerWithUrlAncestry_h +#define hifi_EntityItemWeakPointerWithUrlAncestry_h + +#include "EntityTypes.h" +#include "QUrlAncestry.h" + + +struct EntityItemWeakPointerWithUrlAncestry { + EntityItemWeakPointerWithUrlAncestry( + const EntityItemWeakPointer& a, + const QUrlAncestry& b + ) : entityItemWeakPointer(a), urlAncestry(b) {} + + EntityItemWeakPointer entityItemWeakPointer; + QUrlAncestry urlAncestry; +}; + + +#endif // hifi_EntityItemWeakPointerWithUrlAncestry_h + diff --git a/libraries/shared/src/QUrlAncestry.cpp b/libraries/shared/src/QUrlAncestry.cpp new file mode 100644 index 0000000000..f38c663803 --- /dev/null +++ b/libraries/shared/src/QUrlAncestry.cpp @@ -0,0 +1,35 @@ +// +// QUrlAncestry.cpp +// libraries/shared/src/ +// +// Created by Kerry Ivan Kurian on 10/12/18. +// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QUrlAncestry.h" + + +QUrlAncestry::QUrlAncestry(const QUrl& resource, const QUrl& referrer) { + this->append(referrer); + this->append(resource); +} + +QUrlAncestry::QUrlAncestry( + const QUrl& resource, + const QUrlAncestry& ancestors) : QVector(ancestors) +{ + this->append(resource); +} + +void QUrlAncestry::toJson(QJsonArray& array) const { + for (auto const& qurl : *this) { + array.append(qurl.toDisplayString()); + } +} + +const QUrl QUrlAncestry::url() const { + return this->last(); +} diff --git a/libraries/shared/src/QUrlAncestry.h b/libraries/shared/src/QUrlAncestry.h new file mode 100644 index 0000000000..84c32ff7c1 --- /dev/null +++ b/libraries/shared/src/QUrlAncestry.h @@ -0,0 +1,32 @@ +// +// QUrlAncestry.h +// libraries/shared/src/ +// +// Created by Kerry Ivan Kurian on 10/12/18. +// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +#ifndef hifi_QUrlAncestry_H +#define hifi_QUrlAncestry_H + +#include +#include +#include + + +class QUrlAncestry : public QVector { +public: + QUrlAncestry() {} + QUrlAncestry(const QUrl& resource, const QUrl& referrer = QUrl("__NONE__")); + QUrlAncestry(const QUrl& resource, const QUrlAncestry& ancestors); + + void toJson(QJsonArray& array) const; + const QUrl url() const; +}; + + +#endif // hifi_QUrlVector_H diff --git a/libraries/shared/src/ResourceRequestObserver.cpp b/libraries/shared/src/ResourceRequestObserver.cpp new file mode 100644 index 0000000000..6c52fcdc79 --- /dev/null +++ b/libraries/shared/src/ResourceRequestObserver.cpp @@ -0,0 +1,38 @@ +// +// ResourceAccessMonitor.h +// libraries/networking/src +// +// Created by Kerry Ivan Kurian on 9/27/18. +// 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 +// + + +#include +#include +#include +#include +#include "ResourceRequestObserver.h" +#include "QUrlAncestry.h" + + +// void ResourceRequestObserver::update(const QNetworkRequest& request, const qint64 callerId, const QString& extra) { +// update(QUrlAncestry(request.url()), callerId, extra); +// } + +void ResourceRequestObserver::update( + const QUrlAncestry& urlAncestry, + const qint64 callerId, + const QString& extra +) { + QJsonArray array; + urlAncestry.toJson(array); + QJsonObject data { + { "url", array }, + { "callerId", callerId }, + { "extra", extra } + }; + emit resourceRequestEvent(data.toVariantMap()); +} diff --git a/libraries/shared/src/ResourceRequestObserver.h b/libraries/shared/src/ResourceRequestObserver.h new file mode 100644 index 0000000000..edccdb5e48 --- /dev/null +++ b/libraries/shared/src/ResourceRequestObserver.h @@ -0,0 +1,31 @@ +// +// ResourceRequestObserver.h +// libraries/commerce/src +// +// Created by Kerry Ivan Kurian on 9/27/18. +// 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 +// + + +#include +#include +#include + +#include "DependencyManager.h" +#include "QUrlAncestry.h" + + +class ResourceRequestObserver : public QObject, public Dependency { + Q_OBJECT + SINGLETON_DEPENDENCY + +public: + // void update(const QNetworkRequest& request, const qint64 callerId = -1, const QString& extra = ""); + void update(const QUrlAncestry& urlAncestry, const qint64 callerId = -1, const QString& extra = ""); + +signals: + void resourceRequestEvent(QVariantMap result); +}; diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index cca535a064..d59a6b89d5 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -49,6 +49,33 @@ var NO_BUTTON = 0; // QMessageBox::NoButton var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server."; + +var resourceRequestEvents = []; +function signalResourceRequestEvent(data) { + ui.tablet.sendToQml({ + method: "resourceRequestEvent", + data: data }); +} + +function onResourceRequestEvent(data) { + var resourceRequestEvent = { + "date": JSON.stringify(new Date()), + "url": data.url, + "callerId": data.callerId, + "extra": data.extra }; + resourceRequestEvents.push(resourceRequestEvent); + signalResourceRequestEvent(resourceRequestEvent); +} + +function pushResourceRequestEvents() { + var length = resourceRequestEvents.length + for (var i = 0; i < length; i++) { + if (i in resourceRequestEvents) { + signalResourceRequestEvent(resourceRequestEvents[i]); + } + } +} + function onMessageBoxClosed(id, button) { if (id === messageBox && button === CANCEL_BUTTON) { isDownloadBeingCancelled = true; @@ -522,13 +549,19 @@ function getPositionToCreateEntity(extra) { return position; } -function rezEntity(itemHref, itemType) { +function defaultFor(arg, val) { + return typeof arg !== 'undefined' ? arg : val; +} + +function rezEntity(itemHref, itemType, marketplaceItemTesterId) { var isWearable = itemType === "wearable"; - var success = Clipboard.importEntities(itemHref); + print("!!!!! Clipboard.importEntities " + marketplaceItemTesterId); + var success = Clipboard.importEntities(itemHref, true, marketplaceItemTesterId); var wearableLocalPosition = null; var wearableLocalRotation = null; var wearableLocalDimensions = null; var wearableDimensions = null; + marketplaceItemTesterId = defaultFor(marketplaceItemTesterId, -1); if (itemType === "contentSet") { console.log("Item is a content set; codepath shouldn't go here."); @@ -877,11 +910,12 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': - rezEntity(message.itemHref, message.itemType); + print("!!!!! marketplaces tester_rezClicked"); + rezEntity(message.itemHref, message.itemType, message.itemId); break; case 'tester_newResourceObject': var resourceObject = message.resourceObject; - resourceObjectsInTest[resourceObject.id] = resourceObject; + resourceObjectsInTest[resourceObject.resourceObjectId] = resourceObject; signalNewResourceObjectInTest(resourceObject); break; case 'tester_updateResourceObjectAssetType': @@ -1029,16 +1063,20 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { }; function pushResourceObjectsInTest() { - var maxObjectId = -1; - for (var objectId in resourceObjectsInTest) { - signalNewResourceObjectInTest(resourceObjectsInTest[objectId]); - maxObjectId = (maxObjectId < objectId) ? parseInt(objectId) : maxObjectId; + var maxResourceObjectId = -1; + var length = resourceObjectsInTest.length; + for (var i = 0; i < length; i++) { + if (i in resourceObjectsInTest) { + signalNewResourceObjectInTest(resourceObjectsInTest[i]); + var resourceObjectId = resourceObjectsInTest[i].resourceObjectId; + maxResourceObjectId = (maxResourceObjectId < resourceObjectId) ? parseInt(resourceObjectId) : maxResourceObjectId; + } } // 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 }); + ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxResourceObjectId + 1 }); } // Function Name: onTabletScreenChanged() @@ -1127,6 +1165,7 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { // variable amount of time to come up, in practice less than // 750ms. Script.setTimeout(pushResourceObjectsInTest, 750); + Script.setTimeout(pushResourceRequestEvents, 750); } console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + @@ -1193,6 +1232,7 @@ function startup() { ui.tablet.webEventReceived.connect(onWebEventReceived); Wallet.walletStatusChanged.connect(sendCommerceSettings); Window.messageBoxClosed.connect(onMessageBoxClosed); + ResourceRequestObserver.resourceRequestEvent.connect(onResourceRequestEvent); Wallet.refreshWalletStatus(); } @@ -1226,6 +1266,7 @@ function shutdown() { GlobalServices.myUsernameChanged.disconnect(onUsernameChanged); Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged); ContextOverlay.contextOverlayClicked.disconnect(openInspectionCertificateQML); + ResourceRequestObserver.resourceRequestEvent.disconnect(onResourceRequestEvent); off(); } From 89a5da80e0049de7bbf1670d5b021574d8a96a83 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 00:54:15 -0700 Subject: [PATCH 02/16] Do not display incomplete metadata --- .../marketplaceItemTester/MarketplaceItemTester.qml | 5 ++--- libraries/networking/src/ResourceCache.cpp | 13 ------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 98b0355e81..f06612d035 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -69,9 +69,8 @@ Rectangle { // object ids. if (date >= startDate) { resourceAccessEventText += ( - message.data.callerId + " " + message.data.extra + - " " + message.data.url + - " [" + date.toISOString() + "]\n" + "[" + date.toISOString() + "] " + + message.data.url.toString().replace("__NONE__,", "") + "\n" ); } break; diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 3cf12cb824..186addbd86 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -498,15 +498,7 @@ bool ResourceCache::attemptRequest(QSharedPointer resource) { resource->makeRequest(); return true; } -<<<<<<< HEAD return false; -======= - - ++_requestsActive; - sharedItems->appendActiveRequest(resource); - resource->makeRequest(); - return true; ->>>>>>> ac26e68e78... Display resource-access events in marketplace item tester } void ResourceCache::requestCompleted(QWeakPointer resource) { @@ -614,13 +606,8 @@ void Resource::allReferencesCleared() { } if (_cache && isCacheable()) { -<<<<<<< HEAD // create and reinsert new shared pointer QSharedPointer self(this, &Resource::deleter); -======= - // create and reinsert new shared pointer - QSharedPointer self(this, &Resource::allReferencesCleared); ->>>>>>> ac26e68e78... Display resource-access events in marketplace item tester setSelf(self); reinsert(); From 8b922ad7ccb857188f2c5a74bf881d551d7d8434 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 11:07:45 -0700 Subject: [PATCH 03/16] Add missing dependency --- assignment-client/src/Agent.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 5f1e1ca74a..0b590a6d27 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -57,6 +57,7 @@ #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" #include "AgentScriptingInterface.h" +#include "ResourceRequestObserver.h" static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; @@ -99,6 +100,8 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); + // Needed to ensure the creation of the DebugDraw instance on the main thread DebugDraw::getInstance(); From 77b6389671b8cdb22a4bf62c54e620bde83b9da4 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 12:38:18 -0700 Subject: [PATCH 04/16] Correct location of dependency --- assignment-client/src/Agent.cpp | 3 --- assignment-client/src/AssignmentClient.cpp | 13 ++++++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 0b590a6d27..5f1e1ca74a 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -57,7 +57,6 @@ #include "RecordingScriptingInterface.h" #include "AbstractAudioInterface.h" #include "AgentScriptingInterface.h" -#include "ResourceRequestObserver.h" static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10; @@ -100,8 +99,6 @@ Agent::Agent(ReceivedMessage& message) : DependencyManager::set(); DependencyManager::set(); - DependencyManager::set(); - // Needed to ensure the creation of the DebugDraw instance on the main thread DebugDraw::getInstance(); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 426f3ce6fc..06b3f4da86 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -35,6 +35,8 @@ #include "AssignmentClientLogging.h" #include "AssignmentFactory.h" +#include "ResourceRequestObserver.h" + const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; @@ -49,6 +51,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); auto addressManager = DependencyManager::set(); @@ -159,7 +162,7 @@ void AssignmentClient::setUpStatusToMonitor() { void AssignmentClient::sendStatusPacketToACM() { // tell the assignment client monitor what this assignment client is doing (if anything) auto nodeList = DependencyManager::get(); - + quint8 assignmentType = Assignment::Type::AllTypes; if (_currentAssignment) { @@ -170,7 +173,7 @@ void AssignmentClient::sendStatusPacketToACM() { statusPacket->write(_childAssignmentUUID.toRfc4122()); statusPacket->writePrimitive(assignmentType); - + nodeList->sendPacket(std::move(statusPacket), _assignmentClientMonitorSocket); } @@ -256,10 +259,10 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer message) { const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); - + if (senderSockAddr.getAddress() == QHostAddress::LocalHost || senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) { - + qCDebug(assignment_client) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode."; QCoreApplication::quit(); } else { @@ -312,6 +315,6 @@ void AssignmentClient::assignmentCompleted() { nodeList->setOwnerType(NodeType::Unassigned); nodeList->reset(); nodeList->resetNodeInterestSet(); - + _isAssigned = false; } From 9f91238945ed9f5e27116a7be8a334285b724f39 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 18 Oct 2018 15:19:29 -0700 Subject: [PATCH 05/16] UX/UI improvements --- .../marketplaceItemTester/ItemUnderTest.qml | 352 ++++++++++++ .../MarketplaceItemTester.qml | 499 +++++++----------- .../marketplaceItemTester/spinner.gif | Bin 46135 -> 59412 bytes 3 files changed, 555 insertions(+), 296 deletions(-) create mode 100644 interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml new file mode 100644 index 0000000000..4852158df9 --- /dev/null +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -0,0 +1,352 @@ +// +// ItemUnderTest +// qml/hifi/commerce/marketplaceItemTester +// +// Load items not in the marketplace for testing purposes +// +// Created by Kerry Ivan Kurian on 2018-10-18 +// 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.10 +import QtQuick.Controls 2.3 +import Hifi 1.0 as Hifi +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit + +Rectangle { + id: root; + color: hifi.colors.baseGray + width: parent.width - 16 + height: childrenRect.height + itemHeaderContainer.anchors.topMargin + detailsContainer.anchors.topMargin + + property var detailsExpanded: false + + property var actions: { + "forward": function(resource, assetType, resourceObjectId){ + switch(assetType) { + case "application": + Commerce.openApp(resource); + break; + case "avatar": + MyAvatar.useFullAvatarURL(resource); + break; + case "content set": + urlHandler.handleUrl("hifi://localhost/0,0,0"); + Commerce.replaceContentSet(toUrl(resource), ""); + break; + case "entity": + case "wearable": + rezEntity(resource, assetType, resourceObjectId); + break; + default: + print("Marketplace item tester unsupported assetType " + assetType); + } + }, + "trash": function(resource, assetType){ + if ("application" === assetType) { + Commerce.uninstallApp(resource); + } + sendToScript({ + method: "tester_deleteResourceObject", + objectId: resourceListModel.get(index).id}); + resourceListModel.remove(index); + } + } + + Item { + id: itemHeaderContainer + anchors.top: parent.top + anchors.topMargin: 8 + anchors.left: parent.left + anchors.leftMargin: 8 + width: parent.width - 16 + height: childrenRect.height + + Item { + id: itemNameContainer + width: parent.width * 0.5 + height: childrenRect.height + + HifiStylesUit.RalewaySemiBold { + id: resourceName + height: paintedHeight + width: parent.width + text: { + var match = resource.match(/\/([^/]*)$/); + return match ? match[1] : resource; + } + size: 14 + color: hifi.colors.white + wrapMode: Text.WrapAnywhere + } + + HifiStylesUit.RalewayRegular { + id: resourceUrl + anchors.top: resourceName.bottom; + anchors.topMargin: 4; + height: paintedHeight + width: parent.width + text: resource + size: 12 + color: hifi.colors.faintGray; + wrapMode: Text.WrapAnywhere + } + } + + HifiControlsUit.ComboBox { + id: comboBox + anchors.left: itemNameContainer.right + anchors.leftMargin: 4 + anchors.verticalCenter: itemNameContainer.verticalCenter + height: 30 + width: parent.width * 0.3 - anchors.leftMargin + + model: [ + "application", + "avatar", + "content set", + "entity", + "wearable", + "unknown" + ] + + currentIndex: (("entity or wearable" === assetType) ? + model.indexOf("unknown") : model.indexOf(assetType)) + + Component.onCompleted: { + onCurrentIndexChanged.connect(function() { + assetType = model[currentIndex]; + sendToScript({ + method: "tester_updateResourceObjectAssetType", + objectId: resourceListModel.get(index)["resourceObjectId"], + assetType: assetType }); + }); + } + } + + Button { + id: actionButton + 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, + } + property int color: hifi.buttons.blue; + property int colorScheme: hifi.colorSchemes.dark; + anchors.left: comboBox.right + anchors.leftMargin: 4 + anchors.verticalCenter: itemNameContainer.verticalCenter + width: parent.width * 0.10 - anchors.leftMargin + height: width + enabled: comboBox.model[comboBox.currentIndex] !== "unknown" + + onClicked: { + root.actions["forward"](resource, comboBox.currentText, resourceObjectId); + } + + background: Rectangle { + radius: 4; + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!actionButton.enabled) { + hifi.buttons.disabledColorStart[actionButton.colorScheme] + } else if (actionButton.pressed) { + hifi.buttons.pressedColor[actionButton.color] + } else if (actionButton.hovered) { + hifi.buttons.hoveredColor[actionButton.color] + } else { + hifi.buttons.colorStart[actionButton.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!actionButton.enabled) { + hifi.buttons.disabledColorFinish[actionButton.colorScheme] + } else if (actionButton.pressed) { + hifi.buttons.pressedColor[actionButton.color] + } else if (actionButton.hovered) { + hifi.buttons.hoveredColor[actionButton.color] + } else { + hifi.buttons.colorFinish[actionButton.color] + } + } + } + } + } + + contentItem: Item { + HifiStylesUit.HiFiGlyphs { + id: rezIcon; + text: actionButton.glyphs[comboBox.model[comboBox.currentIndex]]; + anchors.fill: parent + size: 30; + horizontalAlignment: Text.AlignHCenter; + verticalAlignment: Text.AlignVCenter; + color: enabled ? hifi.buttons.textColor[actionButton.color] + : hifi.buttons.disabledTextColor[actionButton.colorScheme] + } + } + } + + Button { + id: trashButton + property int color: hifi.buttons.red; + property int colorScheme: hifi.colorSchemes.dark; + anchors.left: actionButton.right + anchors.verticalCenter: itemNameContainer.verticalCenter + anchors.leftMargin: 4 + width: parent.width * 0.10 - anchors.leftMargin + height: width + + onClicked: { + root.actions["trash"](resource, comboBox.currentText, resourceObjectId); + } + + background: Rectangle { + radius: 4; + gradient: Gradient { + GradientStop { + position: 0.2 + color: { + if (!trashButton.enabled) { + hifi.buttons.disabledColorStart[trashButton.colorScheme] + } else if (trashButton.pressed) { + hifi.buttons.pressedColor[trashButton.color] + } else if (trashButton.hovered) { + hifi.buttons.hoveredColor[trashButton.color] + } else { + hifi.buttons.colorStart[trashButton.color] + } + } + } + GradientStop { + position: 1.0 + color: { + if (!trashButton.enabled) { + hifi.buttons.disabledColorFinish[trashButton.colorScheme] + } else if (trashButton.pressed) { + hifi.buttons.pressedColor[trashButton.color] + } else if (trashButton.hovered) { + hifi.buttons.hoveredColor[trashButton.color] + } else { + hifi.buttons.colorFinish[trashButton.color] + } + } + } + } + } + + contentItem: Item { + HifiStylesUit.HiFiGlyphs { + id: trashIcon; + text: hifi.glyphs.trash + anchors.fill: parent + size: 22; + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: enabled ? hifi.buttons.textColor[trashButton.color] + : hifi.buttons.disabledTextColor[trashButton.colorScheme] + } + } + } + } + + Item { + id: detailsContainer + + width: parent.width - 16 + height: root.detailsExpanded ? 300 : 26 + anchors.top: itemHeaderContainer.bottom + anchors.topMargin: 12 + anchors.left: parent.left + anchors.leftMargin: 8 + + HifiStylesUit.HiFiGlyphs { + id: detailsToggle + anchors.left: parent.left + anchors.leftMargin: -4 + anchors.top: parent.top + anchors.topMargin: -2 + width: 22 + text: root.detailsExpanded ? hifi.glyphs.minimize : hifi.glyphs.maximize + color: hifi.colors.white + size: 22 + MouseArea { + anchors.fill: parent + onClicked: root.detailsExpanded = !root.detailsExpanded + } + } + + ScrollView { + id: detailsTextContainer + anchors.top: parent.top + anchors.left: detailsToggle.right + anchors.leftMargin: 4 + anchors.right: parent.right + height: detailsContainer.height - (root.detailsExpanded ? (copyToClipboardButton.height + copyToClipboardButton.anchors.topMargin) : 0) + clip: true + + TextArea { + id: detailsText + readOnly: true + color: hifi.colors.white + text: { + if (root.detailsExpanded) { + return resourceAccessEventText + } else { + return (resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." + } + } + font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) + wrapMode: TextEdit.NoWrap + + background: Rectangle { + anchors.fill: parent; + color: hifi.colors.baseGrayShadow; + border.width: 0; + } + } + + MouseArea { + anchors.fill: parent + onClicked: { + if (root.detailsExpanded) { + detailsText.selectAll(); + } else { + root.detailsExpanded = true; + } + } + } + } + + HifiControlsUit.Button { + id: copyToClipboardButton; + visible: root.detailsExpanded + color: hifi.buttons.noneBorderlessWhite + colorScheme: hifi.colorSchemes.dark + + anchors.top: detailsTextContainer.bottom + anchors.topMargin: 8 + anchors.right: parent.right + width: 150 + height: 30 + text: "Copy to Clipboard" + + onClicked: { + Window.copyToClipboard(detailsText.text); + } + } + } +} diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index f06612d035..89b1dd3915 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -11,14 +11,12 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 -import QtQuick.Dialogs 1.0 +import QtQuick 2.10 import QtQuick.Layouts 1.1 +import QtQuick.Controls 2.3 import Hifi 1.0 as Hifi -import "../../../styles-uit" as HifiStylesUit -import "../../../controls-uit" as HifiControlsUit +import "qrc:////qml//styles-uit" as HifiStylesUit +import "qrc:////qml//controls-uit" as HifiControlsUit @@ -29,20 +27,208 @@ Rectangle { property string resourceAccessEventText property var nextResourceObjectId: 0 property var startDate - signal sendToScript(var message) HifiStylesUit.HifiConstants { id: hifi } ListModel { id: resourceListModel } - color: hifi.colors.white + color: hifi.colors.darkGray - AnimatedImage { - id: spinner; - source: "spinner.gif" - width: 74; - height: width; - anchors.verticalCenter: parent.verticalCenter; - anchors.horizontalCenter: parent.horizontalCenter; + Component.onCompleted: startDate = new Date() + + // + // TITLE BAR START + // + Item { + id: titleBarContainer + // Size + width: root.width + height: 50 + // Anchors + anchors.left: parent.left + anchors.top: parent.top + + // Title bar text + HifiStylesUit.RalewaySemiBold { + id: titleBarText + text: "Marketplace Item Tester" + // Text size + size: 24 + // Anchors + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.leftMargin: 16 + width: paintedWidth + // Style + color: hifi.colors.lightGrayText + // Alignment + horizontalAlignment: Text.AlignHLeft + verticalAlignment: Text.AlignVCenter + } + + // Separator + HifiControlsUit.Separator { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.bottomMargin: 1 + } + } + // + // TITLE BAR END + // + + Rectangle { + id: spinner + z: 999 + anchors.top: titleBarContainer.bottom + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: buttonContainer.top + color: hifi.colors.darkGray + + AnimatedImage { + source: "spinner.gif" + width: 74 + height: width + anchors.centerIn: parent + } + } + + Rectangle { + id: instructionsContainer + z: 998 + color: hifi.colors.darkGray + visible: resourceListModel.count === 0 && !spinner.visible + anchors.top: titleBarContainer.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.leftMargin: 20 + anchors.right: parent.right + anchors.rightMargin: 20 + anchors.bottom: buttonContainer.top + anchors.bottomMargin: 20 + + HifiStylesUit.RalewayRegular { + text: "Use Marketplace Item Tester to test out your items before submitting them to the Marketplace." + + "\n\nUse one of the buttons below to load your item." + // Text size + size: 20 + // Anchors + anchors.fill: parent + // Style + color: hifi.colors.lightGrayText + wrapMode: Text.Wrap + // Alignment + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + + ListView { + id: itemList + visible: !instructionsContainer.visible + anchors.top: titleBarContainer.bottom + anchors.topMargin: 20 + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: buttonContainer.top + anchors.bottomMargin: 20 + ScrollBar.vertical: ScrollBar { + visible: !instructionsContainer.visible + policy: ScrollBar.AlwaysOn + parent: itemList.parent + anchors.top: itemList.top + anchors.right: itemList.right + anchors.bottom: itemList.bottom + width: 16 + } + clip: true + model: resourceListModel + spacing: 8 + + delegate: ItemUnderTest { + } + } + + Item { + id: buttonContainer + + anchors.left: parent.left + anchors.leftMargin: 12 + anchors.right: parent.right + anchors.rightMargin: 12 + anchors.bottom: parent.bottom + anchors.bottomMargin: 12 + height: 40 + + property string currentAction + property var actions: { + "Load File": function() { + buttonContainer.currentAction = "load file"; + Window.browseChanged.connect(onResourceSelected); + Window.browseAsync("Please select a file (*.app.json *.json *.fst *.json.gz)", "", "Assets (*.app.json *.json *.fst *.json.gz)"); + }, + "Load URL": function() { + buttonContainer.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. + // Alas, there is nothing we can do about that so charge + // ahead as though we are sure the present signal is one + // we expect. + print("!!!! resource selected"); + switch(currentAction) { + case "load file": + Window.browseChanged.disconnect(onResourceSelected); + break + case "load url": + Window.promptTextChanged.disconnect(onResourceSelected); + break; + } + if (resource) { + print("!!!! building resource object"); + var resourceObj = buildResourceObj(resource); + print("!!!! installing resource object"); + installResourceObj(resourceObj); + print("!!!! notifying script of resource object"); + sendToScript({ + method: 'tester_newResourceObject', + resourceObject: resourceObj + }); + } + } + + HifiControlsUit.Button { + enabled: !spinner.visible + anchors.right: parent.horizontalCenter + anchors.rightMargin: width/4 + anchors.verticalCenter: parent.verticalCenter + color: hifi.buttons.blue + fontSize: 20 + text: "Load File" + width: parent.width / 3 + height: parent.height + onClicked: buttonContainer.actions[text]() + } + + HifiControlsUit.Button { + enabled: !spinner.visible + anchors.left: parent.horizontalCenter + anchors.leftMargin: width/4 + anchors.verticalCenter: parent.verticalCenter + color: hifi.buttons.blue + fontSize: 20 + text: "Load URL" + width: parent.width / 3 + height: parent.height + onClicked: buttonContainer.actions[text]() + } } function fromScript(message) { @@ -117,285 +303,6 @@ Rectangle { itemType: entityType, itemId: resourceObjectId }); } - - Component.onCompleted: startDate = new Date() - - ColumnLayout { - id: rootColumn - spacing: 30 - - HifiStylesUit.RalewayRegular { - id: rootHeader - text: "Marketplace Item Tester" - height: 40 - width: paintedWidth - size: 22 - color: hifi.colors.black - anchors.top: parent.top - anchors.topMargin: 20 - anchors.left: parent.left - anchors.leftMargin: 12 - } - - Rectangle { - height: root.height - 100 - width: root.width - anchors.left: parent.left - - ScrollView { - id: scrollView - anchors.fill: parent - anchors.rightMargin: 12 - anchors.bottom: parent.top - anchors.bottomMargin: 20 - anchors.leftMargin: 12 - verticalScrollBarPolicy: Qt.ScrollBarAlwaysOn - - frameVisible: false - - contentItem: ListView { - spacing: 20 - height: 200 - model: resourceListModel - interactive: false - - delegate: Column { - spacing: 8 - - RowLayout { - id: listRow - width: scrollView.width - 20 - anchors.rightMargin: scrollView.rightMargin - spacing: 5 - - property var actions: { - "forward": function(resource, assetType, resourceObjectId){ - switch(assetType) { - case "application": - Commerce.openApp(resource); - break; - case "avatar": - MyAvatar.useFullAvatarURL(resource); - break; - case "content set": - urlHandler.handleUrl("hifi://localhost/0,0,0"); - Commerce.replaceContentSet(toUrl(resource), ""); - break; - case "entity": - case "wearable": - rezEntity(resource, assetType, resourceObjectId); - break; - default: - print("Marketplace item tester unsupported assetType " + assetType); - } - }, - "trash": function(resource, assetType){ - if ("application" === assetType) { - Commerce.uninstallApp(resource); - } - sendToScript({ - method: "tester_deleteResourceObject", - objectId: resourceListModel.get(index).id}); - resourceListModel.remove(index); - } - } - - Column { - Layout.preferredWidth: scrollView.width * .6 - spacing: 5 - Text { - width: listRow.width * .6 - text: { - var match = resource.match(/\/([^/]*)$/); - return match ? match[1] : resource; - } - font.pointSize: 12 - horizontalAlignment: Text.AlignBottom - wrapMode: Text.WrapAnywhere - } - Text { - width: listRow.width * .6 - text: resource - font.pointSize: 8 - horizontalAlignment: Text.AlignBottom - wrapMode: Text.WrapAnywhere - } - } - - ComboBox { - id: comboBox - - Layout.preferredWidth: listRow.width * .2 - - model: [ - "application", - "avatar", - "content set", - "entity", - "wearable", - "unknown" - ] - - currentIndex: (("entity or wearable" === assetType) ? - model.indexOf("unknown") : model.indexOf(assetType)) - - Component.onCompleted: { - onCurrentIndexChanged.connect(function() { - assetType = model[currentIndex]; - sendToScript({ - method: "tester_updateResourceObjectAssetType", - objectId: resourceListModel.get(index)["resourceObjectId"], - assetType: assetType }); - }); - } - } - - Repeater { - model: [ "forward", "trash" ] - - HifiStylesUit.HiFiGlyphs { - 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 { - anchors.fill: parent - onClicked: { - listRow.actions[modelData](resource, comboBox.currentText, resourceObjectId); - } - } - } - } - } - - Rectangle { - id: detailsContainer - - width: scrollView.width - 20 - height: resourceDetails.isOpen ? 300 : 20 - anchors.left: parent.left - - HifiStylesUit.HiFiGlyphs { - id: detailsToggle - anchors.top: parent.top - text: resourceDetails.isOpen ? hifi.glyphs.minimize : hifi.glyphs.maximize - color: hifi.colors.black - size: 22 - verticalAlignment: Text.AlignBottom - MouseArea { - anchors.fill: parent - onClicked: resourceDetails.isOpen = !resourceDetails.isOpen - } - } - - TextArea { - id: resourceDetails - - property var isOpen: false - - width: detailsContainer.width - 20 - height: detailsContainer.height - anchors.top: parent.top - anchors.left: detailsToggle.left - anchors.leftMargin: 20 - verticalScrollBarPolicy: isOpen ? Qt.ScrollBarAsNeeded : Qt.ScrollBarAlwaysOff - frameVisible: isOpen - readOnly: true - - text: { - if (isOpen) { - return resourceAccessEventText - } else { - return (resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." - } - } - font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) - wrapMode: TextEdit.NoWrap - } - } - - Rectangle { - width: listRow.width - height: 1 - color: hifi.colors.black - } - } - } - } - } - - Row { - id: rootActions - spacing: 20 - - anchors.left: parent.left - anchors.leftMargin: root.width / 6 - 10 - anchors.bottomMargin: 40 - anchors.bottom: parent.bottom - - property string currentAction - property var actions: { - "Load File": function(){ - rootActions.currentAction = "load file"; - Window.browseChanged.connect(onResourceSelected); - 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"; - 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. - // Alas, there is nothing we can do about that so charge - // ahead as though we are sure the present signal is one - // we expect. - print("!!!! resource selected"); - switch(currentAction) { - case "load file": - Window.browseChanged.disconnect(onResourceSelected); - break - case "load url": - Window.promptTextChanged.disconnect(onResourceSelected); - break; - } - if (resource) { - print("!!!! building resource object"); - var resourceObj = buildResourceObj(resource); - print("!!!! installing resource object"); - installResourceObj(resourceObj); - print("!!!! notifying script of resource object"); - sendToScript({ - method: 'tester_newResourceObject', - resourceObject: resourceObj }); - } - } - - Repeater { - model: [ "Load File", "Load URL" ] - HifiControlsUit.Button { - color: hifi.buttons.blue - fontSize: 20 - text: modelData - width: root.width / 3 - height: 40 - onClicked: rootActions.actions[text]() - } - } - } - } + + signal sendToScript(var message) } diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif b/interface/resources/qml/hifi/commerce/marketplaceItemTester/spinner.gif index 00f75ae62fa59b28a7813ca00c4185390e3e51da..0536bd1884f1cb4a814b729803b1996c8347d182 100644 GIT binary patch literal 59412 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�)#8TsS*rVAU%J0tpt=B2=IG*djp3b$!Y|O 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 L8@}NiK8F7Q4CAa& literal 46135 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 V-}`%i@9+J+zxVh4-rv9A{u}xRWtji~ From 4d7bc3094aed9445a5830edfb850c22839d7b2e4 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 16:14:53 -0700 Subject: [PATCH 06/16] Fix regression --- .../hifi/commerce/marketplaceItemTester/ItemUnderTest.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index 4852158df9..dcb67f3f12 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -52,7 +52,7 @@ Rectangle { } sendToScript({ method: "tester_deleteResourceObject", - objectId: resourceListModel.get(index).id}); + objectId: resourceListModel.get(index).resourceObjectId}); resourceListModel.remove(index); } } @@ -83,7 +83,7 @@ Rectangle { color: hifi.colors.white wrapMode: Text.WrapAnywhere } - + HifiStylesUit.RalewayRegular { id: resourceUrl anchors.top: resourceName.bottom; @@ -311,7 +311,7 @@ Rectangle { } font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) wrapMode: TextEdit.NoWrap - + background: Rectangle { anchors.fill: parent; color: hifi.colors.baseGrayShadow; From d7d49ed84e66f9517b7adeaa9a1baf0fd7b71f2b Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Thu, 18 Oct 2018 16:25:26 -0700 Subject: [PATCH 07/16] Remove cruft --- .../marketplaceItemTester/MarketplaceItemTester.qml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 89b1dd3915..5f2268132c 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -77,7 +77,7 @@ Rectangle { // // TITLE BAR END // - + Rectangle { id: spinner z: 999 @@ -281,15 +281,6 @@ Rectangle { } } - 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])); - } - } - } - function toUrl(resource) { var httpPattern = /^http/i; return httpPattern.test(resource) ? resource : "file:///" + resource; @@ -303,6 +294,6 @@ Rectangle { itemType: entityType, itemId: resourceObjectId }); } - + signal sendToScript(var message) } From 493262052cde39fd38090960d9332d05f6602504 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 11:32:31 -0700 Subject: [PATCH 08/16] Remove extraneous code changes; remove QUrlAncestry code; remove extra logging --- assignment-client/src/Agent.cpp | 2 +- assignment-client/src/AssignmentClient.cpp | 11 +- interface/src/Application.cpp | 7 +- interface/src/assets/ATPAssetMigrator.cpp | 14 +- .../scripting/ClipboardScriptingInterface.h | 2 +- interface/src/ui/overlays/Web3DOverlay.cpp | 5 +- libraries/avatars/src/AvatarData.cpp | 18 +- libraries/entities/src/EntityEditFilters.cpp | 34 ++-- libraries/entities/src/EntityTree.cpp | 16 +- libraries/entities/src/EntityTree.h | 10 +- libraries/fbx/src/GLTFReader.cpp | 190 +++++++++--------- libraries/fbx/src/OBJReader.cpp | 26 +-- .../networking/src/AssetResourceRequest.cpp | 4 +- .../networking/src/FileResourceRequest.h | 8 +- .../networking/src/HTTPResourceRequest.h | 7 +- .../networking/src/NetworkAccessManager.cpp | 2 +- libraries/networking/src/ResourceCache.cpp | 19 +- libraries/networking/src/ResourceManager.cpp | 3 - libraries/networking/src/ResourceRequest.cpp | 3 +- libraries/networking/src/ResourceRequest.h | 11 +- libraries/octree/src/Octree.cpp | 21 +- libraries/octree/src/Octree.h | 11 +- libraries/qml/src/qml/OffscreenSurface.cpp | 5 - .../src/FileScriptingInterface.cpp | 4 +- .../script-engine/src/XMLHttpRequestClass.cpp | 4 +- .../EntityItemWeakPointerWithUrlAncestry.h | 31 --- libraries/shared/src/QUrlAncestry.cpp | 35 ---- libraries/shared/src/QUrlAncestry.h | 32 --- .../shared/src/ResourceRequestObserver.cpp | 16 +- .../shared/src/ResourceRequestObserver.h | 4 +- 30 files changed, 210 insertions(+), 345 deletions(-) delete mode 100644 libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h delete mode 100644 libraries/shared/src/QUrlAncestry.cpp delete mode 100644 libraries/shared/src/QUrlAncestry.h diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 1561af4d25..4490474599 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -895,7 +895,7 @@ void Agent::aboutToFinish() { { DependencyManager::get()->shutdownScripting(); } - + DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index 06b3f4da86..76ff5ab2ed 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -37,7 +37,6 @@ #include "AssignmentFactory.h" #include "ResourceRequestObserver.h" - const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; @@ -162,7 +161,7 @@ void AssignmentClient::setUpStatusToMonitor() { void AssignmentClient::sendStatusPacketToACM() { // tell the assignment client monitor what this assignment client is doing (if anything) auto nodeList = DependencyManager::get(); - + quint8 assignmentType = Assignment::Type::AllTypes; if (_currentAssignment) { @@ -173,7 +172,7 @@ void AssignmentClient::sendStatusPacketToACM() { statusPacket->write(_childAssignmentUUID.toRfc4122()); statusPacket->writePrimitive(assignmentType); - + nodeList->sendPacket(std::move(statusPacket), _assignmentClientMonitorSocket); } @@ -259,10 +258,10 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer message) { const HifiSockAddr& senderSockAddr = message->getSenderSockAddr(); - + if (senderSockAddr.getAddress() == QHostAddress::LocalHost || senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) { - + qCDebug(assignment_client) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode."; QCoreApplication::quit(); } else { @@ -315,6 +314,6 @@ void AssignmentClient::assignmentCompleted() { nodeList->setOwnerType(NodeType::Unassigned); nodeList->reset(); nodeList->resetNodeInterestSet(); - + _isAssigned = false; } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 242445b0fe..e515a22403 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -170,7 +170,6 @@ #include "ModelPackager.h" #include "scripting/Audio.h" #include "networking/CloseEventSender.h" -#include "QUrlAncestry.h" #include "scripting/TestScriptingInterface.h" #include "scripting/AssetMappingsScriptingInterface.h" #include "scripting/ClipboardScriptingInterface.h" @@ -947,6 +946,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); + DependencyManager::set(); DependencyManager::set(); @@ -1783,7 +1783,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo updateHeartbeat(); QTimer* settingsTimer = new QTimer(); moveToNewNamedThread(settingsTimer, "Settings Thread", [this, settingsTimer]{ - // This needs to run on the settings thread, so we need to pass the `settingsTimer` as the + // 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]{ @@ -5029,7 +5029,8 @@ bool Application::importEntities(const QString& urlOrFilename, const bool isObse bool success = false; _entityClipboard->withWriteLock([&] { _entityClipboard->eraseAllOctreeElements(); - success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId, QUrlAncestry()); + + success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId); if (success) { _entityClipboard->reaverageOctreeElements(); } diff --git a/interface/src/assets/ATPAssetMigrator.cpp b/interface/src/assets/ATPAssetMigrator.cpp index 6912c69db8..be7f2014cc 100644 --- a/interface/src/assets/ATPAssetMigrator.cpp +++ b/interface/src/assets/ATPAssetMigrator.cpp @@ -203,7 +203,7 @@ void ATPAssetMigrator::loadEntityServerFile() { void ATPAssetMigrator::migrateResource(ResourceRequest* request) { // use an asset client to upload the asset auto assetClient = DependencyManager::get(); - + auto upload = assetClient->createUpload(request->getData()); // add this URL to our hash of AssetUpload to original URL @@ -243,7 +243,7 @@ void ATPAssetMigrator::assetUploadFinished(AssetUpload *upload, const QString& h } checkIfFinished(); - + upload->deleteLater(); } @@ -299,24 +299,24 @@ void ATPAssetMigrator::checkIfFinished() { bool ATPAssetMigrator::wantsToMigrateResource(const QUrl& url) { static bool hasAskedForCompleteMigration { false }; static bool wantsCompleteMigration { false }; - + if (!hasAskedForCompleteMigration) { // this is the first resource migration - ask the user if they just want to migrate everything static const QString COMPLETE_MIGRATION_TEXT { "Do you want to migrate all assets found in this entity-server file?\n"\ "Select \"Yes\" to upload all discovered assets to the current asset-server immediately.\n"\ "Select \"No\" to be prompted for each discovered asset." }; - + auto button = OffscreenUi::question(_dialogParent, MESSAGE_BOX_TITLE, COMPLETE_MIGRATION_TEXT, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - + if (button == QMessageBox::Yes) { wantsCompleteMigration = true; } - + hasAskedForCompleteMigration = true; } - + if (wantsCompleteMigration) { return true; } else { diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 535ccfd5ab..60b6ca2e03 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -64,7 +64,7 @@ public: * @returns {boolean} true if the export was successful, otherwise false. */ Q_INVOKABLE bool exportEntities(const QString& filename, const QVector& entityIDs); - + /**jsdoc * Export the entities with centers within a cube to a JSON file. * @function Clipboard.exportEntities diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 53505c2013..084615cae2 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -61,7 +61,6 @@ #include "AboutUtil.h" #include "ResourceRequestObserver.h" - static int MAX_WINDOW_SIZE = 4096; static const float METERS_TO_INCHES = 39.3701f; static const float OPAQUE_ALPHA_THRESHOLD = 0.99f; @@ -539,7 +538,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {boolean} visible=true - If true, the overlay is rendered, otherwise it is not rendered. * * @property {string} name="" - A friendly name for the overlay. - * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and + * @property {Vec3} position - The position of the overlay center. Synonyms: p1, point, and * start. * @property {Vec3} localPosition - The local position of the overlay relative to its parent if the overlay has a * parentID set, otherwise the same value as position. @@ -564,7 +563,7 @@ void Web3DOverlay::setProperties(const QVariantMap& properties) { * @property {string} url - The URL of the Web page to display. * @property {string} scriptURL="" - The URL of a JavaScript file to inject into the Web page. * @property {number} dpi=30 - The dots per inch to display the Web page at, on the overlay. - * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: + * @property {Vec2} dimensions=1,1 - The size of the overlay to display the Web page on, in meters. Synonyms: * scale, size. * @property {number} maxFPS=10 - The maximum update rate for the Web overlay content, in frames/second. * @property {boolean} showKeyboardFocusHighlight=true - If true, the Web overlay is highlighted when it has diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 753707f0fe..032ffe25f7 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -380,7 +380,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } else { AVATAR_MEMCPY(_globalPosition); } - + int numBytes = destinationBuffer - startSection; @@ -648,7 +648,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent if (!data.translationIsDefaultPose) { if (sendAll || last.translationIsDefaultPose || (!cullSmallChanges && last.translation != data.translation) || (cullSmallChanges && glm::distance(data.translation, lastSentJointData[i].translation) > minTranslation)) { - + validity |= (1 << validityBit); #ifdef WANT_DEBUG translationSentCount++; @@ -1055,7 +1055,7 @@ int AvatarData::parseDataFromBuffer(const QByteArray& buffer) { auto newHasProceduralEyeFaceMovement = oneAtBit16(bitItems, PROCEDURAL_EYE_FACE_MOVEMENT); auto newHasProceduralBlinkFaceMovement = oneAtBit16(bitItems, PROCEDURAL_BLINK_FACE_MOVEMENT); - + bool keyStateChanged = (_keyState != newKeyState); bool handStateChanged = (_handState != newHandState); bool faceStateChanged = (_headData->_isFaceTrackerConnected != newFaceTrackerConnected); @@ -1527,7 +1527,7 @@ glm::vec3 AvatarData::getJointTranslation(int index) const { } glm::vec3 AvatarData::getJointTranslation(const QString& name) const { - // Can't do this, because the lock needs to cover the entire set of logic. In theory, the joints could change + // Can't do this, because the lock needs to cover the entire set of logic. In theory, the joints could change // on another thread in between the call to getJointIndex and getJointTranslation // return getJointTranslation(getJointIndex(name)); return readLockWithNamedJointIndex(name, [this](int index) { @@ -1608,7 +1608,7 @@ bool AvatarData::isJointDataValid(const QString& name) const { // return isJointDataValid(getJointIndex(name)); return readLockWithNamedJointIndex(name, false, [&](int index) { - // This is technically superfluous.... the lambda is only called if index is a valid + // This is technically superfluous.... the lambda is only called if index is a valid // offset for _jointData. Nevertheless, it would be confusing to leave the lamdba as // `return true` return index < _jointData.size(); @@ -1827,7 +1827,7 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) { bytesWritten += destination.writePrimitive(traitVersion); } - + AvatarTraits::TraitWireSize encodedURLSize = encodedSkeletonURL.size(); bytesWritten += destination.writePrimitive(encodedURLSize); @@ -1936,7 +1936,7 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { if (expanded == _skeletonModelURL) { return; } - + _skeletonModelURL = expanded; qCDebug(avatars) << "Changing skeleton model for avatar" << getSessionUUID() << "to" << _skeletonModelURL.toString(); @@ -2163,7 +2163,7 @@ void AvatarData::updateJointMappings() { if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) { //// - // TODO: Should we rely upon HTTPResourceRequest instead? + // TODO: Should we rely upon HTTPResourceRequest for ResourceRequestObserver instead? // HTTPResourceRequest::doSend() covers all of the following and // then some. It doesn't cover the connect() call, so we may // want to add a HTTPResourceRequest::doSend() method that does @@ -2402,7 +2402,7 @@ QJsonObject AvatarData::toJson() const { for (auto entityID : _avatarEntityData.keys()) { QVariantMap entityData; QUuid newId = _avatarEntityForRecording.size() == _avatarEntityData.size() ? _avatarEntityForRecording.values()[entityCount++] : entityID; - entityData.insert("id", newId); + entityData.insert("id", newId); entityData.insert("properties", _avatarEntityData.value(entityID).toBase64()); avatarEntityJson.push_back(QVariant(entityData).toJsonObject()); } diff --git a/libraries/entities/src/EntityEditFilters.cpp b/libraries/entities/src/EntityEditFilters.cpp index c88ae138bf..4865c0ba1e 100644 --- a/libraries/entities/src/EntityEditFilters.cpp +++ b/libraries/entities/src/EntityEditFilters.cpp @@ -24,7 +24,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { for (auto id : zoneIDs) { if (!id.isInvalidID()) { // for now, look it up in the tree (soon we need to cache or similar?) - EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); + EntityItemPointer itemPtr = _tree->findEntityByEntityItemID(id); auto zone = std::dynamic_pointer_cast(itemPtr); if (!zone) { // TODO: maybe remove later? @@ -33,7 +33,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { zones.append(id); } } else { - // the null id is the global filter we put in the domain server's + // the null id is the global filter we put in the domain server's // advanced entity server settings zones.append(id); } @@ -43,7 +43,7 @@ QList EntityEditFilters::getZonesByPosition(glm::vec3& position) { bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& propertiesIn, EntityItemProperties& propertiesOut, bool& wasChanged, EntityTree::FilterType filterType, EntityItemID& itemID, EntityItemPointer& existingEntity) { - + // get the ids of all the zones (plus the global entity edit filter) that the position // lies within auto zoneIDs = getZonesByPosition(position); @@ -51,12 +51,12 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper if (!itemID.isInvalidID() && id == itemID) { continue; } - - // get the filter pair, etc... + + // get the filter pair, etc... _lock.lockForRead(); FilterData filterData = _filterDataMap.value(id); _lock.unlock(); - + if (filterData.valid()) { if (filterData.rejectAll) { return false; @@ -153,13 +153,13 @@ bool EntityEditFilters::filter(glm::vec3& position, EntityItemProperties& proper // otherwise, assume it wants to pass all properties propertiesOut = propertiesIn; wasChanged = false; - + } else { return false; } } } - // if we made it here, + // if we made it here, return true; } @@ -175,23 +175,23 @@ void EntityEditFilters::removeFilter(EntityItemID entityID) { void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { QUrl scriptURL(filterURL); - - // setting it to an empty string is same as removing + + // setting it to an empty string is same as removing if (filterURL.size() == 0) { removeFilter(entityID); return; } - + // The following should be abstracted out for use in Agent.cpp (and maybe later AvatarMixer.cpp) if (scriptURL.scheme().isEmpty() || (scriptURL.scheme() == URL_SCHEME_FILE)) { qWarning() << "Cannot load script from local filesystem, because assignment may be on a different computer."; scriptRequestFinished(entityID); return; } - + // first remove any existing info for this entity removeFilter(entityID); - + // reject all edits until we load the script FilterData filterData; filterData.rejectAll = true; @@ -199,7 +199,7 @@ void EntityEditFilters::addFilter(EntityItemID entityID, QString filterURL) { _lock.lockForWrite(); _filterDataMap.insert(entityID, filterData); _lock.unlock(); - + auto scriptRequest = DependencyManager::get()->createResourceRequest( this, scriptURL, true, -1, "EntityEditFilters::addFilter"); if (!scriptRequest) { @@ -265,7 +265,7 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { FilterData filterData; filterData.engine = engine; filterData.rejectAll = false; - + // define the uncaughtException function QScriptEngine& engineRef = *engine; filterData.uncaughtExceptions = [&engineRef, urlString]() { return hadUncaughtExceptions(engineRef, urlString); }; @@ -369,11 +369,11 @@ void EntityEditFilters::scriptRequestFinished(EntityItemID entityID) { _lock.unlock(); qDebug() << "script request filter processed for entity id " << entityID; - + emit filterAdded(entityID, true); return; } - } + } } else if (scriptRequest) { const QString urlString = scriptRequest->getUrl().toString(); qCritical() << "Failed to download script at" << urlString; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 8992157681..0b3b8abba2 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -38,8 +38,6 @@ #include "LogHandler.h" #include "EntityEditFilters.h" #include "EntityDynamicFactoryInterface.h" -#include "QUrlAncestry.h" - static const quint64 DELETED_ENTITIES_EXTRA_USECS_TO_CONSIDER = USECS_PER_MSEC * 50; const float EntityTree::DEFAULT_MAX_TMP_ENTITY_LIFETIME = 60 * 60; // 1 hour @@ -100,7 +98,7 @@ EntityTree::~EntityTree() { eraseAllOctreeElements(false); } -void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { +void EntityTree::setEntityScriptSourceWhitelist(const QString& entityScriptSourceWhitelist) { _entityScriptSourceWhitelist = entityScriptSourceWhitelist.split(',', QString::SkipEmptyParts); } @@ -862,7 +860,7 @@ float findRayIntersectionSortingOp(const OctreeElementPointer& element, void* ex EntityItemID EntityTree::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, + bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType, bool* accurateResult) { @@ -1353,7 +1351,7 @@ bool EntityTree::verifyNonce(const QString& certID, const QString& nonce, Entity key = sent.second; } - QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----\n"; + QString annotatedKey = "-----BEGIN PUBLIC KEY-----\n" + key.insert(64, "\n") + "\n-----END PUBLIC KEY-----\n"; QByteArray hashedActualNonce = QCryptographicHash::hash(QByteArray(actualNonce.toUtf8()), QCryptographicHash::Sha256); bool verificationSuccess = EntityItemProperties::verifySignature(annotatedKey.toUtf8(), hashedActualNonce, QByteArray::fromBase64(nonce.toUtf8())); @@ -1797,7 +1795,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c if (newEntity) { newEntity->markAsChangedOnServer(); notifyNewlyCreatedEntity(*newEntity, senderNode); - + startLogging = usecTimestampNow(); if (wantEditLogging()) { qCDebug(entities) << "User [" << senderNode->getUUID() << "] added entity. ID:" @@ -1822,7 +1820,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c } } else { HIFI_FCDEBUG(entities(), "Edit failed. [" << message.getType() <<"] " << - "entity id:" << entityItemID << + "entity id:" << entityItemID << "existingEntity pointer:" << existingEntity.get()); } } @@ -2043,7 +2041,7 @@ bool EntityTree::hasEntitiesDeletedSince(quint64 sinceTime) { if (hasSomethingNewer) { int elapsed = usecTimestampNow() - considerEntitiesSince; int difference = considerEntitiesSince - sinceTime; - qCDebug(entities) << "EntityTree::hasEntitiesDeletedSince() sinceTime:" << sinceTime + qCDebug(entities) << "EntityTree::hasEntitiesDeletedSince() sinceTime:" << sinceTime << "considerEntitiesSince:" << considerEntitiesSince << "elapsed:" << elapsed << "difference:" << difference; } #endif @@ -2495,7 +2493,7 @@ bool EntityTree::writeToMap(QVariantMap& entityDescription, OctreeElementPointer return true; } -bool EntityTree::readFromMap(QVariantMap& map, const QUrlAncestry& ancestry) { +bool EntityTree::readFromMap(QVariantMap& map) { // These are needed to deal with older content (before adding inheritance modes) int contentVersion = map["Version"].toInt(); bool needsConversion = (contentVersion < (int)EntityVersion::ZoneLightInheritModes); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 8c787f8eb8..2f971b8566 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -22,8 +22,6 @@ #include "EntityTreeElement.h" #include "DeleteEntityOperator.h" #include "MovingEntitiesOperator.h" -#include "QUrlAncestry.h" - class EntityTree; using EntityTreePointer = std::shared_ptr; @@ -96,7 +94,7 @@ public: virtual EntityItemID findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, QVector entityIdsToInclude, QVector entityIdsToDiscard, - bool visibleOnly, bool collidableOnly, bool precisionPicking, + bool visibleOnly, bool collidableOnly, bool precisionPicking, OctreeElementPointer& element, float& distance, BoxFace& face, glm::vec3& surfaceNormal, QVariantMap& extraInfo, Octree::lockType lockType = Octree::TryLock, bool* accurateResult = NULL); @@ -172,7 +170,7 @@ public: void addNewlyCreatedHook(NewlyCreatedEntityHook* hook); void removeNewlyCreatedHook(NewlyCreatedEntityHook* hook); - bool hasAnyDeletedEntities() const { + bool hasAnyDeletedEntities() const { QReadLocker locker(&_recentlyDeletedEntitiesLock); return _recentlyDeletedEntityItemIDs.size() > 0; } @@ -180,7 +178,7 @@ public: bool hasEntitiesDeletedSince(quint64 sinceTime); static quint64 getAdjustedConsiderSince(quint64 sinceTime); - QMultiMap getRecentlyDeletedEntityIDs() const { + QMultiMap getRecentlyDeletedEntityIDs() const { QReadLocker locker(&_recentlyDeletedEntitiesLock); return _recentlyDeletedEntityItemIDs; } @@ -225,7 +223,7 @@ public: virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, bool skipThoseWithBadParents) override; - virtual bool readFromMap(QVariantMap& entityDescription, const QUrlAncestry& ancestry = {}) override; + virtual bool readFromMap(QVariantMap& entityDescription) override; glm::vec3 getContentsDimensions(); float getContentsLargestDimension(); diff --git a/libraries/fbx/src/GLTFReader.cpp b/libraries/fbx/src/GLTFReader.cpp index 89592c399c..b93dc3541b 100644 --- a/libraries/fbx/src/GLTFReader.cpp +++ b/libraries/fbx/src/GLTFReader.cpp @@ -40,7 +40,7 @@ GLTFReader::GLTFReader() { } -bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getStringVal(const QJsonObject& object, const QString& fieldname, QString& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isString()); if (_defined) { @@ -60,7 +60,7 @@ bool GLTFReader::getBoolVal(const QJsonObject& object, const QString& fieldname, return _defined; } -bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, int& value, QMap& defined) { bool _defined = (object.contains(fieldname) && !object[fieldname].isNull()); if (_defined) { @@ -70,7 +70,7 @@ bool GLTFReader::getIntVal(const QJsonObject& object, const QString& fieldname, return _defined; } -bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldname, double& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isDouble()); if (_defined) { @@ -79,7 +79,7 @@ bool GLTFReader::getDoubleVal(const QJsonObject& object, const QString& fieldnam defined.insert(fieldname, _defined); return _defined; } -bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldname, QJsonObject& value, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isObject()); if (_defined) { @@ -89,7 +89,7 @@ bool GLTFReader::getObjectVal(const QJsonObject& object, const QString& fieldnam return _defined; } -bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldname, QVector& values, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -104,7 +104,7 @@ bool GLTFReader::getIntArrayVal(const QJsonObject& object, const QString& fieldn return _defined; } -bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fieldname, QVector& values, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -119,7 +119,7 @@ bool GLTFReader::getDoubleArrayVal(const QJsonObject& object, const QString& fie return _defined; } -bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, +bool GLTFReader::getObjectArrayVal(const QJsonObject& object, const QString& fieldname, QJsonArray& objects, QMap& defined) { bool _defined = (object.contains(fieldname) && object[fieldname].isArray()); if (_defined) { @@ -229,7 +229,7 @@ bool GLTFReader::setAsset(const QJsonObject& object) { QJsonObject jsAsset; bool isAssetDefined = getObjectVal(object, "asset", jsAsset, _file.defined); if (isAssetDefined) { - if (!getStringVal(jsAsset, "version", _file.asset.version, + if (!getStringVal(jsAsset, "version", _file.asset.version, _file.asset.defined) || _file.asset.version != "2.0") { return false; } @@ -241,7 +241,7 @@ bool GLTFReader::setAsset(const QJsonObject& object) { bool GLTFReader::addAccessor(const QJsonObject& object) { GLTFAccessor accessor; - + getIntVal(object, "bufferView", accessor.bufferView, accessor.defined); getIntVal(object, "byteOffset", accessor.byteOffset, accessor.defined); getIntVal(object, "componentType", accessor.componentType, accessor.defined); @@ -261,7 +261,7 @@ bool GLTFReader::addAccessor(const QJsonObject& object) { bool GLTFReader::addAnimation(const QJsonObject& object) { GLTFAnimation animation; - + QJsonArray channels; if (getObjectArrayVal(object, "channels", channels, animation.defined)) { foreach(const QJsonValue & v, channels) { @@ -272,7 +272,7 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { if (getObjectVal(v.toObject(), "target", jsChannel, channel.defined)) { getIntVal(jsChannel, "node", channel.target.node, channel.target.defined); getIntVal(jsChannel, "path", channel.target.path, channel.target.defined); - } + } } } } @@ -291,7 +291,7 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { } } } - + _file.animations.push_back(animation); return true; @@ -299,20 +299,20 @@ bool GLTFReader::addAnimation(const QJsonObject& object) { bool GLTFReader::addBufferView(const QJsonObject& object) { GLTFBufferView bufferview; - + getIntVal(object, "buffer", bufferview.buffer, bufferview.defined); getIntVal(object, "byteLength", bufferview.byteLength, bufferview.defined); getIntVal(object, "byteOffset", bufferview.byteOffset, bufferview.defined); getIntVal(object, "target", bufferview.target, bufferview.defined); - + _file.bufferviews.push_back(bufferview); - + return true; } bool GLTFReader::addBuffer(const QJsonObject& object) { GLTFBuffer buffer; - + getIntVal(object, "byteLength", buffer.byteLength, buffer.defined); if (getStringVal(object, "uri", buffer.uri, buffer.defined)) { if (!readBinary(buffer.uri, buffer.blob)) { @@ -320,13 +320,13 @@ bool GLTFReader::addBuffer(const QJsonObject& object) { } } _file.buffers.push_back(buffer); - + return true; } bool GLTFReader::addCamera(const QJsonObject& object) { GLTFCamera camera; - + QJsonObject jsPerspective; QJsonObject jsOrthographic; QString type; @@ -346,28 +346,28 @@ bool GLTFReader::addCamera(const QJsonObject& object) { } else if (getStringVal(object, "type", type, camera.defined)) { camera.type = getCameraType(type); } - + _file.cameras.push_back(camera); - + return true; } bool GLTFReader::addImage(const QJsonObject& object) { GLTFImage image; - + QString mime; getStringVal(object, "uri", image.uri, image.defined); if (getStringVal(object, "mimeType", mime, image.defined)) { image.mimeType = getImageMimeType(mime); } getIntVal(object, "bufferView", image.bufferView, image.defined); - + _file.images.push_back(image); return true; } -bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& field, +bool GLTFReader::getIndexFromObject(const QJsonObject& object, const QString& field, int& outidx, QMap& defined) { QJsonObject subobject; if (getObjectVal(object, field, subobject, defined)) { @@ -393,20 +393,20 @@ bool GLTFReader::addMaterial(const QJsonObject& object) { getDoubleVal(object, "alphaCutoff", material.alphaCutoff, material.defined); QJsonObject jsMetallicRoughness; if (getObjectVal(object, "pbrMetallicRoughness", jsMetallicRoughness, material.defined)) { - getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", - material.pbrMetallicRoughness.baseColorFactor, + getDoubleArrayVal(jsMetallicRoughness, "baseColorFactor", + material.pbrMetallicRoughness.baseColorFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "baseColorTexture", - material.pbrMetallicRoughness.baseColorTexture, + getIndexFromObject(jsMetallicRoughness, "baseColorTexture", + material.pbrMetallicRoughness.baseColorTexture, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "metallicFactor", - material.pbrMetallicRoughness.metallicFactor, + getDoubleVal(jsMetallicRoughness, "metallicFactor", + material.pbrMetallicRoughness.metallicFactor, material.pbrMetallicRoughness.defined); - getDoubleVal(jsMetallicRoughness, "roughnessFactor", - material.pbrMetallicRoughness.roughnessFactor, + getDoubleVal(jsMetallicRoughness, "roughnessFactor", + material.pbrMetallicRoughness.roughnessFactor, material.pbrMetallicRoughness.defined); - getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", - material.pbrMetallicRoughness.metallicRoughnessTexture, + getIndexFromObject(jsMetallicRoughness, "metallicRoughnessTexture", + material.pbrMetallicRoughness.metallicRoughnessTexture, material.pbrMetallicRoughness.defined); } _file.materials.push_back(material); @@ -428,7 +428,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { getIntVal(jsPrimitive, "mode", primitive.mode, primitive.defined); getIntVal(jsPrimitive, "indices", primitive.indices, primitive.defined); getIntVal(jsPrimitive, "material", primitive.material, primitive.defined); - + QJsonObject jsAttributes; if (getObjectVal(jsPrimitive, "attributes", jsAttributes, primitive.defined)) { QStringList attrKeys = jsAttributes.keys(); @@ -455,7 +455,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { primitive.targets.push_back(target); } } - } + } mesh.primitives.push_back(primitive); } } @@ -469,7 +469,7 @@ bool GLTFReader::addMesh(const QJsonObject& object) { bool GLTFReader::addNode(const QJsonObject& object) { GLTFNode node; - + getStringVal(object, "name", node.name, node.defined); getIntVal(object, "camera", node.camera, node.defined); getIntVal(object, "mesh", node.mesh, node.defined); @@ -524,10 +524,10 @@ bool GLTFReader::addSkin(const QJsonObject& object) { } bool GLTFReader::addTexture(const QJsonObject& object) { - GLTFTexture texture; + GLTFTexture texture; getIntVal(object, "sampler", texture.sampler, texture.defined); getIntVal(object, "source", texture.source, texture.defined); - + _file.textures.push_back(texture); return true; @@ -535,7 +535,7 @@ bool GLTFReader::addTexture(const QJsonObject& object) { bool GLTFReader::parseGLTF(const QByteArray& model) { PROFILE_RANGE_EX(resource_parse, __FUNCTION__, 0xffff0000, nullptr); - + QJsonDocument d = QJsonDocument::fromJson(model); QJsonObject jsFile = d.object(); @@ -707,25 +707,25 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { foreach(int child, node.children) nodeDependencies[child].push_back(nodecount); nodecount++; } - + nodecount = 0; foreach(auto &node, _file.nodes) { // collect node transform - _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); + _file.nodes[nodecount].transforms.push_back(getModelTransform(node)); if (nodeDependencies[nodecount].size() == 1) { int parentidx = nodeDependencies[nodecount][0]; while (true) { // iterate parents // collect parents transforms - _file.nodes[nodecount].transforms.push_back(getModelTransform(_file.nodes[parentidx])); + _file.nodes[nodecount].transforms.push_back(getModelTransform(_file.nodes[parentidx])); if (nodeDependencies[parentidx].size() == 1) { parentidx = nodeDependencies[parentidx][0]; } else break; } } - + nodecount++; } - + //Build default joints geometry.joints.resize(1); geometry.joints[0].isFree = false; @@ -756,7 +756,7 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { setFBXMaterial(fbxMaterial, _file.materials[i]); } - + nodecount = 0; // Build meshes @@ -789,11 +789,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { QVector raw_vertices; QVector raw_normals; - bool success = addArrayOfType(indicesBuffer.blob, - indicesBufferview.byteOffset + indicesAccBoffset, - indicesAccessor.count, - part.triangleIndices, - indicesAccessor.type, + bool success = addArrayOfType(indicesBuffer.blob, + indicesBufferview.byteOffset + indicesAccBoffset, + indicesAccessor.count, + part.triangleIndices, + indicesAccessor.type, indicesAccessor.componentType); if (!success) { @@ -813,10 +813,10 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { int accBoffset = accessor.defined["byteOffset"] ? accessor.byteOffset : 0; if (key == "POSITION") { QVector vertices; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, vertices, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, vertices, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF POSITION data for model " << _url; @@ -827,11 +827,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "NORMAL") { QVector normals; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - normals, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + normals, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF NORMAL data for model " << _url; @@ -842,11 +842,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "TEXCOORD_0") { QVector texcoords; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - texcoords, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + texcoords, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_0 data for model " << _url; @@ -857,11 +857,11 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { } } else if (key == "TEXCOORD_1") { QVector texcoords; - success = addArrayOfType(buffer.blob, - bufferview.byteOffset + accBoffset, - accessor.count, - texcoords, - accessor.type, + success = addArrayOfType(buffer.blob, + bufferview.byteOffset + accBoffset, + accessor.count, + texcoords, + accessor.type, accessor.componentType); if (!success) { qWarning(modelformat) << "There was a problem reading glTF TEXCOORD_1 data for model " << _url; @@ -888,8 +888,8 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { mesh.meshExtents.addPoint(vertex); geometry.meshExtents.addPoint(vertex); } - - // since mesh.modelTransform seems to not have any effect I apply the transformation the model + + // since mesh.modelTransform seems to not have any effect I apply the transformation the model for (int h = 0; h < mesh.vertices.size(); h++) { glm::vec4 ver = glm::vec4(mesh.vertices[h], 1); if (node.transforms.size() > 0) { @@ -901,18 +901,18 @@ bool GLTFReader::buildGeometry(FBXGeometry& geometry, const QUrl& url) { mesh.meshIndex = geometry.meshes.size(); FBXReader::buildModelMesh(mesh, url.toString()); } - + } nodecount++; } - + return true; } -FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, +FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping, const QUrl& url, bool loadLightmaps, float lightmapLevel) { - + _url = url; // Normalize url for local files @@ -928,10 +928,10 @@ FBXGeometry* GLTFReader::readGLTF(QByteArray& model, const QVariantHash& mapping FBXGeometry& geometry = *geometryPtr; buildGeometry(geometry, url); - + //fbxDebugDump(geometry); return geometryPtr; - + } bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { @@ -940,7 +940,7 @@ bool GLTFReader::readBinary(const QString& url, QByteArray& outdata) { qCDebug(modelformat) << "binaryUrl: " << binaryUrl << " OriginalUrl: " << _url; bool success; std::tie(success, outdata) = requestData(binaryUrl); - + return success; } @@ -1000,7 +1000,7 @@ QNetworkReply* GLTFReader::request(QUrl& url, bool isTest) { FBXTexture GLTFReader::getFBXTexture(const GLTFTexture& texture) { FBXTexture fbxtex = FBXTexture(); fbxtex.texcoordSet = 0; - + if (texture.defined["source"]) { QString url = _file.images[texture.source].uri; QString fname = QUrl(url).fileName(); @@ -1020,10 +1020,10 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.defined["name"]) { fbxmat.name = fbxmat.materialID = material.name; } - + if (material.defined["emissiveFactor"] && material.emissiveFactor.size() == 3) { - glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], - material.emissiveFactor[1], + glm::vec3 emissive = glm::vec3(material.emissiveFactor[0], + material.emissiveFactor[1], material.emissiveFactor[2]); fbxmat._material->setEmissive(emissive); } @@ -1032,12 +1032,12 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia fbxmat.emissiveTexture = getFBXTexture(_file.textures[material.emissiveTexture]); fbxmat.useEmissiveMap = true; } - + if (material.defined["normalTexture"]) { fbxmat.normalTexture = getFBXTexture(_file.textures[material.normalTexture]); fbxmat.useNormalMap = true; } - + if (material.defined["occlusionTexture"]) { fbxmat.occlusionTexture = getFBXTexture(_file.textures[material.occlusionTexture]); fbxmat.useOcclusionMap = true; @@ -1045,7 +1045,7 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.defined["pbrMetallicRoughness"]) { fbxmat.isPBSMaterial = true; - + if (material.pbrMetallicRoughness.defined["metallicFactor"]) { fbxmat.metallic = material.pbrMetallicRoughness.metallicFactor; } @@ -1063,23 +1063,23 @@ void GLTFReader::setFBXMaterial(FBXMaterial& fbxmat, const GLTFMaterial& materia if (material.pbrMetallicRoughness.defined["roughnessFactor"]) { fbxmat._material->setRoughness(material.pbrMetallicRoughness.roughnessFactor); } - if (material.pbrMetallicRoughness.defined["baseColorFactor"] && + if (material.pbrMetallicRoughness.defined["baseColorFactor"] && material.pbrMetallicRoughness.baseColorFactor.size() == 4) { - glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], - material.pbrMetallicRoughness.baseColorFactor[1], + glm::vec3 dcolor = glm::vec3(material.pbrMetallicRoughness.baseColorFactor[0], + material.pbrMetallicRoughness.baseColorFactor[1], material.pbrMetallicRoughness.baseColorFactor[2]); fbxmat.diffuseColor = dcolor; fbxmat._material->setAlbedo(dcolor); fbxmat._material->setOpacity(material.pbrMetallicRoughness.baseColorFactor[3]); - } + } } } template -bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, +bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType) { - + QDataStream blobstream(bin); blobstream.setByteOrder(QDataStream::LittleEndian); blobstream.setVersion(QDataStream::Qt_5_9); @@ -1134,9 +1134,9 @@ bool GLTFReader::readArray(const QByteArray& bin, int byteOffset, int count, return true; } template -bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count, +bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count, QVector& outarray, int accessorType, int componentType) { - + switch (componentType) { case GLTFAccessorComponentType::BYTE: {} case GLTFAccessorComponentType::UNSIGNED_BYTE: { @@ -1158,8 +1158,8 @@ bool GLTFReader::addArrayOfType(const QByteArray& bin, int byteOffset, int count return false; } -void GLTFReader::retriangulate(const QVector& inIndices, const QVector& in_vertices, - const QVector& in_normals, QVector& outIndices, +void GLTFReader::retriangulate(const QVector& inIndices, const QVector& in_vertices, + const QVector& in_normals, QVector& outIndices, QVector& out_vertices, QVector& out_normals) { for (int i = 0; i < inIndices.size(); i = i + 3) { diff --git a/libraries/fbx/src/OBJReader.cpp b/libraries/fbx/src/OBJReader.cpp index 00109e9030..7a2cbff497 100644 --- a/libraries/fbx/src/OBJReader.cpp +++ b/libraries/fbx/src/OBJReader.cpp @@ -15,7 +15,7 @@ #include "OBJReader.h" #include // .obj files are not locale-specific. The C/ASCII charset applies. -#include +#include #include #include @@ -263,16 +263,16 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { default: materials[matName] = currentMaterial; #ifdef WANT_DEBUG - qCDebug(modelformat) << + qCDebug(modelformat) << "OBJ Reader Last material illumination model:" << currentMaterial.illuminationModel << - " shininess:" << currentMaterial.shininess << + " shininess:" << currentMaterial.shininess << " opacity:" << currentMaterial.opacity << - " diffuse color:" << currentMaterial.diffuseColor << - " specular color:" << currentMaterial.specularColor << - " emissive color:" << currentMaterial.emissiveColor << - " diffuse texture:" << currentMaterial.diffuseTextureFilename << - " specular texture:" << currentMaterial.specularTextureFilename << - " emissive texture:" << currentMaterial.emissiveTextureFilename << + " diffuse color:" << currentMaterial.diffuseColor << + " specular color:" << currentMaterial.specularColor << + " emissive color:" << currentMaterial.emissiveColor << + " diffuse texture:" << currentMaterial.diffuseTextureFilename << + " specular texture:" << currentMaterial.specularTextureFilename << + " emissive texture:" << currentMaterial.emissiveTextureFilename << " bump texture:" << currentMaterial.bumpTextureFilename << " opacity texture:" << currentMaterial.opacityTextureFilename; #endif @@ -352,7 +352,7 @@ void OBJReader::parseMaterialLibrary(QIODevice* device) { } } } -} +} void OBJReader::parseTextureLine(const QByteArray& textureLine, QByteArray& filename, OBJMaterialTextureOptions& textureOptions) { // Texture options reference http://paulbourke.net/dataformats/mtl/ @@ -794,7 +794,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m n0 = checked_at(normals, face.normalIndices[0]); n1 = checked_at(normals, face.normalIndices[1]); n2 = checked_at(normals, face.normalIndices[2]); - } else { + } else { // generate normals from triangle plane if not provided n0 = n1 = n2 = glm::cross(v1 - v0, v2 - v0); } @@ -924,7 +924,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m bool applyNonMetallic = false; bool fresnelOn = false; - // Illumination model reference http://paulbourke.net/dataformats/mtl/ + // Illumination model reference http://paulbourke.net/dataformats/mtl/ switch (objMaterial.illuminationModel) { case 0: // Color on and Ambient off // We don't support ambient = do nothing? @@ -968,7 +968,7 @@ FBXGeometry::Pointer OBJReader::readOBJ(QByteArray& model, const QVariantHash& m case 10: // Casts shadows onto invisible surfaces // Do nothing? break; - } + } if (applyTransparency) { fbxMaterial.opacity = std::max(fbxMaterial.opacity, ILLUMINATION_MODEL_MIN_OPACITY); diff --git a/libraries/networking/src/AssetResourceRequest.cpp b/libraries/networking/src/AssetResourceRequest.cpp index 23ab1548a0..6f5b13f98d 100644 --- a/libraries/networking/src/AssetResourceRequest.cpp +++ b/libraries/networking/src/AssetResourceRequest.cpp @@ -39,7 +39,7 @@ AssetResourceRequest::~AssetResourceRequest() { if (_assetMappingRequest) { _assetMappingRequest->deleteLater(); } - + if (_assetRequest) { _assetRequest->deleteLater(); } @@ -82,7 +82,7 @@ void AssetResourceRequest::requestMappingForPath(const AssetUtils::AssetPath& pa // make sure we'll hear about the result of the get mapping request connect(_assetMappingRequest, &GetMappingRequest::finished, this, [this, path](GetMappingRequest* request){ auto statTracker = DependencyManager::get(); - + Q_ASSERT(_state == InProgress); Q_ASSERT(request == _assetMappingRequest); diff --git a/libraries/networking/src/FileResourceRequest.h b/libraries/networking/src/FileResourceRequest.h index 12b5b7d72e..fa9a1617a9 100644 --- a/libraries/networking/src/FileResourceRequest.h +++ b/libraries/networking/src/FileResourceRequest.h @@ -12,19 +12,19 @@ #ifndef hifi_FileResourceRequest_h #define hifi_FileResourceRequest_h -#include "ResourceRequest.h" -#include "QUrlAncestry.h" +#include +#include "ResourceRequest.h" class FileResourceRequest : public ResourceRequest { Q_OBJECT public: FileResourceRequest( - const QUrlAncestry& urlAncestry, + const QUrl& url, const bool isObservable = true, const qint64 callerId = -1, const QString& extra = "" - ) : ResourceRequest(urlAncestry, isObservable, callerId, extra) { } + ) : ResourceRequest(url, isObservable, callerId, extra) { } protected: virtual void doSend() override; diff --git a/libraries/networking/src/HTTPResourceRequest.h b/libraries/networking/src/HTTPResourceRequest.h index 41f605e856..c725934f2f 100644 --- a/libraries/networking/src/HTTPResourceRequest.h +++ b/libraries/networking/src/HTTPResourceRequest.h @@ -13,21 +13,20 @@ #define hifi_HTTPResourceRequest_h #include +#include #include #include "ResourceRequest.h" -#include "QUrlAncestry.h" - class HTTPResourceRequest : public ResourceRequest { Q_OBJECT public: HTTPResourceRequest( - const QUrlAncestry& urlAncestry, + const QUrl& url, const bool isObservable = true, const qint64 callerId = -1, const QString& = "" - ) : ResourceRequest(urlAncestry, isObservable, callerId) { } + ) : ResourceRequest(url, isObservable, callerId) { } ~HTTPResourceRequest(); protected: diff --git a/libraries/networking/src/NetworkAccessManager.cpp b/libraries/networking/src/NetworkAccessManager.cpp index c5229d65ac..f73243e675 100644 --- a/libraries/networking/src/NetworkAccessManager.cpp +++ b/libraries/networking/src/NetworkAccessManager.cpp @@ -22,7 +22,7 @@ QNetworkAccessManager& NetworkAccessManager::getInstance() { if (!networkAccessManagers.hasLocalData()) { networkAccessManagers.setLocalData(new QNetworkAccessManager()); } - + return *networkAccessManagers.localData(); } diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 186addbd86..f2fbe41804 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -320,7 +320,7 @@ QVariantList ResourceCache::getResourceList() { return list; } - + void ResourceCache::setRequestLimit(uint32_t limit) { auto sharedItems = DependencyManager::get(); sharedItems->setRequestLimit(limit); @@ -337,7 +337,6 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& QReadLocker locker(&_resourcesLock); resource = _resources.value(url).lock(); } - if (resource) { removeUnusedResource(resource); } @@ -382,7 +381,7 @@ void ResourceCache::addUnusedResource(const QSharedPointer& resource) return; } reserveUnusedResource(resource->getBytes()); - + resource->setLRUKey(++_lastLRUKey); { @@ -411,7 +410,7 @@ void ResourceCache::reserveUnusedResource(qint64 resourceSize) { _unusedResourcesSize + resourceSize > _unusedResourcesMaxSize) { // unload the oldest resource QMap >::iterator it = _unusedResources.begin(); - + it.value()->setCache(nullptr); auto size = it.value()->getBytes(); @@ -477,7 +476,7 @@ void ResourceCache::updateTotalSize(const qint64& deltaSize) { emit dirty(); } - + QList> ResourceCache::getLoadingRequests() { return DependencyManager::get()->getLoadingRequests(); } @@ -592,7 +591,7 @@ void Resource::refresh() { _request = nullptr; ResourceCache::requestCompleted(_self); } - + _activeUrl = _url; init(); ensureLoading(); @@ -606,7 +605,7 @@ void Resource::allReferencesCleared() { } if (_cache && isCacheable()) { - // create and reinsert new shared pointer + // create and reinsert new shared pointer QSharedPointer self(this, &Resource::deleter); setSelf(self); reinsert(); @@ -631,10 +630,10 @@ void Resource::init(bool resetLoaded) { _loaded = false; } _attempts = 0; - + if (_url.isEmpty()) { _startedLoading = _loaded = true; - + } else if (!(_url.isValid())) { _startedLoading = _failedToLoad = true; } @@ -756,7 +755,7 @@ void Resource::handleReplyFinished() { } else { handleFailedRequest(result); } - + _request->disconnect(this); _request->deleteLater(); _request = nullptr; diff --git a/libraries/networking/src/ResourceManager.cpp b/libraries/networking/src/ResourceManager.cpp index d9774e3437..9539a10c2d 100644 --- a/libraries/networking/src/ResourceManager.cpp +++ b/libraries/networking/src/ResourceManager.cpp @@ -26,7 +26,6 @@ #include "NetworkAccessManager.h" #include "NetworkLogging.h" - ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(atpSupportEnabled) { _thread.setObjectName("Resource Manager Thread"); @@ -125,7 +124,6 @@ ResourceRequest* ResourceManager::createResourceRequest( ResourceRequest* request = nullptr; - qDebug() << "!!!! in createResourceRequest " << callerId; if (scheme == URL_SCHEME_FILE || scheme == URL_SCHEME_QRC) { request = new FileResourceRequest(normalizedURL, isObservable, callerId, extra); } else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) { @@ -146,7 +144,6 @@ ResourceRequest* ResourceManager::createResourceRequest( QObject::connect(parent, &QObject::destroyed, request, &QObject::deleteLater); } request->moveToThread(&_thread); - return request; } diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index a651e9a2b6..acaf657dfe 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -21,9 +21,8 @@ void ResourceRequest::send() { if (_isObservable) { DependencyManager::get()->update( - _urlAncestry, _callerId, _extra + " => ResourceRequest::send" ); + _url, _callerId, _extra + " => ResourceRequest::send" ); } - if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection); return; diff --git a/libraries/networking/src/ResourceRequest.h b/libraries/networking/src/ResourceRequest.h index 3ce1a9da2b..eb306ca5be 100644 --- a/libraries/networking/src/ResourceRequest.h +++ b/libraries/networking/src/ResourceRequest.h @@ -18,8 +18,6 @@ #include #include "ByteRange.h" -#include "QUrlAncestry.h" - const QString STAT_ATP_REQUEST_STARTED = "StartedATPRequest"; const QString STAT_HTTP_REQUEST_STARTED = "StartedHTTPRequest"; @@ -46,15 +44,14 @@ public: static const bool IS_NOT_OBSERVABLE = false; ResourceRequest( - const QUrlAncestry& urlAncestry, + const QUrl& url, const bool isObservable = IS_OBSERVABLE, const qint64 callerId = -1, const QString& extra = "" - ) : _urlAncestry(urlAncestry), + ) : _url(url), _isObservable(isObservable), _callerId(callerId), - _extra(extra), - _url(urlAncestry.last()) + _extra(extra) { } virtual ~ResourceRequest() = default; @@ -103,7 +100,6 @@ protected: virtual void doSend() = 0; void recordBytesDownloadedInStats(const QString& statName, int64_t bytesReceived); - QUrl _url; QUrl _relativePathURL; State _state { NotStarted }; @@ -119,7 +115,6 @@ protected: bool _isObservable; qint64 _callerId; QString _extra; - QUrlAncestry _urlAncestry; }; #endif diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index a50438dd54..efddf3c14f 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -669,7 +669,7 @@ OctreeElementPointer Octree::getElementEnclosingPoint(const glm::vec3& point, Oc return args.element; } -bool Octree::readFromFile(const char* fileName, const QUrlAncestry& urlAncestry) { +bool Octree::readFromFile(const char* fileName) { QString qFileName = findMostRecentFileExtension(fileName, PERSIST_EXTENSIONS); if (qFileName.endsWith(".json.gz")) { @@ -689,7 +689,7 @@ bool Octree::readFromFile(const char* fileName, const QUrlAncestry& urlAncestry) qCDebug(octree) << "Loading file" << qFileName << "..."; - bool success = readFromStream(fileLength, fileInputStream, "", urlAncestry); + bool success = readFromStream(fileLength, fileInputStream, ""); file.close(); @@ -737,8 +737,7 @@ QString getMarketplaceID(const QString& urlString) { bool Octree::readFromURL( const QString& urlString, const bool isObservable, - const qint64 callerId, - const QUrlAncestry& urlAncestry + const qint64 callerId ) { QString trimmedUrl = urlString.trimmed(); QString marketplaceID = getMarketplaceID(trimmedUrl); @@ -767,19 +766,18 @@ bool Octree::readFromURL( if (wasCompressed) { QDataStream inputStream(uncompressedJsonData); - return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID, urlAncestry); + return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID); } QDataStream inputStream(data); - return readFromStream(data.size(), inputStream, marketplaceID, urlAncestry); + return readFromStream(data.size(), inputStream, marketplaceID); } bool Octree::readFromStream( uint64_t streamLength, QDataStream& inputStream, - const QString& marketplaceID, - const QUrlAncestry& urlAncestry + const QString& marketplaceID ) { // decide if this is binary SVO or JSON-formatted SVO QIODevice *device = inputStream.device(); @@ -792,7 +790,7 @@ bool Octree::readFromStream( return false; } else { qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength; - return readJSONFromStream(streamLength, inputStream, marketplaceID, urlAncestry); + return readJSONFromStream(streamLength, inputStream, marketplaceID); } } @@ -824,8 +822,7 @@ const int READ_JSON_BUFFER_SIZE = 2048; bool Octree::readJSONFromStream( uint64_t streamLength, QDataStream& inputStream, - const QString& marketplaceID, /*=""*/ - const QUrlAncestry& urlAncestry + const QString& marketplaceID /*=""*/ ) { // if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until // we get an eof. Leave streamLength parameter for consistency. @@ -851,7 +848,7 @@ bool Octree::readJSONFromStream( } QVariant asVariant = asDocument.toVariant(); QVariantMap asMap = asVariant.toMap(); - bool success = readFromMap(asMap, urlAncestry); + bool success = readFromMap(asMap); delete[] rawData; return success; } diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 53acbc5a60..44b429582a 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -29,7 +29,6 @@ #include "OctreePacketData.h" #include "OctreeSceneStats.h" #include "OctreeUtils.h" -#include "QUrlAncestry.h" class ReadBitstreamToTreeParams; class Octree; @@ -210,13 +209,13 @@ public: bool skipThoseWithBadParents) = 0; // Octree importers - bool readFromFile(const char* filename, const QUrlAncestry& urlAncestry = {}); - bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1, const QUrlAncestry& urlAncestry = {}); // will support file urls as well... - bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const QUrlAncestry& urlAncestry = {}); + bool readFromFile(const char* filename); + bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1); // will support file urls as well... + bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); bool readSVOFromStream(uint64_t streamLength, QDataStream& inputStream); - bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const QUrlAncestry& urlAncestry = {}); + bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); bool readJSONFromGzippedFile(QString qFileName); - virtual bool readFromMap(QVariantMap& entityDescription, const QUrlAncestry& urlAncestry = {}) = 0; + virtual bool readFromMap(QVariantMap& entityDescription) = 0; uint64_t getOctreeElementsCount(); diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index eccb812f09..cbcafe9c7d 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -258,29 +258,24 @@ void OffscreenSurface::setMaxFps(uint8_t maxFps) { } void OffscreenSurface::load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback) { - qDebug() << "Here 1"; loadInternal(qmlSource, false, parent, [callback](QQmlContext* context, QQuickItem* newItem) { QJSValue(callback).call(QJSValueList() << context->engine()->newQObject(newItem)); }); } void OffscreenSurface::load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& callback) { - qDebug() << "Here 2"; loadInternal(qmlSource, createNewContext, nullptr, callback); } void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback, const QmlContextCallback& contextCallback) { - qDebug() << "Here 3"; loadInternal(qmlSource, true, nullptr, callback, contextCallback); } void OffscreenSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { - qDebug() << "Here 4"; load(qmlSource, false, callback); } void OffscreenSurface::load(const QString& qmlSourceFile, const QmlContextObjectCallback& callback) { - qDebug() << "Here 5"; return load(QUrl(qmlSourceFile), callback); } diff --git a/libraries/script-engine/src/FileScriptingInterface.cpp b/libraries/script-engine/src/FileScriptingInterface.cpp index 103ed6d232..4e07877d57 100644 --- a/libraries/script-engine/src/FileScriptingInterface.cpp +++ b/libraries/script-engine/src/FileScriptingInterface.cpp @@ -50,7 +50,7 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool tempDir = zipTemp.path(); path.remove("file:///"); } - + qCDebug(scriptengine) << "Temporary directory at: " + tempDir; if (!isTempDir(tempDir)) { qCDebug(scriptengine) << "Temporary directory mismatch; risk of losing files"; @@ -58,7 +58,7 @@ void FileScriptingInterface::runUnzip(QString path, QUrl url, bool autoAdd, bool } QStringList fileList = unzipFile(path, tempDir); - + if (!fileList.isEmpty()) { qCDebug(scriptengine) << "First file to upload: " + fileList.first(); } else { diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 297d3bb924..a74d185c6a 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -63,7 +63,7 @@ QScriptValue XMLHttpRequestClass::constructor(QScriptContext* context, QScriptEn QScriptValue XMLHttpRequestClass::getStatus() const { if (_reply) { return QScriptValue(_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()); - } + } return QScriptValue(0); } @@ -144,7 +144,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) { auto accountManager = DependencyManager::get(); - + if (accountManager->hasValidAccessToken()) { static const QString HTTP_AUTHORIZATION_HEADER = "Authorization"; QString bearerString = "Bearer " + accountManager->getAccountInfo().getAccessToken().token; diff --git a/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h b/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h deleted file mode 100644 index fd3647b19e..0000000000 --- a/libraries/shared/src/EntityItemWeakPointerWithUrlAncestry.h +++ /dev/null @@ -1,31 +0,0 @@ -// -// EntityItemWeakPointerWithUrlAncestry.h -// libraries/shared/src/ -// -// Created by Kerry Ivan Kurian 10/15/18 -// 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 -// - -#ifndef hifi_EntityItemWeakPointerWithUrlAncestry_h -#define hifi_EntityItemWeakPointerWithUrlAncestry_h - -#include "EntityTypes.h" -#include "QUrlAncestry.h" - - -struct EntityItemWeakPointerWithUrlAncestry { - EntityItemWeakPointerWithUrlAncestry( - const EntityItemWeakPointer& a, - const QUrlAncestry& b - ) : entityItemWeakPointer(a), urlAncestry(b) {} - - EntityItemWeakPointer entityItemWeakPointer; - QUrlAncestry urlAncestry; -}; - - -#endif // hifi_EntityItemWeakPointerWithUrlAncestry_h - diff --git a/libraries/shared/src/QUrlAncestry.cpp b/libraries/shared/src/QUrlAncestry.cpp deleted file mode 100644 index f38c663803..0000000000 --- a/libraries/shared/src/QUrlAncestry.cpp +++ /dev/null @@ -1,35 +0,0 @@ -// -// QUrlAncestry.cpp -// libraries/shared/src/ -// -// Created by Kerry Ivan Kurian on 10/12/18. -// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "QUrlAncestry.h" - - -QUrlAncestry::QUrlAncestry(const QUrl& resource, const QUrl& referrer) { - this->append(referrer); - this->append(resource); -} - -QUrlAncestry::QUrlAncestry( - const QUrl& resource, - const QUrlAncestry& ancestors) : QVector(ancestors) -{ - this->append(resource); -} - -void QUrlAncestry::toJson(QJsonArray& array) const { - for (auto const& qurl : *this) { - array.append(qurl.toDisplayString()); - } -} - -const QUrl QUrlAncestry::url() const { - return this->last(); -} diff --git a/libraries/shared/src/QUrlAncestry.h b/libraries/shared/src/QUrlAncestry.h deleted file mode 100644 index 84c32ff7c1..0000000000 --- a/libraries/shared/src/QUrlAncestry.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// QUrlAncestry.h -// libraries/shared/src/ -// -// Created by Kerry Ivan Kurian on 10/12/18. -// Copyright (c) 2018 High Fidelity, Inc. All rights reserved. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - - -#ifndef hifi_QUrlAncestry_H -#define hifi_QUrlAncestry_H - -#include -#include -#include - - -class QUrlAncestry : public QVector { -public: - QUrlAncestry() {} - QUrlAncestry(const QUrl& resource, const QUrl& referrer = QUrl("__NONE__")); - QUrlAncestry(const QUrl& resource, const QUrlAncestry& ancestors); - - void toJson(QJsonArray& array) const; - const QUrl url() const; -}; - - -#endif // hifi_QUrlVector_H diff --git a/libraries/shared/src/ResourceRequestObserver.cpp b/libraries/shared/src/ResourceRequestObserver.cpp index 6c52fcdc79..5e0925520a 100644 --- a/libraries/shared/src/ResourceRequestObserver.cpp +++ b/libraries/shared/src/ResourceRequestObserver.cpp @@ -15,22 +15,12 @@ #include #include #include "ResourceRequestObserver.h" -#include "QUrlAncestry.h" - -// void ResourceRequestObserver::update(const QNetworkRequest& request, const qint64 callerId, const QString& extra) { -// update(QUrlAncestry(request.url()), callerId, extra); -// } - -void ResourceRequestObserver::update( - const QUrlAncestry& urlAncestry, +void ResourceRequestObserver::update(const QUrl& requestUrl, const qint64 callerId, - const QString& extra -) { + const QString& extra) { QJsonArray array; - urlAncestry.toJson(array); - QJsonObject data { - { "url", array }, + QJsonObject data { { "url", requestUrl.toString() }, { "callerId", callerId }, { "extra", extra } }; diff --git a/libraries/shared/src/ResourceRequestObserver.h b/libraries/shared/src/ResourceRequestObserver.h index edccdb5e48..1b1bc322e5 100644 --- a/libraries/shared/src/ResourceRequestObserver.h +++ b/libraries/shared/src/ResourceRequestObserver.h @@ -15,7 +15,6 @@ #include #include "DependencyManager.h" -#include "QUrlAncestry.h" class ResourceRequestObserver : public QObject, public Dependency { @@ -23,8 +22,7 @@ class ResourceRequestObserver : public QObject, public Dependency { SINGLETON_DEPENDENCY public: - // void update(const QNetworkRequest& request, const qint64 callerId = -1, const QString& extra = ""); - void update(const QUrlAncestry& urlAncestry, const qint64 callerId = -1, const QString& extra = ""); + void update(const QUrl& requestUrl, const qint64 callerId = -1, const QString& extra = ""); signals: void resourceRequestEvent(QVariantMap result); From bccca94111f175e9d9e5daded7e3e219ee9a9f6e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 13:21:52 -0700 Subject: [PATCH 09/16] Prevent duplicate resources in logs. Thanks to Roxanne! --- libraries/networking/src/ResourceRequest.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/networking/src/ResourceRequest.cpp b/libraries/networking/src/ResourceRequest.cpp index acaf657dfe..c63bd4c563 100644 --- a/libraries/networking/src/ResourceRequest.cpp +++ b/libraries/networking/src/ResourceRequest.cpp @@ -19,15 +19,15 @@ void ResourceRequest::send() { - if (_isObservable) { - DependencyManager::get()->update( - _url, _callerId, _extra + " => ResourceRequest::send" ); - } if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "send", Qt::QueuedConnection); return; } + if (_isObservable) { + DependencyManager::get()->update(_url, _callerId, _extra + " => ResourceRequest::send"); + } + Q_ASSERT(_state == NotStarted); _state = InProgress; From d44f9e2ccc9b51a4a93a990997f61cc86c4b6be7 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 15:21:37 -0700 Subject: [PATCH 10/16] Tons of improvements --- .../marketplaceItemTester/ItemUnderTest.qml | 25 ++++++--- .../MarketplaceItemTester.qml | 38 +++----------- interface/src/commerce/QmlCommerce.cpp | 25 ++++++--- interface/src/commerce/QmlCommerce.h | 2 +- .../scripting/ClipboardScriptingInterface.cpp | 10 +--- scripts/system/marketplaces/marketplaces.js | 52 ++++++++++++------- 6 files changed, 79 insertions(+), 73 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index dcb67f3f12..c5aaf20a8f 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -29,7 +29,7 @@ Rectangle { "forward": function(resource, assetType, resourceObjectId){ switch(assetType) { case "application": - Commerce.openApp(resource); + Commerce.installApp(resource, true); break; case "avatar": MyAvatar.useFullAvatarURL(resource); @@ -137,7 +137,7 @@ Rectangle { "entity": hifi.glyphs.wand, "trash": hifi.glyphs.trash, "unknown": hifi.glyphs.circleSlash, - "wearable": hifi.glyphs.hat, + "wearable": hifi.glyphs.hat } property int color: hifi.buttons.blue; property int colorScheme: hifi.colorSchemes.dark; @@ -149,7 +149,18 @@ Rectangle { enabled: comboBox.model[comboBox.currentIndex] !== "unknown" onClicked: { - root.actions["forward"](resource, comboBox.currentText, resourceObjectId); + if (model.currentlyRecordingResources) { + model.currentlyRecordingResources = false; + } else { + model.resourceAccessEventText = ""; + model.currentlyRecordingResources = true; + root.actions["forward"](resource, comboBox.currentText, resourceObjectId); + } + sendToScript({ + method: "tester_updateResourceRecordingStatus", + objectId: resourceListModel.get(index).resourceObjectId, + status: model.currentlyRecordingResources + }); } background: Rectangle { @@ -189,7 +200,7 @@ Rectangle { contentItem: Item { HifiStylesUit.HiFiGlyphs { id: rezIcon; - text: actionButton.glyphs[comboBox.model[comboBox.currentIndex]]; + text: model.currentlyRecordingResources ? hifi.glyphs.scriptStop : actionButton.glyphs[comboBox.model[comboBox.currentIndex]]; anchors.fill: parent size: 30; horizontalAlignment: Text.AlignHCenter; @@ -304,9 +315,9 @@ Rectangle { color: hifi.colors.white text: { if (root.detailsExpanded) { - return resourceAccessEventText + return model.resourceAccessEventText } else { - return (resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." + return (model.resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." } } font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) @@ -340,7 +351,7 @@ Rectangle { anchors.top: detailsTextContainer.bottom anchors.topMargin: 8 anchors.right: parent.right - width: 150 + width: 160 height: 30 text: "Copy to Clipboard" diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml index 5f2268132c..2a4f2d0e22 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/MarketplaceItemTester.qml @@ -24,17 +24,13 @@ Rectangle { id: root property string installedApps - property string resourceAccessEventText property var nextResourceObjectId: 0 - property var startDate HifiStylesUit.HifiConstants { id: hifi } ListModel { id: resourceListModel } color: hifi.colors.darkGray - Component.onCompleted: startDate = new Date() - // // TITLE BAR START // @@ -147,8 +143,7 @@ Rectangle { model: resourceListModel spacing: 8 - delegate: ItemUnderTest { - } + delegate: ItemUnderTest { } } Item { @@ -194,8 +189,6 @@ Rectangle { if (resource) { print("!!!! building resource object"); var resourceObj = buildResourceObj(resource); - print("!!!! installing resource object"); - installResourceObj(resourceObj); print("!!!! notifying script of resource object"); sendToScript({ method: 'tester_newResourceObject', @@ -235,6 +228,7 @@ Rectangle { switch (message.method) { case "newResourceObjectInTest": var resourceObject = message.resourceObject; + resourceListModel.clear(); // REMOVE THIS once we support specific referrers resourceListModel.append(resourceObject); spinner.visible = false; break; @@ -244,21 +238,9 @@ Rectangle { spinner.visible = false; break; case "resourceRequestEvent": - try { - var date = new Date(JSON.parse(message.data.date)); - } catch(err) { - print("!!!!! Date conversion failed: " + JSON.stringify(message.data)); - } - // XXX Eventually this date check goes away b/c we will - // be able to match up resouce access events to resource - // object ids, ignoring those that have -1 resource - // object ids. - if (date >= startDate) { - resourceAccessEventText += ( - "[" + date.toISOString() + "] " + - message.data.url.toString().replace("__NONE__,", "") + "\n" - ); - } + // When we support multiple items under test simultaneously, + // we'll have to replace "0" with the correct index. + resourceListModel.setProperty(0, "resourceAccessEventText", message.resourceAccessEventText); break; } } @@ -270,17 +252,13 @@ Rectangle { resource.match(/\.json\.gz$/) ? "content set" : resource.match(/\.json$/) ? "entity or wearable" : "unknown"); - return { "resourceObjectId": nextResourceObjectId++, + // Uncomment this once we support more than one item in test at the same time + //nextResourceObjectId++; + return { "resourceObjectId": nextResourceObjectId, "resource": resource, "assetType": assetType }; } - function installResourceObj(resourceObj) { - if ("application" === resourceObj.assetType) { - Commerce.installApp(resourceObj.resource); - } - } - function toUrl(resource) { var httpPattern = /^http/i; return httpPattern.test(resource) ? resource : "file:///" + resource; diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 83907df103..ffe89ffc5b 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -315,7 +315,7 @@ QString QmlCommerce::getInstalledApps(const QString& justInstalledAppID) { return installedAppsFromMarketplace; } -bool QmlCommerce::installApp(const QString& itemHref) { +bool QmlCommerce::installApp(const QString& itemHref, const bool& alsoOpenImmediately) { if (!QDir(_appsPath).exists()) { if (!QDir().mkdir(_appsPath)) { qCDebug(commerce) << "Couldn't make _appsPath directory."; @@ -325,8 +325,8 @@ bool QmlCommerce::installApp(const QString& itemHref) { QUrl appHref(itemHref); - auto request = DependencyManager::get()->createResourceRequest( - this, appHref, true, -1, "QmlCommerce::installApp"); + auto request = + DependencyManager::get()->createResourceRequest(this, appHref, true, -1, "QmlCommerce::installApp"); if (!request) { qCDebug(commerce) << "Couldn't create resource request for app."; @@ -358,13 +358,22 @@ bool QmlCommerce::installApp(const QString& itemHref) { QJsonObject appFileJsonObject = appFileJsonDocument.object(); QString scriptUrl = appFileJsonObject["scriptURL"].toString(); - if ((DependencyManager::get()->loadScript(scriptUrl.trimmed())).isNull()) { - qCDebug(commerce) << "Couldn't load script."; - return false; + // Don't try to re-load (install) a script if it's already running + QStringList runningScripts = DependencyManager::get()->getRunningScripts(); + if (!runningScripts.contains(scriptUrl)) { + if ((DependencyManager::get()->loadScript(scriptUrl.trimmed())).isNull()) { + qCDebug(commerce) << "Couldn't load script."; + return false; + } + + QFileInfo appFileInfo(appFile); + emit appInstalled(appFileInfo.baseName()); + } + + if (alsoOpenImmediately) { + QmlCommerce::openApp(itemHref); } - QFileInfo appFileInfo(appFile); - emit appInstalled(appFileInfo.baseName()); return true; }); request->send(); diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index bee30e1b62..2e3c0ec24d 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -88,7 +88,7 @@ protected: Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID); Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = ""); - Q_INVOKABLE bool installApp(const QString& appHref); + Q_INVOKABLE bool installApp(const QString& appHref, const bool& alsoOpenImmediately = false); Q_INVOKABLE bool uninstallApp(const QString& appHref); Q_INVOKABLE bool openApp(const QString& appHref); diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index c14f4ea895..c2d2b69883 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -46,17 +46,11 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float return retVal; } -bool ClipboardScriptingInterface::importEntities( - const QString& filename, - const bool isObservable, - const qint64 callerId -) { +bool ClipboardScriptingInterface::importEntities(const QString& filename) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "importEntities", Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename), - Q_ARG(const bool, isObservable), - Q_ARG(const qint64, callerId)); + Q_ARG(const QString&, filename)); return retVal; } diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index d59a6b89d5..d6056f83a6 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -52,27 +52,30 @@ var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't writ var resourceRequestEvents = []; function signalResourceRequestEvent(data) { + // Once we can tie resource request events to specific resources, + // we will have to update the "0" in here. + resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + + data.url.toString().replace("__NONE__,", "") + "\n"; + ui.tablet.sendToQml({ method: "resourceRequestEvent", - data: data }); + data: data, + resourceAccessEventText: resourceObjectsInTest[0].resourceAccessEventText + }); } function onResourceRequestEvent(data) { - var resourceRequestEvent = { - "date": JSON.stringify(new Date()), - "url": data.url, - "callerId": data.callerId, - "extra": data.extra }; - resourceRequestEvents.push(resourceRequestEvent); - signalResourceRequestEvent(resourceRequestEvent); -} - -function pushResourceRequestEvents() { - var length = resourceRequestEvents.length - for (var i = 0; i < length; i++) { - if (i in resourceRequestEvents) { - signalResourceRequestEvent(resourceRequestEvents[i]); - } + // Once we can tie resource request events to specific resources, + // we will have to update the "0" in here. + if (resourceObjectsInTest[0] && resourceObjectsInTest[0].currentlyRecordingResources) { + var resourceRequestEvent = { + "date": new Date(), + "url": data.url, + "callerId": data.callerId, + "extra": data.extra + }; + resourceRequestEvents.push(resourceRequestEvent); + signalResourceRequestEvent(resourceRequestEvent); } } @@ -849,7 +852,8 @@ var resourceObjectsInTest = []; function signalNewResourceObjectInTest(resourceObject) { ui.tablet.sendToQml({ method: "newResourceObjectInTest", - resourceObject: resourceObject }); + resourceObject: resourceObject + }); } var onQmlMessageReceived = function onQmlMessageReceived(message) { @@ -915,6 +919,9 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { break; case 'tester_newResourceObject': var resourceObject = message.resourceObject; + resourceObjectsInTest = []; // REMOVE THIS once we support specific referrers + resourceObject.currentlyRecordingResources = false; + resourceObject.resourceAccessEventText = ""; resourceObjectsInTest[resourceObject.resourceObjectId] = resourceObject; signalNewResourceObjectInTest(resourceObject); break; @@ -924,6 +931,12 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'tester_deleteResourceObject': delete resourceObjectsInTest[message.objectId]; break; + case 'tester_updateResourceRecordingStatus': + resourceObjectsInTest[message.objectId].currentlyRecordingResources = message.status; + if (message.status) { + resourceObjectsInTest[message.objectId].resourceAccessEventText = ""; + } + break; case 'header_marketplaceImageClicked': case 'purchases_backClicked': openMarketplace(message.referrerURL); @@ -1076,7 +1089,9 @@ function pushResourceObjectsInTest() { // 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: maxResourceObjectId + 1 }); + //ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: maxResourceObjectId + 1 }); + // Since, for now, we only support 1 object in test, always send id: 0 + ui.tablet.sendToQml({ method: "nextObjectIdInTest", id: 0 }); } // Function Name: onTabletScreenChanged() @@ -1165,7 +1180,6 @@ var onTabletScreenChanged = function onTabletScreenChanged(type, url) { // variable amount of time to come up, in practice less than // 750ms. Script.setTimeout(pushResourceObjectsInTest, 750); - Script.setTimeout(pushResourceRequestEvents, 750); } console.debug(ui.buttonName + " app reports: Tablet screen changed.\nNew screen type: " + type + From de93bbb08bcc0f61722cdb10d216fd38eb98ce01 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 15:43:28 -0700 Subject: [PATCH 11/16] Only show unique resources --- .../marketplaceItemTester/ItemUnderTest.qml | 5 ++-- scripts/system/marketplaces/marketplaces.js | 24 +++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index c5aaf20a8f..fcb4eaae43 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -314,10 +314,11 @@ Rectangle { readOnly: true color: hifi.colors.white text: { - if (root.detailsExpanded) { + var numUniqueResources = (model.resourceAccessEventText.split("\n").length - 1); + if (root.detailsExpanded && numUniqueResources > 0) { return model.resourceAccessEventText } else { - return (model.resourceAccessEventText.split("\n").length - 1).toString() + " resources loaded..." + return numUniqueResources.toString() + " unique resource" + (numUniqueResources === 1 ? "" : "s") + " loaded - expand for details" } } font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index d6056f83a6..487920b764 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -54,14 +54,24 @@ var resourceRequestEvents = []; function signalResourceRequestEvent(data) { // Once we can tie resource request events to specific resources, // we will have to update the "0" in here. - resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + - data.url.toString().replace("__NONE__,", "") + "\n"; + if (!resourceObjectsInTest[0].resourceUrls) { + resourceObjectsInTest[0].resourceUrls = []; + } - ui.tablet.sendToQml({ - method: "resourceRequestEvent", - data: data, - resourceAccessEventText: resourceObjectsInTest[0].resourceAccessEventText - }); + var resourceUrl = data.url.toString().replace("__NONE__,", ""); + + if (resourceObjectsInTest[0].resourceUrls.indexOf(resourceUrl) === -1) { + resourceObjectsInTest[0].resourceUrls.push(resourceUrl); + + resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + + resourceUrl + "\n"; + + ui.tablet.sendToQml({ + method: "resourceRequestEvent", + data: data, + resourceAccessEventText: resourceObjectsInTest[0].resourceAccessEventText + }); + } } function onResourceRequestEvent(data) { From f9cc4f5a696c15790ce4c04f4ab89b65cd738fe4 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 16:01:49 -0700 Subject: [PATCH 12/16] Count unique source and url pairs --- .../commerce/marketplaceItemTester/ItemUnderTest.qml | 2 +- scripts/system/marketplaces/marketplaces.js | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml index fcb4eaae43..27277a28f4 100644 --- a/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml +++ b/interface/resources/qml/hifi/commerce/marketplaceItemTester/ItemUnderTest.qml @@ -318,7 +318,7 @@ Rectangle { if (root.detailsExpanded && numUniqueResources > 0) { return model.resourceAccessEventText } else { - return numUniqueResources.toString() + " unique resource" + (numUniqueResources === 1 ? "" : "s") + " loaded - expand for details" + return numUniqueResources.toString() + " unique source/resource url pair" + (numUniqueResources === 1 ? "" : "s") + " recorded" } } font: Qt.font({ family: "Courier", pointSize: 8, weight: Font.Normal }) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 487920b764..59279bada7 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -54,17 +54,17 @@ var resourceRequestEvents = []; function signalResourceRequestEvent(data) { // Once we can tie resource request events to specific resources, // we will have to update the "0" in here. - if (!resourceObjectsInTest[0].resourceUrls) { - resourceObjectsInTest[0].resourceUrls = []; + if (!resourceObjectsInTest[0].resourceDataArray) { + resourceObjectsInTest[0].resourceDataArray = []; } - var resourceUrl = data.url.toString().replace("__NONE__,", ""); + var resourceData = "from: " + data.extra + ": " + data.url.toString().replace("__NONE__,", ""); - if (resourceObjectsInTest[0].resourceUrls.indexOf(resourceUrl) === -1) { - resourceObjectsInTest[0].resourceUrls.push(resourceUrl); + if (resourceObjectsInTest[0].resourceDataArray.indexOf(resourceData) === -1) { + resourceObjectsInTest[0].resourceDataArray.push(resourceData); resourceObjectsInTest[0].resourceAccessEventText += "[" + data.date.toISOString() + "] " + - resourceUrl + "\n"; + resourceData + "\n"; ui.tablet.sendToQml({ method: "resourceRequestEvent", From 1023e6d2b9ceec7052b1b39e29b3047d7cbe4210 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 16:03:05 -0700 Subject: [PATCH 13/16] Fix ClipboardScriptingInterface: --- .../src/scripting/ClipboardScriptingInterface.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index c2d2b69883..c14f4ea895 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -46,11 +46,17 @@ bool ClipboardScriptingInterface::exportEntities(const QString& filename, float return retVal; } -bool ClipboardScriptingInterface::importEntities(const QString& filename) { +bool ClipboardScriptingInterface::importEntities( + const QString& filename, + const bool isObservable, + const qint64 callerId +) { bool retVal; BLOCKING_INVOKE_METHOD(qApp, "importEntities", Q_RETURN_ARG(bool, retVal), - Q_ARG(const QString&, filename)); + Q_ARG(const QString&, filename), + Q_ARG(const bool, isObservable), + Q_ARG(const qint64, callerId)); return retVal; } From f5a5c0dad3511fe73782dfd92ea7951fe83797ab Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 19 Oct 2018 16:48:23 -0700 Subject: [PATCH 14/16] Attempt to fix wearable bug? --- scripts/system/marketplaces/marketplaces.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 59279bada7..004f3fcbb8 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -54,10 +54,6 @@ var resourceRequestEvents = []; function signalResourceRequestEvent(data) { // Once we can tie resource request events to specific resources, // we will have to update the "0" in here. - if (!resourceObjectsInTest[0].resourceDataArray) { - resourceObjectsInTest[0].resourceDataArray = []; - } - var resourceData = "from: " + data.extra + ": " + data.url.toString().replace("__NONE__,", ""); if (resourceObjectsInTest[0].resourceDataArray.indexOf(resourceData) === -1) { @@ -933,6 +929,7 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { resourceObject.currentlyRecordingResources = false; resourceObject.resourceAccessEventText = ""; resourceObjectsInTest[resourceObject.resourceObjectId] = resourceObject; + resourceObjectsInTest[resourceObject.resourceObjectId].resourceDataArray = []; signalNewResourceObjectInTest(resourceObject); break; case 'tester_updateResourceObjectAssetType': @@ -944,6 +941,7 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'tester_updateResourceRecordingStatus': resourceObjectsInTest[message.objectId].currentlyRecordingResources = message.status; if (message.status) { + resourceObjectsInTest[message.objectId].resourceDataArray = []; resourceObjectsInTest[message.objectId].resourceAccessEventText = ""; } break; From a1bb68517626d4a31823a9d2782b0ed77d890672 Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Mon, 22 Oct 2018 11:04:31 -0700 Subject: [PATCH 15/16] Remove cruft --- libraries/octree/src/Octree.cpp | 2 +- scripts/system/marketplaces/marketplaces.js | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index efddf3c14f..06db92bcf7 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -689,7 +689,7 @@ bool Octree::readFromFile(const char* fileName) { qCDebug(octree) << "Loading file" << qFileName << "..."; - bool success = readFromStream(fileLength, fileInputStream, ""); + bool success = readFromStream(fileLength, fileInputStream); file.close(); diff --git a/scripts/system/marketplaces/marketplaces.js b/scripts/system/marketplaces/marketplaces.js index 004f3fcbb8..3085145176 100644 --- a/scripts/system/marketplaces/marketplaces.js +++ b/scripts/system/marketplaces/marketplaces.js @@ -564,7 +564,6 @@ function defaultFor(arg, val) { function rezEntity(itemHref, itemType, marketplaceItemTesterId) { var isWearable = itemType === "wearable"; - print("!!!!! Clipboard.importEntities " + marketplaceItemTesterId); var success = Clipboard.importEntities(itemHref, true, marketplaceItemTesterId); var wearableLocalPosition = null; var wearableLocalRotation = null; @@ -920,7 +919,6 @@ var onQmlMessageReceived = function onQmlMessageReceived(message) { case 'checkout_rezClicked': case 'purchases_rezClicked': case 'tester_rezClicked': - print("!!!!! marketplaces tester_rezClicked"); rezEntity(message.itemHref, message.itemType, message.itemId); break; case 'tester_newResourceObject': From a2a6acd45c2d7b5d7a082e84c567ba0ada31802d Mon Sep 17 00:00:00 2001 From: Kerry Ivan Kurian Date: Mon, 22 Oct 2018 14:04:59 -0700 Subject: [PATCH 16/16] Improve code formatting --- libraries/networking/src/ResourceCache.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index f2fbe41804..6f7cad8a04 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -349,7 +349,8 @@ QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& resource = createResource( url, fallback.isValid() ? getResource(fallback, QUrl()) : QSharedPointer(), - extra); resource->setSelf(resource); + extra); + resource->setSelf(resource); resource->setCache(this); resource->moveToThread(qApp->thread()); connect(resource.data(), &Resource::updateSize, this, &ResourceCache::updateTotalSize);