Merge pull request #20 from birarda/asset-upload

fix some bugs, add an asset-uploader with copiable URL
This commit is contained in:
Ryan Huffman 2015-08-27 15:16:21 -07:00
commit db97996319
31 changed files with 519 additions and 69 deletions

View file

@ -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));

View file

@ -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",

View file

@ -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);

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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");

View file

@ -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";

View file

@ -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");
}

View file

@ -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)

View 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);
}

View 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

View file

@ -1,6 +1,6 @@
//
// DialogsManager.cpp
//
// interface/src/ui
//
// Created by Clement on 1/18/15.
// Copyright 2015 High Fidelity, Inc.

View file

@ -1,6 +1,6 @@
//
// DialogsManager.h
//
// interface/src/ui
//
// Created by Clement on 1/18/15.
// Copyright 2015 High Fidelity, Inc.

View file

@ -16,8 +16,7 @@
#include <OffscreenQmlDialog.h>
class LoginDialog : public OffscreenQmlDialog
{
class LoginDialog : public OffscreenQmlDialog {
Q_OBJECT
HIFI_QML_DECL

View file

@ -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));

View file

@ -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

View file

@ -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));

View file

@ -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;

View 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());
}
}

View 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

View file

@ -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

View file

@ -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:

View file

@ -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) {

View file

@ -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");
}

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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

View file

@ -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();

View file

@ -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);
}

View file

@ -75,6 +75,7 @@ public:
public slots:
void cleanupConnection(HifiSockAddr sockAddr);
void clearConnections();
private slots:
void readPendingDatagrams();