From 78c4c2599e007cfec2765b3fff4d1c8a99c73f93 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 13 Dec 2018 13:22:52 -0800 Subject: [PATCH] Add start of marketplace uploading and project file list --- .../resources/qml/hifi/AvatarPackager.qml | 7 +- .../qml/hifi/avatarPackager/AvatarProject.qml | 38 ++++++--- interface/src/avatar/AvatarPackager.cpp | 18 +++++ interface/src/avatar/AvatarPackager.h | 13 +++- interface/src/avatar/AvatarProject.cpp | 47 ++++++++++- interface/src/avatar/AvatarProject.h | 36 +++++---- .../src/avatar/MarketplaceItemUploader.cpp | 57 ++++++++++++++ .../src/avatar/MarketplaceItemUploader.h | 55 +++++++++++++ libraries/fbx/src/FST.cpp | 45 +++++++++++ libraries/fbx/src/FST.h | 49 ++++++++++++ libraries/networking/src/AccountManager.cpp | 78 ++++++++++--------- libraries/networking/src/AccountManager.h | 34 ++++---- 12 files changed, 388 insertions(+), 89 deletions(-) create mode 100644 interface/src/avatar/MarketplaceItemUploader.cpp create mode 100644 interface/src/avatar/MarketplaceItemUploader.h create mode 100644 libraries/fbx/src/FST.cpp create mode 100644 libraries/fbx/src/FST.h diff --git a/interface/resources/qml/hifi/AvatarPackager.qml b/interface/resources/qml/hifi/AvatarPackager.qml index 2044529427..434cd4128f 100644 --- a/interface/resources/qml/hifi/AvatarPackager.qml +++ b/interface/resources/qml/hifi/AvatarPackager.qml @@ -23,14 +23,13 @@ Windows.ScrollingWindow { //HifiConstants { id: hifi } Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom + anchors.fill: parent + AvatarProject { id: avatarProject colorScheme: root.colorScheme visible: false + anchors.fill: parent } Rectangle { diff --git a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml index 0a6ed6c459..c365d7436e 100644 --- a/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml +++ b/interface/resources/qml/hifi/avatarPackager/AvatarProject.qml @@ -13,26 +13,23 @@ Rectangle { property int colorScheme; + color: "blue" + visible: true - - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: parent.bottom + + anchors.fill: parent RalewaySemiBold { id: avatarProjectLabel size: 24; - anchors.left: parent.left - anchors.top: parent.top + width: parent.width anchors.topMargin: 25 anchors.bottomMargin: 25 text: 'Avatar Project' } HifiControls.Button { id: openFolderButton - anchors.left: parent.left - anchors.right: parent.right + width: parent.width anchors.top: avatarProjectLabel.bottom text: qsTr("Open Project Folder") colorScheme: root.colorScheme @@ -43,15 +40,32 @@ Rectangle { } HifiControls.Button { id: uploadButton - anchors.left: parent.left - anchors.right: parent.right + width: parent.width anchors.top: openFolderButton.bottom text: qsTr("Upload") color: hifi.buttons.blue colorScheme: root.colorScheme height: 30 onClicked: function() { - + } + } + Text { + id: modelText + anchors.top: uploadButton.bottom + height: 30 + text: parent.height + } + Rectangle { + color: 'white' + visible: AvatarPackagerCore.currentAvatarProject !== null + width: parent.width + anchors.top: modelText.bottom + height: 1000 + + ListView { + anchors.fill: parent + model: AvatarPackagerCore.currentAvatarProject === null ? [] : AvatarPackagerCore.currentAvatarProject.projectFiles + delegate: Text { text: 'File: ' + modelData } } } } diff --git a/interface/src/avatar/AvatarPackager.cpp b/interface/src/avatar/AvatarPackager.cpp index 1088c862d4..3fdf193087 100644 --- a/interface/src/avatar/AvatarPackager.cpp +++ b/interface/src/avatar/AvatarPackager.cpp @@ -15,6 +15,18 @@ #include #include +#include "ModelSelector.h" +#include + +#include +#include + +std::once_flag setupQMLTypesFlag; +AvatarPackager::AvatarPackager() { + std::call_once(setupQMLTypesFlag, []() { + qmlRegisterType(); + }); +} bool AvatarPackager::open() { static const QUrl url{ "hifi/AvatarPackager.qml" }; @@ -32,5 +44,11 @@ QObject* AvatarPackager::openAvatarProject(QString avatarProjectFSTPath) { //_currentAvatarProject = nullptr; } _currentAvatarProject = AvatarProject::openAvatarProject(avatarProjectFSTPath); + emit avatarProjectChanged(); return _currentAvatarProject; } + +QObject* AvatarPackager::uploadItem() { + std::vector filePaths; + return new MarketplaceItemUploader(QUuid(), filePaths); +} diff --git a/interface/src/avatar/AvatarPackager.h b/interface/src/avatar/AvatarPackager.h index a8ef6c6421..f002631f17 100644 --- a/interface/src/avatar/AvatarPackager.h +++ b/interface/src/avatar/AvatarPackager.h @@ -21,16 +21,23 @@ class AvatarPackager : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY - Q_PROPERTY(QObject* currentAvatarProject READ getAvatarProject) + Q_PROPERTY(QObject* currentAvatarProject READ getAvatarProject NOTIFY avatarProjectChanged) public: + AvatarPackager(); bool open(); Q_INVOKABLE QObject* openAvatarProject(QString avatarProjectFSTPath); +signals: + void avatarProjectChanged(); + private: Q_INVOKABLE AvatarProject* getAvatarProject() const { return _currentAvatarProject; }; - AvatarProject* _currentAvatarProject { nullptr }; + //Q_INVOKABLE QObject* openAvatarProject(); + Q_INVOKABLE QObject* uploadItem(); + + AvatarProject* _currentAvatarProject{ nullptr }; }; -#endif // hifi_AvatarPackager_h +#endif // hifi_AvatarPackager_h diff --git a/interface/src/avatar/AvatarProject.cpp b/interface/src/avatar/AvatarProject.cpp index a3b22ab2c2..32fe4febcd 100644 --- a/interface/src/avatar/AvatarProject.cpp +++ b/interface/src/avatar/AvatarProject.cpp @@ -11,17 +11,56 @@ #include "AvatarProject.h" -AvatarProject* AvatarProject::openAvatarProject(QString path) { +#include + +#include +#include +#include +#include + +AvatarProject* AvatarProject::openAvatarProject(const QString& path) { const auto pathToLower = path.toLower(); if (pathToLower.endsWith(".fst")) { - // TODO: do we open FSTs from any path? - return new AvatarProject(path); + QFile file{ path }; + if (!file.open(QIODevice::ReadOnly)) { + return nullptr; + } + return new AvatarProject(path, file.readAll()); } if (pathToLower.endsWith(".fbx")) { // TODO: Create FST here: - } return nullptr; } + +AvatarProject::AvatarProject(const QString& fstPath, const QByteArray& data) : + _fstPath(fstPath), _fst(fstPath, FSTReader::readMapping(data)) { + + _directory = QFileInfo(_fstPath).absoluteDir(); + + //_projectFiles = _directory.entryList(); + refreshProjectFiles(); + + auto fileInfo = QFileInfo(_fstPath); + _projectPath = fileInfo.absoluteDir().absolutePath(); +} + +void AvatarProject::appendDirectory(QString prefix, QDir dir) { + qDebug() << "Inside of " << prefix << dir.absolutePath(); + auto flags = QDir::Dirs | QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot | QDir::Hidden; + for (auto& entry : dir.entryInfoList({}, flags)) { + if (entry.isFile()) { + _projectFiles.append(prefix + "/" + entry.fileName()); + } else if (entry.isDir()) { + qDebug() << "Found dir " << entry.absoluteFilePath() << " in " << dir.absolutePath(); + appendDirectory(prefix + dir.dirName() + "/", entry.absoluteFilePath()); + } + } +} + +void AvatarProject::refreshProjectFiles() { + _projectFiles.clear(); + appendDirectory("", _directory); +} diff --git a/interface/src/avatar/AvatarProject.h b/interface/src/avatar/AvatarProject.h index 2114b147dd..6dc64cda6f 100644 --- a/interface/src/avatar/AvatarProject.h +++ b/interface/src/avatar/AvatarProject.h @@ -13,12 +13,21 @@ #ifndef hifi_AvatarProject_h #define hifi_AvatarProject_h +#include "FST.h" + +#include #include #include #include +#include +#include class AvatarProject : public QObject { Q_OBJECT + Q_PROPERTY(FST* fst READ getFST) + + Q_PROPERTY(QStringList projectFiles MEMBER _projectFiles) + Q_PROPERTY(QString projectFolderPath READ getProjectPath) Q_PROPERTY(QString projectFSTPath READ getFSTPath) Q_PROPERTY(QString projectFBXPath READ getFBXPath) @@ -37,17 +46,10 @@ public: /** * returns the AvatarProject or a nullptr on failure. */ - static AvatarProject* openAvatarProject(QString path); + static AvatarProject* openAvatarProject(const QString& path); private: - AvatarProject(QString fstPath) : - _fstPath(fstPath) { - auto fileInfo = QFileInfo(_fstPath); - _projectPath = fileInfo.absoluteDir().absolutePath(); - - _fstPath = _projectPath + "TemporaryFBX.fbx"; - - } + AvatarProject(const QString& fstPath, const QByteArray& data); ~AvatarProject() { // TODO: cleanup FST / AvatarProjectUploader etc. @@ -55,13 +57,19 @@ private: Q_INVOKABLE QString getProjectPath() const { return _projectPath; } Q_INVOKABLE QString getFSTPath() const { return _fstPath; } - Q_INVOKABLE QString getFBXPath() const { return _fbxPath; } + Q_INVOKABLE QString getFBXPath() const { return _fst.getModelPath(); } + FST* getFST() { return &_fst; } + + void refreshProjectFiles(); + void appendDirectory(QString prefix, QDir dir); + + FST _fst; + + QDir _directory; + QStringList _projectFiles{}; QString _projectPath; QString _fstPath; - // TODO: handle this in the FST Class - QString _fbxPath; - }; -#endif // hifi_AvatarProject_h +#endif // hifi_AvatarProject_h diff --git a/interface/src/avatar/MarketplaceItemUploader.cpp b/interface/src/avatar/MarketplaceItemUploader.cpp new file mode 100644 index 0000000000..c2671aadec --- /dev/null +++ b/interface/src/avatar/MarketplaceItemUploader.cpp @@ -0,0 +1,57 @@ +// +// MarketplaceItemUploader.cpp +// +// +// Created by Ryan Huffman on 12/10/2018 +// 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 "MarketplaceItemUploader.h" + +#include +#include + +#include + +MarketplaceItemUploader::MarketplaceItemUploader(QUuid marketplaceID, std::vector filePaths) + : _filePaths(filePaths), _marketplaceID(marketplaceID) { + +} + +void MarketplaceItemUploader::send() { + auto accountManager = DependencyManager::get(); + auto request = accountManager->createRequest("/marketplace/item", AccountManagerAuth::Required); + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QByteArray data; + + /* + auto reply = networkAccessManager.post(request, data); + + connect(reply, &QNetworkReply::uploadProgress, this, &MarketplaceItemUploader::uploadProgress); + + connect(reply, &QNetworkReply::finished, this, [this, reply]() { + auto error = reply->error(); + if (error == QNetworkReply::NoError) { + } else { + } + emit complete(); + }); + */ + + QTimer* timer = new QTimer(); + timer->setInterval(1000); + connect(timer, &QTimer::timeout, this, [this, timer]() { + if (progress <= 1.0f) { + progress += 0.1; + emit uploadProgress(progress * 100.0f, 100.0f); + } else { + emit complete(); + timer->stop(); + } + }); + timer->start(); +} diff --git a/interface/src/avatar/MarketplaceItemUploader.h b/interface/src/avatar/MarketplaceItemUploader.h new file mode 100644 index 0000000000..2a3071244e --- /dev/null +++ b/interface/src/avatar/MarketplaceItemUploader.h @@ -0,0 +1,55 @@ +// +// MarketplaceItemUploader.h +// +// +// Created by Ryan Huffman on 12/10/2018 +// 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 +// + +#pragma once +#ifndef hifi_MarketplaceItemUploader_h +#define hifi_MarketplaceItemUploader_h + +#include +#include + +class QNetworkReply; + +class MarketplaceItemUploader : public QObject { + Q_OBJECT +public: + enum class Error + { + None, + ItemNotUpdateable, + ItemDoesNotExist, + RequestTimedOut, + Unknown + }; + enum class State + { + Ready, + Sent + }; + + MarketplaceItemUploader(QUuid markertplaceID, std::vector filePaths); + + float progress{ 0.0f }; + + Q_INVOKABLE void send(); + +signals: + void uploadProgress(qint64 bytesSent, qint64 bytesTotal); + void complete(); + +private: + + QNetworkReply* _reply; + QUuid _marketplaceID; + std::vector _filePaths; +}; + +#endif // hifi_MarketplaceItemUploader_h diff --git a/libraries/fbx/src/FST.cpp b/libraries/fbx/src/FST.cpp new file mode 100644 index 0000000000..6574b66e51 --- /dev/null +++ b/libraries/fbx/src/FST.cpp @@ -0,0 +1,45 @@ +// +// FST.cpp +// +// Created by Ryan Huffman on 12/11/15. +// 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 "FST.h" + +#include +#include + +FST::FST(QString fstPath, QVariantHash data) : _fstPath(fstPath) { + if (data.contains("name")) { + _name = data["name"].toString(); + data.remove("name"); + } + + if (data.contains("filename")) { + _modelPath = data["filename"].toString(); + data.remove("filename"); + } + + _other = data; +} + +QString FST::absoluteModelPath() const { + QFileInfo fileInfo{ _fstPath }; + QDir dir{ fileInfo.absoluteDir() }; + return dir.absoluteFilePath(_modelPath); +} + +void FST::setName(const QString& name) { + _name = name; + emit nameChanged(name); +} + +void FST::setModelPath(const QString& modelPath) { + _modelPath = modelPath; + emit modelPathChanged(modelPath); +} \ No newline at end of file diff --git a/libraries/fbx/src/FST.h b/libraries/fbx/src/FST.h new file mode 100644 index 0000000000..e8c67c6c6b --- /dev/null +++ b/libraries/fbx/src/FST.h @@ -0,0 +1,49 @@ +// +// FST.h +// +// Created by Ryan Huffman on 12/11/15. +// 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_FST_h +#define hifi_FST_h + +#include +#include + +class FST : public QObject { + Q_OBJECT + Q_PROPERTY(QString name READ getName WRITE setName NOTIFY nameChanged) + Q_PROPERTY(QString modelPath READ getModelPath WRITE setModelPath NOTIFY modelPathChanged) + Q_PROPERTY(QUuid marketplaceID READ getMarketplaceID) +public: + FST(QString fstPath, QVariantHash data); + + QString absoluteModelPath() const; + + QString getName() const { return _name; } + void setName(const QString& name); + + QString getModelPath() const { return _modelPath; } + void setModelPath(const QString& modelPath); + + QUuid getMarketplaceID() const { return _marketplaceID; } + +signals: + void nameChanged(const QString& name); + void modelPathChanged(const QString& modelPath); + +private: + QString _fstPath; + + QString _name{}; + QString _modelPath{}; + QUuid _marketplaceID{}; + + QVariantHash _other{}; +}; + +#endif // hifi_FST_h diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 5721ac9334..989661cb81 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -208,6 +208,44 @@ void AccountManager::setSessionID(const QUuid& sessionID) { } } +QNetworkRequest AccountManager::createRequest(QString path, AccountManagerAuth::Type authType) { + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); + + networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, + uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); + + QUrl requestURL = _authURL; + + if (requestURL.isEmpty()) { // Assignment client doesn't set _authURL. + requestURL = getMetaverseServerURL(); + } + + if (path.startsWith("/")) { + requestURL.setPath(path); + } else { + requestURL.setPath("/" + path); + } + + if (authType != AccountManagerAuth::None ) { + if (hasValidAccessToken()) { + networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, + _accountInfo.getAccessToken().authorizationHeaderValue()); + } else { + if (authType == AccountManagerAuth::Required) { + qCDebug(networking) << "No valid access token present. Bailing on invoked request to" + << path << "that requires authentication"; + return QNetworkRequest(); + } + } + } + + networkRequest.setUrl(requestURL); + + return networkRequest; +} + void AccountManager::sendRequest(const QString& path, AccountManagerAuth::Type authType, QNetworkAccessManager::Operation operation, @@ -231,46 +269,10 @@ void AccountManager::sendRequest(const QString& path, QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::UserAgentHeader, _userAgentGetter()); - - networkRequest.setRawHeader(METAVERSE_SESSION_ID_HEADER, - uuidStringWithoutCurlyBraces(_sessionID).toLocal8Bit()); - - QUrl requestURL = _authURL; - - if (requestURL.isEmpty()) { // Assignment client doesn't set _authURL. - requestURL = getMetaverseServerURL(); - } - - if (path.startsWith("/")) { - requestURL.setPath(path); - } else { - requestURL.setPath("/" + path); - } - - if (!query.isEmpty()) { - requestURL.setQuery(query); - } - - if (authType != AccountManagerAuth::None ) { - if (hasValidAccessToken()) { - networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, - _accountInfo.getAccessToken().authorizationHeaderValue()); - } else { - if (authType == AccountManagerAuth::Required) { - qCDebug(networking) << "No valid access token present. Bailing on invoked request to" - << path << "that requires authentication"; - return; - } - } - } - - networkRequest.setUrl(requestURL); + QNetworkRequest networkRequest = createRequest(path, authType); if (VERBOSE_HTTP_REQUEST_DEBUGGING) { - qCDebug(networking) << "Making a request to" << qPrintable(requestURL.toString()); + qCDebug(networking) << "Making a request to" << qPrintable(networkRequest.url().toString()); if (!dataByteArray.isEmpty()) { qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray); diff --git a/libraries/networking/src/AccountManager.h b/libraries/networking/src/AccountManager.h index d5406707e7..77f20472fa 100644 --- a/libraries/networking/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -28,7 +28,8 @@ class JSONCallbackParameters { public: - JSONCallbackParameters(QObject* callbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), + JSONCallbackParameters(QObject* callbackReceiver = nullptr, + const QString& jsonCallbackMethod = QString(), const QString& errorCallbackMethod = QString()); bool isEmpty() const { return !callbackReceiver; } @@ -39,11 +40,12 @@ public: }; namespace AccountManagerAuth { - enum Type { - None, - Required, - Optional - }; +enum Type +{ + None, + Required, + Optional +}; } Q_DECLARE_METATYPE(AccountManagerAuth::Type); @@ -60,6 +62,7 @@ class AccountManager : public QObject, public Dependency { public: AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER); + QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType); Q_INVOKABLE void sendRequest(const QString& path, AccountManagerAuth::Type authType, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, @@ -84,7 +87,7 @@ public: void requestProfile(); DataServerAccountInfo& getAccountInfo() { return _accountInfo; } - void setAccountInfo(const DataServerAccountInfo &newAccountInfo); + void setAccountInfo(const DataServerAccountInfo& newAccountInfo); static QJsonObject dataObjectFromResponse(QNetworkReply* requestReply); @@ -104,7 +107,10 @@ public: public slots: void requestAccessToken(const QString& login, const QString& password); void requestAccessTokenWithSteam(QByteArray authSessionTicket); - void requestAccessTokenWithAuthCode(const QString& authCode, const QString& clientId, const QString& clientSecret, const QString& redirectUri); + void requestAccessTokenWithAuthCode(const QString& authCode, + const QString& clientId, + const QString& clientSecret, + const QString& redirectUri); void refreshAccessToken(); void requestAccessTokenFinished(); @@ -148,15 +154,15 @@ private: QUrl _authURL; DataServerAccountInfo _accountInfo; - bool _isWaitingForTokenRefresh { false }; - bool _isAgent { false }; + bool _isWaitingForTokenRefresh{ false }; + bool _isAgent{ false }; - bool _isWaitingForKeypairResponse { false }; + bool _isWaitingForKeypairResponse{ false }; QByteArray _pendingPrivateKey; - QUuid _sessionID { QUuid::createUuid() }; + QUuid _sessionID{ QUuid::createUuid() }; - bool _limitedCommerce { false }; + bool _limitedCommerce{ false }; }; -#endif // hifi_AccountManager_h +#endif // hifi_AccountManager_h