Add start of marketplace uploading and project file list

This commit is contained in:
Ryan Huffman 2018-12-13 13:22:52 -08:00
parent 96673bb5b9
commit 78c4c2599e
12 changed files with 388 additions and 89 deletions

View file

@ -23,14 +23,13 @@ Windows.ScrollingWindow {
//HifiConstants { id: hifi } //HifiConstants { id: hifi }
Rectangle { Rectangle {
anchors.left: parent.left anchors.fill: parent
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
AvatarProject { AvatarProject {
id: avatarProject id: avatarProject
colorScheme: root.colorScheme colorScheme: root.colorScheme
visible: false visible: false
anchors.fill: parent
} }
Rectangle { Rectangle {

View file

@ -13,26 +13,23 @@ Rectangle {
property int colorScheme; property int colorScheme;
color: "blue"
visible: true visible: true
anchors.left: parent.left anchors.fill: parent
anchors.right: parent.right
anchors.top: parent.top
anchors.bottom: parent.bottom
RalewaySemiBold { RalewaySemiBold {
id: avatarProjectLabel id: avatarProjectLabel
size: 24; size: 24;
anchors.left: parent.left width: parent.width
anchors.top: parent.top
anchors.topMargin: 25 anchors.topMargin: 25
anchors.bottomMargin: 25 anchors.bottomMargin: 25
text: 'Avatar Project' text: 'Avatar Project'
} }
HifiControls.Button { HifiControls.Button {
id: openFolderButton id: openFolderButton
anchors.left: parent.left width: parent.width
anchors.right: parent.right
anchors.top: avatarProjectLabel.bottom anchors.top: avatarProjectLabel.bottom
text: qsTr("Open Project Folder") text: qsTr("Open Project Folder")
colorScheme: root.colorScheme colorScheme: root.colorScheme
@ -43,15 +40,32 @@ Rectangle {
} }
HifiControls.Button { HifiControls.Button {
id: uploadButton id: uploadButton
anchors.left: parent.left width: parent.width
anchors.right: parent.right
anchors.top: openFolderButton.bottom anchors.top: openFolderButton.bottom
text: qsTr("Upload") text: qsTr("Upload")
color: hifi.buttons.blue color: hifi.buttons.blue
colorScheme: root.colorScheme colorScheme: root.colorScheme
height: 30 height: 30
onClicked: function() { 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: '<b>File:</b> ' + modelData }
} }
} }
} }

View file

@ -15,6 +15,18 @@
#include <QUrl> #include <QUrl>
#include <OffscreenUi.h> #include <OffscreenUi.h>
#include "ModelSelector.h"
#include <avatar/MarketplaceItemUploader.h>
#include <thread>
#include <mutex>
std::once_flag setupQMLTypesFlag;
AvatarPackager::AvatarPackager() {
std::call_once(setupQMLTypesFlag, []() {
qmlRegisterType<FST>();
});
}
bool AvatarPackager::open() { bool AvatarPackager::open() {
static const QUrl url{ "hifi/AvatarPackager.qml" }; static const QUrl url{ "hifi/AvatarPackager.qml" };
@ -32,5 +44,11 @@ QObject* AvatarPackager::openAvatarProject(QString avatarProjectFSTPath) {
//_currentAvatarProject = nullptr; //_currentAvatarProject = nullptr;
} }
_currentAvatarProject = AvatarProject::openAvatarProject(avatarProjectFSTPath); _currentAvatarProject = AvatarProject::openAvatarProject(avatarProjectFSTPath);
emit avatarProjectChanged();
return _currentAvatarProject; return _currentAvatarProject;
} }
QObject* AvatarPackager::uploadItem() {
std::vector<QString> filePaths;
return new MarketplaceItemUploader(QUuid(), filePaths);
}

View file

@ -21,16 +21,23 @@
class AvatarPackager : public QObject, public Dependency { class AvatarPackager : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
Q_PROPERTY(QObject* currentAvatarProject READ getAvatarProject) Q_PROPERTY(QObject* currentAvatarProject READ getAvatarProject NOTIFY avatarProjectChanged)
public: public:
AvatarPackager();
bool open(); bool open();
Q_INVOKABLE QObject* openAvatarProject(QString avatarProjectFSTPath); Q_INVOKABLE QObject* openAvatarProject(QString avatarProjectFSTPath);
signals:
void avatarProjectChanged();
private: private:
Q_INVOKABLE AvatarProject* getAvatarProject() const { return _currentAvatarProject; }; 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

View file

@ -11,17 +11,56 @@
#include "AvatarProject.h" #include "AvatarProject.h"
AvatarProject* AvatarProject::openAvatarProject(QString path) { #include <FSTReader.h>
#include <QFile>
#include <QFileInfo>
#include <QUrl>
#include <QDebug>
AvatarProject* AvatarProject::openAvatarProject(const QString& path) {
const auto pathToLower = path.toLower(); const auto pathToLower = path.toLower();
if (pathToLower.endsWith(".fst")) { if (pathToLower.endsWith(".fst")) {
// TODO: do we open FSTs from any path? QFile file{ path };
return new AvatarProject(path); if (!file.open(QIODevice::ReadOnly)) {
return nullptr;
}
return new AvatarProject(path, file.readAll());
} }
if (pathToLower.endsWith(".fbx")) { if (pathToLower.endsWith(".fbx")) {
// TODO: Create FST here: // TODO: Create FST here:
} }
return nullptr; 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);
}

View file

@ -13,12 +13,21 @@
#ifndef hifi_AvatarProject_h #ifndef hifi_AvatarProject_h
#define hifi_AvatarProject_h #define hifi_AvatarProject_h
#include "FST.h"
#include <QDir>
#include <QObject> #include <QObject>
#include <QDir> #include <QDir>
#include <QFileInfo> #include <QFileInfo>
#include <QVariantHash>
#include <QUuid>
class AvatarProject : public QObject { class AvatarProject : public QObject {
Q_OBJECT Q_OBJECT
Q_PROPERTY(FST* fst READ getFST)
Q_PROPERTY(QStringList projectFiles MEMBER _projectFiles)
Q_PROPERTY(QString projectFolderPath READ getProjectPath) Q_PROPERTY(QString projectFolderPath READ getProjectPath)
Q_PROPERTY(QString projectFSTPath READ getFSTPath) Q_PROPERTY(QString projectFSTPath READ getFSTPath)
Q_PROPERTY(QString projectFBXPath READ getFBXPath) Q_PROPERTY(QString projectFBXPath READ getFBXPath)
@ -37,17 +46,10 @@ public:
/** /**
* returns the AvatarProject or a nullptr on failure. * returns the AvatarProject or a nullptr on failure.
*/ */
static AvatarProject* openAvatarProject(QString path); static AvatarProject* openAvatarProject(const QString& path);
private: private:
AvatarProject(QString fstPath) : AvatarProject(const QString& fstPath, const QByteArray& data);
_fstPath(fstPath) {
auto fileInfo = QFileInfo(_fstPath);
_projectPath = fileInfo.absoluteDir().absolutePath();
_fstPath = _projectPath + "TemporaryFBX.fbx";
}
~AvatarProject() { ~AvatarProject() {
// TODO: cleanup FST / AvatarProjectUploader etc. // TODO: cleanup FST / AvatarProjectUploader etc.
@ -55,13 +57,19 @@ private:
Q_INVOKABLE QString getProjectPath() const { return _projectPath; } Q_INVOKABLE QString getProjectPath() const { return _projectPath; }
Q_INVOKABLE QString getFSTPath() const { return _fstPath; } 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 _projectPath;
QString _fstPath; QString _fstPath;
// TODO: handle this in the FST Class
QString _fbxPath;
}; };
#endif // hifi_AvatarProject_h #endif // hifi_AvatarProject_h

View file

@ -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 <AccountManager.h>
#include <DependencyManager.h>
#include <qtimer.h>
MarketplaceItemUploader::MarketplaceItemUploader(QUuid marketplaceID, std::vector<QString> filePaths)
: _filePaths(filePaths), _marketplaceID(marketplaceID) {
}
void MarketplaceItemUploader::send() {
auto accountManager = DependencyManager::get<AccountManager>();
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();
}

View file

@ -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 <QObject>
#include <QUuid>
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<QString> 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<QString> _filePaths;
};
#endif // hifi_MarketplaceItemUploader_h

45
libraries/fbx/src/FST.cpp Normal file
View file

@ -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 <QDir>
#include <QFileInfo>
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);
}

49
libraries/fbx/src/FST.h Normal file
View file

@ -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 <QVariantHash>
#include <QUuid>
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

View file

@ -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, void AccountManager::sendRequest(const QString& path,
AccountManagerAuth::Type authType, AccountManagerAuth::Type authType,
QNetworkAccessManager::Operation operation, QNetworkAccessManager::Operation operation,
@ -231,46 +269,10 @@ void AccountManager::sendRequest(const QString& path,
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest; QNetworkRequest networkRequest = createRequest(path, authType);
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);
if (VERBOSE_HTTP_REQUEST_DEBUGGING) { 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()) { if (!dataByteArray.isEmpty()) {
qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray); qCDebug(networking) << "The POST/PUT body -" << QString(dataByteArray);

View file

@ -28,7 +28,8 @@
class JSONCallbackParameters { class JSONCallbackParameters {
public: public:
JSONCallbackParameters(QObject* callbackReceiver = nullptr, const QString& jsonCallbackMethod = QString(), JSONCallbackParameters(QObject* callbackReceiver = nullptr,
const QString& jsonCallbackMethod = QString(),
const QString& errorCallbackMethod = QString()); const QString& errorCallbackMethod = QString());
bool isEmpty() const { return !callbackReceiver; } bool isEmpty() const { return !callbackReceiver; }
@ -39,11 +40,12 @@ public:
}; };
namespace AccountManagerAuth { namespace AccountManagerAuth {
enum Type { enum Type
None, {
Required, None,
Optional Required,
}; Optional
};
} }
Q_DECLARE_METATYPE(AccountManagerAuth::Type); Q_DECLARE_METATYPE(AccountManagerAuth::Type);
@ -60,6 +62,7 @@ class AccountManager : public QObject, public Dependency {
public: public:
AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER); AccountManager(UserAgentGetter userAgentGetter = DEFAULT_USER_AGENT_GETTER);
QNetworkRequest createRequest(QString path, AccountManagerAuth::Type authType);
Q_INVOKABLE void sendRequest(const QString& path, Q_INVOKABLE void sendRequest(const QString& path,
AccountManagerAuth::Type authType, AccountManagerAuth::Type authType,
QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation, QNetworkAccessManager::Operation operation = QNetworkAccessManager::GetOperation,
@ -84,7 +87,7 @@ public:
void requestProfile(); void requestProfile();
DataServerAccountInfo& getAccountInfo() { return _accountInfo; } DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
void setAccountInfo(const DataServerAccountInfo &newAccountInfo); void setAccountInfo(const DataServerAccountInfo& newAccountInfo);
static QJsonObject dataObjectFromResponse(QNetworkReply* requestReply); static QJsonObject dataObjectFromResponse(QNetworkReply* requestReply);
@ -104,7 +107,10 @@ public:
public slots: public slots:
void requestAccessToken(const QString& login, const QString& password); void requestAccessToken(const QString& login, const QString& password);
void requestAccessTokenWithSteam(QByteArray authSessionTicket); 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 refreshAccessToken();
void requestAccessTokenFinished(); void requestAccessTokenFinished();
@ -148,15 +154,15 @@ private:
QUrl _authURL; QUrl _authURL;
DataServerAccountInfo _accountInfo; DataServerAccountInfo _accountInfo;
bool _isWaitingForTokenRefresh { false }; bool _isWaitingForTokenRefresh{ false };
bool _isAgent { false }; bool _isAgent{ false };
bool _isWaitingForKeypairResponse { false }; bool _isWaitingForKeypairResponse{ false };
QByteArray _pendingPrivateKey; QByteArray _pendingPrivateKey;
QUuid _sessionID { QUuid::createUuid() }; QUuid _sessionID{ QUuid::createUuid() };
bool _limitedCommerce { false }; bool _limitedCommerce{ false };
}; };
#endif // hifi_AccountManager_h #endif // hifi_AccountManager_h