mirror of
https://github.com/overte-org/overte.git
synced 2025-04-21 06:44:06 +02:00
Merge pull request #20 from birarda/asset-upload
fix some bugs, add an asset-uploader with copiable URL
This commit is contained in:
commit
db97996319
31 changed files with 519 additions and 69 deletions
|
@ -148,12 +148,32 @@ void AssetServer::handleAssetGet(QSharedPointer<NLPacket> packet, SharedNodePoin
|
|||
}
|
||||
|
||||
void AssetServer::handleAssetUpload(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
|
||||
|
||||
auto data = packetList->getMessage();
|
||||
QBuffer buffer { &data };
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
||||
MessageID messageID;
|
||||
buffer.read(reinterpret_cast<char*>(&messageID), sizeof(messageID));
|
||||
|
||||
if (!senderNode->getCanRez()) {
|
||||
// this is a node the domain told us is not allowed to rez entities
|
||||
// for now this also means it isn't allowed to add assets
|
||||
// so return a packet with error that indicates that
|
||||
|
||||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError));
|
||||
|
||||
// write the message ID and a permission denied error
|
||||
permissionErrorPacket->writePrimitive(messageID);
|
||||
permissionErrorPacket->writePrimitive(AssetServerError::PERMISSION_DENIED);
|
||||
|
||||
// send off the packet
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode);
|
||||
|
||||
// return so we're not attempting to handle upload
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t extensionLength;
|
||||
buffer.read(reinterpret_cast<char*>(&extensionLength), sizeof(extensionLength));
|
||||
|
|
|
@ -166,6 +166,21 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "asset_server",
|
||||
"label": "Asset Server",
|
||||
"assignment-types": [3],
|
||||
"settings": [
|
||||
{
|
||||
"name": "enabled",
|
||||
"type": "checkbox",
|
||||
"label": "Enabled",
|
||||
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
|
||||
"default": false,
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "audio_env",
|
||||
"label": "Audio Environment",
|
||||
|
|
|
@ -48,7 +48,8 @@ QUuid DomainGatekeeper::assignmentUUIDForPendingAssignment(const QUuid& tempUUID
|
|||
}
|
||||
|
||||
const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer
|
||||
<< NodeType::AvatarMixer << NodeType::EntityServer;
|
||||
<< NodeType::AvatarMixer << NodeType::EntityServer
|
||||
<< NodeType::AssetServer;
|
||||
|
||||
void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<NLPacket> packet) {
|
||||
if (packet->getPayloadSize() == 0) {
|
||||
|
@ -65,6 +66,16 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<NLPacket> pack
|
|||
return;
|
||||
}
|
||||
|
||||
static const NodeSet VALID_NODE_TYPES {
|
||||
NodeType::AudioMixer, NodeType::AvatarMixer, NodeType::AssetServer, NodeType::EntityServer, NodeType::Agent
|
||||
};
|
||||
|
||||
if (!VALID_NODE_TYPES.contains(nodeConnection.nodeType)) {
|
||||
qDebug() << "Received an invalid node type with connect request. Will not allow connection from"
|
||||
<< nodeConnection.senderSockAddr;
|
||||
return;
|
||||
}
|
||||
|
||||
// check if this connect request matches an assignment in the queue
|
||||
auto pendingAssignment = _pendingAssignedNodes.find(nodeConnection.connectUUID);
|
||||
|
||||
|
|
|
@ -285,14 +285,15 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
|||
// register as the packet receiver for the types we want
|
||||
PacketReceiver& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket");
|
||||
|
||||
|
||||
// NodeList won't be available to the settings manager when it is created, so call registerListener here
|
||||
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
|
||||
|
||||
// register the gatekeeper for the packets it needs to receive
|
||||
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
|
||||
|
@ -571,6 +572,18 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
|
|||
&& defaultedType != Assignment::UNUSED_0
|
||||
&& defaultedType != Assignment::UNUSED_1
|
||||
&& defaultedType != Assignment::AgentType) {
|
||||
|
||||
if (defaultedType == Assignment::AssetServerType) {
|
||||
// Make sure the asset-server is enabled before adding it here.
|
||||
// Initially we do not assign it by default so we can test it in HF domains first
|
||||
static const QString ASSET_SERVER_ENABLED_KEYPATH = "asset_server.enabled";
|
||||
|
||||
if (!_settingsManager.valueOrDefaultValueForKeyPath(ASSET_SERVER_ENABLED_KEYPATH).toBool()) {
|
||||
// skip to the next iteration if asset-server isn't enabled
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// type has not been set from a command line or config file config, use the default
|
||||
// by clearing whatever exists and writing a single default assignment with no payload
|
||||
Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType);
|
||||
|
|
|
@ -3763,6 +3763,9 @@ void Application::nodeAdded(SharedNodePointer node) {
|
|||
if (node->getType() == NodeType::AvatarMixer) {
|
||||
// new avatar mixer, send off our identity packet right away
|
||||
_myAvatar->sendIdentityPacket();
|
||||
} else if (node->getType() == NodeType::AssetServer) {
|
||||
// the addition of an asset-server always re-enables the upload to asset server menu option
|
||||
Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3810,6 +3813,10 @@ void Application::nodeKilled(SharedNodePointer node) {
|
|||
} else if (node->getType() == NodeType::AvatarMixer) {
|
||||
// our avatar mixer has gone away - clear the hash of avatars
|
||||
DependencyManager::get<AvatarManager>()->clearOtherAvatars();
|
||||
} else if (node->getType() == NodeType::AssetServer
|
||||
&& !DependencyManager::get<NodeList>()->soloNodeOfType(NodeType::AssetServer)) {
|
||||
// this was our last asset server - disable the menu option to upload an asset
|
||||
Menu::getInstance()->getActionForOption(MenuOption::UploadAsset)->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@
|
|||
#include <UserActivityLogger.h>
|
||||
#include <VrMenu.h>
|
||||
|
||||
#include <AssetClient.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "AccountManager.h"
|
||||
#include "audio/AudioScope.h"
|
||||
|
@ -34,13 +32,15 @@
|
|||
#include "devices/3DConnexionClient.h"
|
||||
#include "MainWindow.h"
|
||||
#include "scripting/MenuScriptingInterface.h"
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#include "SpeechRecognizer.h"
|
||||
#endif
|
||||
#include "ui/AssetUploadDialogFactory.h"
|
||||
#include "ui/DialogsManager.h"
|
||||
#include "ui/StandAloneJSConsole.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
||||
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
|
||||
#include "SpeechRecognizer.h"
|
||||
#endif
|
||||
|
||||
#include "Menu.h"
|
||||
|
||||
Menu* Menu::_instance = NULL;
|
||||
|
@ -90,36 +90,6 @@ Menu::Menu() {
|
|||
addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J,
|
||||
qApp, SLOT(toggleRunningScriptsWidget()));
|
||||
|
||||
// Asset uploading
|
||||
{
|
||||
auto action = new QAction("Upload File", fileMenu);
|
||||
fileMenu->addAction(action);
|
||||
action->setMenuRole(QAction::NoRole);
|
||||
_actionHash.insert("Upload File", action);
|
||||
|
||||
connect(action, &QAction::triggered, [this](bool checked) {
|
||||
qDebug() << "Clicked upload file";
|
||||
auto filename = QFileDialog::getOpenFileUrl(nullptr, "Select a file to upload");
|
||||
if (!filename.isEmpty()) {
|
||||
qDebug() << "Selected: " << filename;
|
||||
QFile file { filename.path() };
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QFileInfo fileInfo { filename.path() };
|
||||
auto extension = fileInfo.suffix();
|
||||
auto data = file.readAll();
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
assetClient->uploadAsset(data, extension, [this, extension](bool result, QString hash) mutable {
|
||||
if (result) {
|
||||
QMessageBox::information(this, "Upload Successful", "URL: apt:/" + hash + "." + extension);
|
||||
} else {
|
||||
QMessageBox::warning(this, "Upload Failed", "There was an error uploading the file.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
auto addressManager = DependencyManager::get<AddressManager>();
|
||||
|
||||
addDisabledActionAndSeparator(fileMenu, "History");
|
||||
|
@ -383,7 +353,21 @@ Menu::Menu() {
|
|||
addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools,
|
||||
0, // QML Qt::SHIFT | Qt::Key_L,
|
||||
dialogsManager.data(), SLOT(lodTools()));
|
||||
|
||||
|
||||
MenuWrapper* assetDeveloperMenu = developerMenu->addMenu("Assets");
|
||||
|
||||
auto& assetDialogFactory = AssetUploadDialogFactory::getInstance();
|
||||
assetDialogFactory.setParent(this);
|
||||
|
||||
QAction* assetUpload = addActionToQMenuAndActionHash(assetDeveloperMenu,
|
||||
MenuOption::UploadAsset,
|
||||
0,
|
||||
&assetDialogFactory,
|
||||
SLOT(showDialog()));
|
||||
|
||||
// disable the asset upload action by default - it gets enabled only if asset server becomes present
|
||||
assetUpload->setEnabled(false);
|
||||
|
||||
MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar");
|
||||
|
||||
MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking");
|
||||
|
|
|
@ -284,6 +284,7 @@ namespace MenuOption {
|
|||
const QString ToolWindow = "Tool Window";
|
||||
const QString TransmitterDrive = "Transmitter Drive";
|
||||
const QString TurnWithHead = "Turn using Head";
|
||||
const QString UploadAsset = "Upload File to Asset Server";
|
||||
const QString UseAudioForMouth = "Use Audio for Mouth";
|
||||
const QString UseCamera = "Use Camera";
|
||||
const QString VelocityFilter = "Velocity Filter";
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
|
||||
OctreePacketProcessor::OctreePacketProcessor() {
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
||||
packetReceiver.registerDirectListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData,
|
||||
PacketType::EntityErase, PacketType::OctreeStats },
|
||||
|
||||
packetReceiver.registerDirectListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,7 @@
|
|||
|
||||
#include <OffscreenQmlDialog.h>
|
||||
|
||||
class AddressBarDialog : public OffscreenQmlDialog
|
||||
{
|
||||
class AddressBarDialog : public OffscreenQmlDialog {
|
||||
Q_OBJECT
|
||||
HIFI_QML_DECL
|
||||
Q_PROPERTY(bool backEnabled READ backEnabled NOTIFY backEnabledChanged)
|
||||
|
|
159
interface/src/ui/AssetUploadDialogFactory.cpp
Normal file
159
interface/src/ui/AssetUploadDialogFactory.cpp
Normal file
|
@ -0,0 +1,159 @@
|
|||
//
|
||||
// AssetUploadDialogFactory.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-26.
|
||||
// Copyright 2015 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 "AssetUploadDialogFactory.h"
|
||||
|
||||
#include <AssetClient.h>
|
||||
#include <AssetUpload.h>
|
||||
#include <AssetUtils.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtWidgets/QDialogButtonBox>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
|
||||
AssetUploadDialogFactory& AssetUploadDialogFactory::getInstance() {
|
||||
static AssetUploadDialogFactory staticInstance;
|
||||
return staticInstance;
|
||||
}
|
||||
|
||||
AssetUploadDialogFactory::AssetUploadDialogFactory() {
|
||||
|
||||
}
|
||||
|
||||
static const QString PERMISSION_DENIED_ERROR = "You do not have permission to upload content to this asset-server.";
|
||||
|
||||
void AssetUploadDialogFactory::showDialog() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
if (nodeList->getThisNodeCanRez()) {
|
||||
auto filename = QFileDialog::getOpenFileUrl(_dialogParent, "Select a file to upload");
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
qDebug() << "Selected filename for upload to asset-server: " << filename;
|
||||
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto upload = assetClient->createUpload(filename.path());
|
||||
|
||||
if (upload) {
|
||||
// connect to the finished signal so we know when the AssetUpload is done
|
||||
QObject::connect(upload, &AssetUpload::finished, this, &AssetUploadDialogFactory::handleUploadFinished);
|
||||
|
||||
// start the upload now
|
||||
upload->start();
|
||||
} else {
|
||||
// show a QMessageBox to say that there is no local asset server
|
||||
QString messageBoxText = QString("Could not upload \n\n%1\n\nbecause you are currently not connected" \
|
||||
" to a local asset-server.").arg(QFileInfo(filename.toString()).fileName());
|
||||
|
||||
QMessageBox::information(_dialogParent, "Failed to Upload", messageBoxText);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// we don't have permission to upload to asset server in this domain - show the permission denied error
|
||||
showErrorDialog(QString(), PERMISSION_DENIED_ERROR);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void AssetUploadDialogFactory::handleUploadFinished(AssetUpload* upload, const QString& hash) {
|
||||
if (upload->getResult() == AssetUpload::Success) {
|
||||
// show message box for successful upload, with copiable text for ATP hash
|
||||
QDialog* hashCopyDialog = new QDialog(_dialogParent);
|
||||
|
||||
// delete the dialog on close
|
||||
hashCopyDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
// set the window title
|
||||
hashCopyDialog->setWindowTitle(tr("Successful Asset Upload"));
|
||||
|
||||
// setup a layout for the contents of the dialog
|
||||
QVBoxLayout* boxLayout = new QVBoxLayout;
|
||||
|
||||
// set the label text (this shows above the text box)
|
||||
QLabel* lineEditLabel = new QLabel;
|
||||
lineEditLabel->setText(QString("ATP URL for %1").arg(QFileInfo(upload->getFilename()).fileName()));
|
||||
|
||||
// setup the line edit to hold the copiable text
|
||||
QLineEdit* lineEdit = new QLineEdit;
|
||||
|
||||
QString atpURL = QString("%1://%2.%3").arg(ATP_SCHEME).arg(hash).arg(upload->getExtension());
|
||||
|
||||
// set the ATP URL as the text value so it's copiable
|
||||
lineEdit->insert(atpURL);
|
||||
|
||||
// figure out what size this line edit should be using font metrics
|
||||
QFontMetrics textMetrics { lineEdit->font() };
|
||||
|
||||
// set the fixed width on the line edit
|
||||
// pad it by 10 to cover the border and some extra space on the right side (for clicking)
|
||||
static const int LINE_EDIT_RIGHT_PADDING { 10 };
|
||||
|
||||
lineEdit->setFixedWidth(textMetrics.width(atpURL) + LINE_EDIT_RIGHT_PADDING );
|
||||
|
||||
// left align the ATP URL line edit
|
||||
lineEdit->home(true);
|
||||
|
||||
// add the label and line edit to the dialog
|
||||
boxLayout->addWidget(lineEditLabel);
|
||||
boxLayout->addWidget(lineEdit);
|
||||
|
||||
// setup an OK button to close the dialog
|
||||
QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
connect(buttonBox, &QDialogButtonBox::accepted, hashCopyDialog, &QDialog::close);
|
||||
boxLayout->addWidget(buttonBox);
|
||||
|
||||
// set the new layout on the dialog
|
||||
hashCopyDialog->setLayout(boxLayout);
|
||||
|
||||
// show the new dialog
|
||||
hashCopyDialog->show();
|
||||
} else {
|
||||
// figure out the right error message for the message box
|
||||
QString additionalError;
|
||||
|
||||
switch (upload->getResult()) {
|
||||
case AssetUpload::PermissionDenied:
|
||||
additionalError = PERMISSION_DENIED_ERROR;
|
||||
break;
|
||||
case AssetUpload::TooLarge:
|
||||
additionalError = "The uploaded content was too large and could not be stored in the asset-server.";
|
||||
break;
|
||||
case AssetUpload::ErrorLoadingFile:
|
||||
additionalError = "The file could not be opened. Please check your permissions and try again.";
|
||||
break;
|
||||
default:
|
||||
// not handled, do not show a message box
|
||||
return;
|
||||
}
|
||||
|
||||
// display a message box with the error
|
||||
showErrorDialog(QFileInfo(upload->getFilename()).fileName(), additionalError);
|
||||
}
|
||||
|
||||
upload->deleteLater();
|
||||
}
|
||||
|
||||
void AssetUploadDialogFactory::showErrorDialog(const QString& filename, const QString& additionalError) {
|
||||
QString errorMessage;
|
||||
|
||||
if (!filename.isEmpty()) {
|
||||
errorMessage += QString("Failed to upload %1.\n\n").arg(filename);
|
||||
}
|
||||
|
||||
errorMessage += additionalError;
|
||||
|
||||
QMessageBox::warning(_dialogParent, "Failed Upload", errorMessage);
|
||||
}
|
42
interface/src/ui/AssetUploadDialogFactory.h
Normal file
42
interface/src/ui/AssetUploadDialogFactory.h
Normal file
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// AssetUploadDialogFactory.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-26.
|
||||
// Copyright 2015 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_AssetUploadDialogFactory_h
|
||||
#define hifi_AssetUploadDialogFactory_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class AssetUpload;
|
||||
|
||||
class AssetUploadDialogFactory : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
AssetUploadDialogFactory(const AssetUploadDialogFactory& other) = delete;
|
||||
AssetUploadDialogFactory& operator=(const AssetUploadDialogFactory& rhs) = delete;
|
||||
|
||||
static AssetUploadDialogFactory& getInstance();
|
||||
|
||||
void setDialogParent(QWidget* dialogParent) { _dialogParent = dialogParent; }
|
||||
public slots:
|
||||
void showDialog();
|
||||
private slots:
|
||||
void handleUploadFinished(AssetUpload* upload, const QString& hash);
|
||||
private:
|
||||
AssetUploadDialogFactory();
|
||||
|
||||
void showErrorDialog(const QString& filename, const QString& additionalError);
|
||||
|
||||
QWidget* _dialogParent { nullptr };
|
||||
};
|
||||
|
||||
#endif // hifi_AssetUploadDialogFactory_h
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// DialogsManager.cpp
|
||||
//
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Clement on 1/18/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// DialogsManager.h
|
||||
//
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Clement on 1/18/15.
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -16,8 +16,7 @@
|
|||
|
||||
#include <OffscreenQmlDialog.h>
|
||||
|
||||
class LoginDialog : public OffscreenQmlDialog
|
||||
{
|
||||
class LoginDialog : public OffscreenQmlDialog {
|
||||
Q_OBJECT
|
||||
HIFI_QML_DECL
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QThread>
|
||||
|
||||
#include "AssetRequest.h"
|
||||
#include "AssetUpload.h"
|
||||
#include "NodeList.h"
|
||||
#include "PacketReceiver.h"
|
||||
#include "AssetUtils.h"
|
||||
|
@ -29,10 +30,10 @@ AssetClient::AssetClient() {
|
|||
packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply");
|
||||
}
|
||||
|
||||
AssetRequest* AssetClient::create(QString hash, QString extension) {
|
||||
AssetRequest* AssetClient::createRequest(QString hash, QString extension) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
AssetRequest* req;
|
||||
QMetaObject::invokeMethod(this, "create",
|
||||
QMetaObject::invokeMethod(this, "createRequest",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(AssetRequest*, req),
|
||||
Q_ARG(QString, hash),
|
||||
|
@ -49,15 +50,32 @@ AssetRequest* AssetClient::create(QString hash, QString extension) {
|
|||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
auto request = new AssetRequest(assetClient.data(), hash, extension);
|
||||
|
||||
return request;
|
||||
return new AssetRequest(this, hash, extension);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AssetUpload* AssetClient::createUpload(QString filename) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
AssetUpload* upload;
|
||||
QMetaObject::invokeMethod(this, "createUpload",
|
||||
Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(AssetUpload*, upload),
|
||||
Q_ARG(QString, filename));
|
||||
return upload;
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
return new AssetUpload(this, filename);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool AssetClient::getAsset(QString hash, QString extension, DataOffset start, DataOffset end, ReceivedAssetCallback callback) {
|
||||
if (hash.length() != HASH_HEX_LENGTH) {
|
||||
qDebug() << "Invalid hash size";
|
||||
|
@ -71,6 +89,9 @@ bool AssetClient::getAsset(QString hash, QString extension, DataOffset start, Da
|
|||
auto packet = NLPacket::create(PacketType::AssetGet);
|
||||
|
||||
auto messageID = ++_currentID;
|
||||
|
||||
qDebug() << "Requesting data from" << start << "to" << end << "of" << hash << "from asset-server.";
|
||||
|
||||
packet->writePrimitive(messageID);
|
||||
|
||||
packet->write(hash.toLatin1());
|
||||
|
@ -166,6 +187,7 @@ void AssetClient::handleAssetGetReply(QSharedPointer<NLPacketList> packetList, S
|
|||
bool AssetClient::uploadAsset(QByteArray data, QString extension, UploadResultCallback callback) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
|
||||
|
||||
if (assetServer) {
|
||||
auto packetList = std::unique_ptr<NLPacketList>(new NLPacketList(PacketType::AssetUpload, QByteArray(), true, true));
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "NLPacket.h"
|
||||
|
||||
class AssetRequest;
|
||||
class AssetUpload;
|
||||
|
||||
struct AssetInfo {
|
||||
QString hash;
|
||||
|
@ -37,7 +38,8 @@ class AssetClient : public QObject, public Dependency {
|
|||
public:
|
||||
AssetClient();
|
||||
|
||||
Q_INVOKABLE AssetRequest* create(QString hash, QString extension);
|
||||
Q_INVOKABLE AssetRequest* createRequest(QString hash, QString extension);
|
||||
Q_INVOKABLE AssetUpload* createUpload(QString filename);
|
||||
|
||||
private slots:
|
||||
void handleAssetGetInfoReply(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
|
@ -45,9 +47,6 @@ private slots:
|
|||
void handleAssetUploadReply(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
|
||||
|
||||
private:
|
||||
friend class AssetRequest;
|
||||
friend class Menu;
|
||||
|
||||
bool getAssetInfo(QString hash, QString extension, GetInfoCallback callback);
|
||||
bool getAsset(QString hash, QString extension, DataOffset start, DataOffset end, ReceivedAssetCallback callback);
|
||||
bool uploadAsset(QByteArray data, QString extension, UploadResultCallback callback);
|
||||
|
@ -56,6 +55,9 @@ private:
|
|||
QHash<MessageID, ReceivedAssetCallback> _pendingRequests;
|
||||
QHash<MessageID, GetInfoCallback> _pendingInfoRequests;
|
||||
QHash<MessageID, UploadResultCallback> _pendingUploads;
|
||||
|
||||
friend class AssetRequest;
|
||||
friend class AssetUpload;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -50,6 +50,7 @@ void AssetRequest::start() {
|
|||
++_numPendingRequests;
|
||||
auto start = i * CHUNK_SIZE;
|
||||
auto end = std::min((i + 1) * CHUNK_SIZE, info.size);
|
||||
|
||||
assetClient->getAsset(_hash, _extension, start, end, [this, start, end](bool success, QByteArray data) {
|
||||
Q_ASSERT(data.size() == (end - start));
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ void AssetResourceRequest::doSend() {
|
|||
auto hash = parts[0];
|
||||
auto extension = parts.length() > 1 ? parts[1] : "";
|
||||
|
||||
auto request = assetClient->create(hash, extension);
|
||||
auto request = assetClient->createRequest(hash, extension);
|
||||
|
||||
if (!request) {
|
||||
return;
|
||||
|
|
67
libraries/networking/src/AssetUpload.cpp
Normal file
67
libraries/networking/src/AssetUpload.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// AssetUpload.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-26.
|
||||
// Copyright 2015 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 "AssetUpload.h"
|
||||
|
||||
#include <QtCore/QFileInfo>
|
||||
#include <QtCore/QThread>
|
||||
|
||||
#include "AssetClient.h"
|
||||
|
||||
AssetUpload::AssetUpload(QObject* object, const QString& filename) :
|
||||
_filename(filename)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void AssetUpload::start() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "start", Qt::AutoConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
// try to open the file at the given filename
|
||||
QFile file { _filename };
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
|
||||
// file opened, read the data and grab the extension
|
||||
_extension = QFileInfo(_filename).suffix();
|
||||
|
||||
auto data = file.readAll();
|
||||
|
||||
// ask the AssetClient to upload the asset and emit the proper signals from the passed callback
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
|
||||
assetClient->uploadAsset(data, _extension, [this](bool success, QString hash){
|
||||
if (success) {
|
||||
// successful upload - emit finished with a point to ourselves and the resulting hash
|
||||
_result = Success;
|
||||
|
||||
emit finished(this, hash);
|
||||
} else {
|
||||
// error during upload - emit finished with an empty hash
|
||||
// callers can get the error from this object
|
||||
|
||||
// TODO: get the actual error from the callback
|
||||
_result = PermissionDenied;
|
||||
|
||||
emit finished(this, hash);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// we couldn't open the file - set the error result
|
||||
_result = ErrorLoadingFile;
|
||||
|
||||
// emit that we are done
|
||||
emit finished(this, QString());
|
||||
}
|
||||
}
|
53
libraries/networking/src/AssetUpload.h
Normal file
53
libraries/networking/src/AssetUpload.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// AssetUpload.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2015-08-26.
|
||||
// Copyright 2015 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_AssetUpload_h
|
||||
#define hifi_AssetUpload_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
// You should be able to upload an asset from any thread, and handle the responses in a safe way
|
||||
// on your own thread. Everything should happen on AssetClient's thread, the caller should
|
||||
// receive events by connecting to signals on an object that lives on AssetClient's threads.
|
||||
|
||||
class AssetUpload : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
enum Result {
|
||||
Success = 0,
|
||||
Timeout,
|
||||
TooLarge,
|
||||
PermissionDenied,
|
||||
ErrorLoadingFile
|
||||
};
|
||||
|
||||
AssetUpload(QObject* parent, const QString& filename);
|
||||
|
||||
Q_INVOKABLE void start();
|
||||
|
||||
const QString& getFilename() const { return _filename; }
|
||||
const QString& getExtension() const { return _extension; }
|
||||
const Result& getResult() const { return _result; }
|
||||
|
||||
signals:
|
||||
void finished(AssetUpload* upload, const QString& hash);
|
||||
void progress(uint64_t totalReceived, uint64_t total);
|
||||
|
||||
private:
|
||||
QString _filename;
|
||||
QString _extension;
|
||||
Result _result;
|
||||
};
|
||||
|
||||
#endif // hifi_AssetUpload_h
|
|
@ -25,6 +25,9 @@ enum AssetServerError : uint8_t {
|
|||
ASSET_NOT_FOUND,
|
||||
INVALID_BYTE_RANGE,
|
||||
ASSET_TOO_LARGE,
|
||||
PERMISSION_DENIED
|
||||
};
|
||||
|
||||
const QString ATP_SCHEME = "atp";
|
||||
|
||||
#endif
|
||||
|
|
|
@ -127,6 +127,8 @@ const char* Assignment::getTypeName() const {
|
|||
return "avatar-mixer";
|
||||
case Assignment::AgentType:
|
||||
return "agent";
|
||||
case Assignment::AssetServerType:
|
||||
return "asset-server";
|
||||
case Assignment::EntityServerType:
|
||||
return "entity-server";
|
||||
default:
|
||||
|
|
|
@ -409,6 +409,9 @@ void LimitedNodeList::eraseAllNodes() {
|
|||
|
||||
void LimitedNodeList::reset() {
|
||||
eraseAllNodes();
|
||||
|
||||
// we need to make sure any socket connections are gone so wait on that here
|
||||
_nodeSocket.clearConnections();
|
||||
}
|
||||
|
||||
void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) {
|
||||
|
|
|
@ -31,6 +31,7 @@ void NodeType::init() {
|
|||
TypeNameHash.insert(NodeType::Agent, "Agent");
|
||||
TypeNameHash.insert(NodeType::AudioMixer, "Audio Mixer");
|
||||
TypeNameHash.insert(NodeType::AvatarMixer, "Avatar Mixer");
|
||||
TypeNameHash.insert(NodeType::AssetServer, "Asset Server");
|
||||
TypeNameHash.insert(NodeType::Unassigned, "Unassigned");
|
||||
}
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object,
|
|||
qCWarning(networking) << "Registering a packet listener for packet type" << type
|
||||
<< "that will remove a previously registered listener";
|
||||
}
|
||||
|
||||
|
||||
// add the mapping
|
||||
_packetListenerMap[type] = ObjectMethodPair(QPointer<QObject>(object), slot);
|
||||
}
|
||||
|
@ -210,10 +210,30 @@ void PacketReceiver::unregisterListener(QObject* listener) {
|
|||
|
||||
{
|
||||
QMutexLocker packetListenerLocker(&_packetListenerLock);
|
||||
std::remove_if(std::begin(_packetListenerMap), std::end(_packetListenerMap),
|
||||
[&listener](const ObjectMethodPair& pair) {
|
||||
return pair.first == listener;
|
||||
});
|
||||
|
||||
// TODO: replace the two while loops below with a replace_if on the vector (once we move to Message everywhere)
|
||||
|
||||
// clear any registrations for this listener in _packetListenerMap
|
||||
auto it = _packetListenerMap.begin();
|
||||
|
||||
while (it != _packetListenerMap.end()) {
|
||||
if (it.value().first == listener) {
|
||||
it = _packetListenerMap.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
// clear any registrations for this listener in _packetListListener
|
||||
auto listIt = _packetListListenerMap.begin();
|
||||
|
||||
while (listIt != _packetListListenerMap.end()) {
|
||||
if (listIt.value().first == listener) {
|
||||
listIt = _packetListListenerMap.erase(listIt);
|
||||
} else {
|
||||
++listIt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QMutexLocker directConnectSetLocker(&_directConnectSetMutex);
|
||||
|
|
|
@ -69,6 +69,7 @@ private:
|
|||
using ObjectMethodPair = std::pair<QPointer<QObject>, QMetaMethod>;
|
||||
|
||||
QMutex _packetListenerLock;
|
||||
// TODO: replace the two following hashes with an std::vector once we switch Packet/PacketList to Message
|
||||
QHash<PacketType, ObjectMethodPair> _packetListenerMap;
|
||||
QHash<PacketType, ObjectMethodPair> _packetListListenerMap;
|
||||
int _inPacketCount = 0;
|
||||
|
|
|
@ -659,6 +659,7 @@ void Connection::processTimeoutNAK(std::unique_ptr<ControlPacket> controlPacket)
|
|||
}
|
||||
|
||||
void Connection::resetReceiveState() {
|
||||
|
||||
// reset all SequenceNumber member variables back to default
|
||||
SequenceNumber defaultSequenceNumber;
|
||||
|
||||
|
@ -669,6 +670,9 @@ void Connection::resetReceiveState() {
|
|||
|
||||
_lastSentACK = defaultSequenceNumber;
|
||||
|
||||
// clear the sent ACKs
|
||||
_sentACKs.clear();
|
||||
|
||||
// clear the loss list and _lastNAKTime
|
||||
_lossList.clear();
|
||||
_lastNAKTime = high_resolution_clock::time_point();
|
||||
|
|
|
@ -134,6 +134,12 @@ void SendQueue::queuePacketList(std::unique_ptr<PacketList> packetList) {
|
|||
|
||||
void SendQueue::stop() {
|
||||
_isRunning = false;
|
||||
|
||||
// in case we're waiting to send another handshake, release the condition_variable now so we cleanup sooner
|
||||
_handshakeACKCondition.notify_one();
|
||||
|
||||
// in case the empty condition is waiting for packets/loss release it now so that the queue is cleaned up
|
||||
_emptyCondition.notify_one();
|
||||
}
|
||||
|
||||
void SendQueue::sendPacket(const Packet& packet) {
|
||||
|
@ -274,7 +280,7 @@ void SendQueue::run() {
|
|||
|
||||
// if we didn't find a packet to re-send AND we think we can fit a new packet on the wire
|
||||
// (this is according to the current flow window size) then we send out a new packet
|
||||
if (!sentAPacket) {
|
||||
if (_hasReceivedHandshakeACK && !sentAPacket) {
|
||||
flowWindowFull = (seqlen(SequenceNumber { (uint32_t) _lastACKSequenceNumber }, _currentSequenceNumber) >
|
||||
_flowWindowSize);
|
||||
sentAPacket = maybeSendNewPacket();
|
||||
|
@ -299,8 +305,9 @@ void SendQueue::run() {
|
|||
|
||||
if (flowWindowFull && (high_resolution_clock::now() - _flowWindowFullSince) > CONSIDER_INACTIVE_AFTER) {
|
||||
// If the flow window has been full for over CONSIDER_INACTIVE_AFTER,
|
||||
// then signal the queue is inactive
|
||||
// then signal the queue is inactive and return so it can be cleaned up
|
||||
emit queueInactive();
|
||||
return;
|
||||
} else {
|
||||
// During our processing above we didn't send any packets and the flow window is not full.
|
||||
|
||||
|
@ -310,15 +317,18 @@ void SendQueue::run() {
|
|||
|
||||
// The packets queue and loss list mutexes are now both locked - check if they're still both empty
|
||||
if (doubleLock.try_lock() && _packets.empty() && _naks.getLength() == 0) {
|
||||
|
||||
// both are empty - let's use a condition_variable_any to wait
|
||||
auto cvStatus = _emptyCondition.wait_for(doubleLock, CONSIDER_INACTIVE_AFTER);
|
||||
|
||||
// we have the double lock again - Make sure to unlock it
|
||||
doubleLock.unlock();
|
||||
|
||||
// Check if we've been inactive for too long
|
||||
if (cvStatus == std::cv_status::timeout) {
|
||||
// the wait_for released because we've been inactive for too long
|
||||
// so emit our inactive signal and return so the send queue can be cleaned up
|
||||
emit queueInactive();
|
||||
return;
|
||||
}
|
||||
|
||||
// skip to the next iteration
|
||||
|
|
|
@ -83,7 +83,7 @@ private:
|
|||
void sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber);
|
||||
|
||||
bool maybeSendNewPacket(); // Figures out what packet to send next
|
||||
bool maybeResendPacket(); // Determines whether to resend a packet and wich one
|
||||
bool maybeResendPacket(); // Determines whether to resend a packet and which one
|
||||
|
||||
// Increments current sequence number and return it
|
||||
SequenceNumber getNextSequenceNumber();
|
||||
|
|
|
@ -154,8 +154,19 @@ Connection& Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) {
|
|||
return *it->second;
|
||||
}
|
||||
|
||||
void Socket::clearConnections() {
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "clearConnections", Qt::BlockingQueuedConnection);
|
||||
return;
|
||||
}
|
||||
|
||||
// clear all of the current connections in the socket
|
||||
qDebug() << "Clearing all remaining connections in Socket.";
|
||||
_connectionsHash.clear();
|
||||
}
|
||||
|
||||
void Socket::cleanupConnection(HifiSockAddr sockAddr) {
|
||||
qCDebug(networking) << "Socket::cleanupConnection called for connection to" << sockAddr;
|
||||
qCDebug(networking) << "Socket::cleanupConnection called for UDT connection to" << sockAddr;
|
||||
_connectionsHash.erase(sockAddr);
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ public:
|
|||
|
||||
public slots:
|
||||
void cleanupConnection(HifiSockAddr sockAddr);
|
||||
void clearConnections();
|
||||
|
||||
private slots:
|
||||
void readPendingDatagrams();
|
||||
|
|
Loading…
Reference in a new issue