resolve conflicts on merge with origin/protocol

This commit is contained in:
Stephen Birarda 2015-08-27 14:45:22 -07:00
commit c80c4a9b45
53 changed files with 1861 additions and 527 deletions

View file

@ -16,6 +16,7 @@
#include "audio/AudioMixer.h" #include "audio/AudioMixer.h"
#include "avatars/AvatarMixer.h" #include "avatars/AvatarMixer.h"
#include "entities/EntityServer.h" #include "entities/EntityServer.h"
#include "assets/AssetServer.h"
ThreadedAssignment* AssignmentFactory::unpackAssignment(NLPacket& packet) { ThreadedAssignment* AssignmentFactory::unpackAssignment(NLPacket& packet) {
@ -33,6 +34,8 @@ ThreadedAssignment* AssignmentFactory::unpackAssignment(NLPacket& packet) {
return new Agent(packet); return new Agent(packet);
case Assignment::EntityServerType: case Assignment::EntityServerType:
return new EntityServer(packet); return new EntityServer(packet);
case Assignment::AssetServerType:
return new AssetServer(packet);
default: default:
return NULL; return NULL;
} }

View file

@ -0,0 +1,202 @@
//
// AssetServer.cpp
// assignment-client/src/assets
//
// Created by Ryan Huffman on 2015/07/21
// 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 "AssetServer.h"
#include <QBuffer>
#include <QCryptographicHash>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QCoreApplication>
#include <QEventLoop>
#include <QRunnable>
#include <QString>
#include "NetworkLogging.h"
#include "NodeType.h"
#include "SendAssetTask.h"
const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server";
AssetServer::AssetServer(NLPacket& packet) :
ThreadedAssignment(packet),
_taskPool(this)
{
// Most of the work will be I/O bound, reading from disk and constructing packet objects,
// so the ideal is greater than the number of cores on the system.
_taskPool.setMaxThreadCount(20);
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet");
packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo");
packetReceiver.registerMessageListener(PacketType::AssetUpload, this, "handleAssetUpload");
}
void AssetServer::run() {
ThreadedAssignment::commonInit(ASSET_SERVER_LOGGING_TARGET_NAME, NodeType::AssetServer);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
_resourcesDirectory = QDir(QCoreApplication::applicationDirPath()).filePath("resources/assets");
if (!_resourcesDirectory.exists()) {
qDebug() << "Creating resources directory";
_resourcesDirectory.mkpath(".");
}
qDebug() << "Serving files from: " << _resourcesDirectory.path();
// Scan for new files
qDebug() << "Looking for new files in asset directory";
auto files = _resourcesDirectory.entryInfoList(QDir::Files);
QRegExp filenameRegex { "^[a-f0-9]{" + QString::number(HASH_HEX_LENGTH) + "}(\\..+)?$" };
for (const auto& fileInfo : files) {
auto filename = fileInfo.fileName();
if (!filenameRegex.exactMatch(filename)) {
qDebug() << "Found file: " << filename;
if (!fileInfo.isReadable()) {
qDebug() << "\tCan't open file for reading: " << filename;
continue;
}
// Read file
QFile file { fileInfo.absoluteFilePath() };
file.open(QFile::ReadOnly);
QByteArray data = file.readAll();
auto hash = hashData(data);
qDebug() << "\tMoving " << filename << " to " << hash;
file.rename(_resourcesDirectory.absoluteFilePath(hash) + "." + fileInfo.suffix());
}
}
}
void AssetServer::handleAssetGetInfo(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
QByteArray assetHash;
MessageID messageID;
uint8_t extensionLength;
if (packet->getPayloadSize() < qint64(HASH_HEX_LENGTH + sizeof(messageID) + sizeof(extensionLength))) {
qDebug() << "ERROR bad file request";
return;
}
packet->readPrimitive(&messageID);
assetHash = packet->readWithoutCopy(HASH_HEX_LENGTH);
packet->readPrimitive(&extensionLength);
QByteArray extension = packet->read(extensionLength);
auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply);
replyPacket->writePrimitive(messageID);
replyPacket->write(assetHash);
QString fileName = QString(assetHash) + "." + extension;
QFileInfo fileInfo { _resourcesDirectory.filePath(fileName) };
if (fileInfo.exists() && fileInfo.isReadable()) {
qDebug() << "Opening file: " << fileInfo.filePath();
replyPacket->writePrimitive(AssetServerError::NO_ERROR);
replyPacket->writePrimitive(fileInfo.size());
} else {
qDebug() << "Asset not found: " << assetHash;
replyPacket->writePrimitive(AssetServerError::ASSET_NOT_FOUND);
}
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacket(std::move(replyPacket), *senderNode);
}
void AssetServer::handleAssetGet(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
MessageID messageID;
QByteArray assetHash;
uint8_t extensionLength;
DataOffset start;
DataOffset end;
auto minSize = qint64(sizeof(messageID) + HASH_HEX_LENGTH + sizeof(extensionLength) + sizeof(start) + sizeof(end));
if (packet->getPayloadSize() < minSize) {
qDebug() << "ERROR bad file request";
return;
}
packet->readPrimitive(&messageID);
assetHash = packet->read(HASH_HEX_LENGTH);
packet->readPrimitive(&extensionLength);
QByteArray extension = packet->read(extensionLength);
packet->readPrimitive(&start);
packet->readPrimitive(&end);
qDebug() << "Received a request for the file (" << messageID << "): " << assetHash << " from " << start << " to " << end;
// Queue task
QString filePath = _resourcesDirectory.filePath(QString(assetHash) + "." + QString(extension));
auto task = new SendAssetTask(messageID, assetHash, filePath, start, end, senderNode);
_taskPool.start(task);
}
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));
uint8_t extensionLength;
buffer.read(reinterpret_cast<char*>(&extensionLength), sizeof(extensionLength));
QByteArray extension = buffer.read(extensionLength);
qDebug() << "Got extension: " << extension;
uint64_t fileSize;
buffer.read(reinterpret_cast<char*>(&fileSize), sizeof(fileSize));
qDebug() << "Receiving a file of size " << fileSize;
auto replyPacket = NLPacket::create(PacketType::AssetUploadReply);
replyPacket->writePrimitive(messageID);
if (fileSize > MAX_UPLOAD_SIZE) {
replyPacket->writePrimitive(AssetServerError::ASSET_TOO_LARGE);
} else {
QByteArray fileData = buffer.read(fileSize);
QString hash = hashData(fileData);
qDebug() << "Got data: (" << hash << ") ";
QFile file { _resourcesDirectory.filePath(QString(hash)) + "." + QString(extension) };
if (file.exists()) {
qDebug() << "[WARNING] This file already exists: " << hash;
} else {
file.open(QIODevice::WriteOnly);
file.write(fileData);
file.close();
}
replyPacket->writePrimitive(AssetServerError::NO_ERROR);
replyPacket->write(hash.toLatin1());
}
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacket(std::move(replyPacket), *senderNode);
}
QString AssetServer::hashData(const QByteArray& data) {
return QString(QCryptographicHash::hash(data, QCryptographicHash::Sha256).toHex());
}

View file

@ -0,0 +1,47 @@
//
// AssetServer.h
// assignment-client/src/assets
//
// Created by Ryan Huffman on 2015/07/21
// 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
//
#ifndef hifi_AssetServer_h
#define hifi_AssetServer_h
#include <QDir>
#include <ThreadedAssignment.h>
#include <QThreadPool>
#include "AssetUtils.h"
class AssetServer : public ThreadedAssignment {
Q_OBJECT
public:
AssetServer(NLPacket& packet);
static QString hashData(const QByteArray& data);
public slots:
void run();
private slots:
void handleAssetGetInfo(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void handleAssetGet(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void handleAssetUpload(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
private:
static void writeError(NLPacketList* packetList, AssetServerError error);
QDir _resourcesDirectory;
QThreadPool _taskPool;
};
inline void writeError(NLPacketList* packetList, AssetServerError error) {
packetList->writePrimitive(error);
}
#endif

View file

@ -0,0 +1,70 @@
//
// SendAssetTask.cpp
// assignment-client/src/assets
//
// Created by Ryan Huffman 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 "SendAssetTask.h"
#include <QFile>
#include <DependencyManager.h>
#include <NetworkLogging.h>
#include <NLPacket.h>
#include <NLPacketList.h>
#include <NodeList.h>
#include "AssetUtils.h"
SendAssetTask::SendAssetTask(MessageID messageID, const QByteArray& assetHash, QString filePath, DataOffset start, DataOffset end,
const SharedNodePointer& sendToNode) :
QRunnable(),
_messageID(messageID),
_assetHash(assetHash),
_filePath(filePath),
_start(start),
_end(end),
_sendToNode(sendToNode)
{
}
void SendAssetTask::run() {
qDebug() << "Starting task to send asset: " << _assetHash << " for messageID " << _messageID;
auto replyPacketList = std::unique_ptr<NLPacketList>(new NLPacketList(PacketType::AssetGetReply, QByteArray(), true, true));
replyPacketList->write(_assetHash);
replyPacketList->writePrimitive(_messageID);
if (_end <= _start) {
writeError(replyPacketList.get(), AssetServerError::INVALID_BYTE_RANGE);
} else {
QFile file { _filePath };
if (file.open(QIODevice::ReadOnly)) {
if (file.size() < _end) {
writeError(replyPacketList.get(), AssetServerError::INVALID_BYTE_RANGE);
qCDebug(networking) << "Bad byte range: " << _assetHash << " " << _start << ":" << _end;
} else {
auto size = _end - _start;
file.seek(_start);
replyPacketList->writePrimitive(AssetServerError::NO_ERROR);
replyPacketList->writePrimitive(size);
replyPacketList->write(file.read(size));
qCDebug(networking) << "Sending asset: " << _assetHash;
}
file.close();
} else {
qCDebug(networking) << "Asset not found: " << _filePath << "(" << _assetHash << ")";
writeError(replyPacketList.get(), AssetServerError::ASSET_NOT_FOUND);
}
}
auto nodeList = DependencyManager::get<NodeList>();
nodeList->sendPacketList(std::move(replyPacketList), *_sendToNode);
}

View file

@ -0,0 +1,42 @@
//
// SendAssetTask.h
// assignment-client/src/assets
//
// Created by Ryan Huffman 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
//
#ifndef hifi_SendAssetTask_h
#define hifi_SendAssetTask_h
#include <QByteArray>
#include <QString>
#include <QRunnable>
#include "AssetUtils.h"
#include "AssetServer.h"
#include "Node.h"
class SendAssetTask : public QRunnable {
public:
SendAssetTask(MessageID messageID, const QByteArray& assetHash, QString filePath, DataOffset start, DataOffset end,
const SharedNodePointer& sendToNode);
void run();
signals:
void finished();
private:
MessageID _messageID;
QByteArray _assetHash;
QString _filePath;
DataOffset _start;
DataOffset _end;
SharedNodePointer _sendToNode;
};
#endif

View file

@ -573,7 +573,6 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet<Ass
if (!excludedTypes.contains(defaultedType) if (!excludedTypes.contains(defaultedType)
&& defaultedType != Assignment::UNUSED_0 && defaultedType != Assignment::UNUSED_0
&& defaultedType != Assignment::UNUSED_1 && defaultedType != Assignment::UNUSED_1
&& defaultedType != Assignment::UNUSED_2
&& defaultedType != Assignment::AgentType) { && defaultedType != Assignment::AgentType) {
// type has not been set from a command line or config file config, use the default // 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 // by clearing whatever exists and writing a single default assignment with no payload

View file

@ -28,39 +28,39 @@ Item {
anchors.fill: parent anchors.fill: parent
onClicked: { root.expanded = !root.expanded; } onClicked: { root.expanded = !root.expanded; }
} }
Column { Column {
id: generalCol id: generalCol
spacing: 4; x: 4; y: 4; spacing: 4; x: 4; y: 4;
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Servers: " + root.serverCount text: "Servers: " + root.serverCount
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Avatars: " + root.avatarCount text: "Avatars: " + root.avatarCount
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Framerate: " + root.framerate text: "Framerate: " + root.framerate
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Simrate: " + root.simrate text: "Simrate: " + root.simrate
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount text: "Packets In/Out: " + root.packetInCount + "/" + root.packetOutCount
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Mbps In/Out: " + root.mbpsIn.toFixed(2) + "/" + root.mbpsOut.toFixed(2) text: "Mbps In/Out: " + root.mbpsIn.toFixed(2) + "/" + root.mbpsOut.toFixed(2)
} }
} }
} }
@ -77,30 +77,35 @@ Item {
Column { Column {
id: pingCol id: pingCol
spacing: 4; x: 4; y: 4; spacing: 4; x: 4; y: 4;
Text { Text {
color: root.fontColor color: root.fontColor
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Audio ping: " + root.audioPing text: "Audio ping: " + root.audioPing
} }
Text { Text {
color: root.fontColor color: root.fontColor
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Avatar ping: " + root.avatarPing text: "Avatar ping: " + root.avatarPing
} }
Text { Text {
color: root.fontColor color: root.fontColor
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Entities avg ping: " + root.entitiesPing text: "Entities avg ping: " + root.entitiesPing
} }
Text { Text {
color: root.fontColor color: root.fontColor
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded; text: "Asset ping: " + root.assetPing
}
Text {
color: root.fontColor
font.pixelSize: root.fontSize
visible: root.expanded;
text: "Voxel max ping: " + 0 text: "Voxel max ping: " + 0
} }
} }
} }
Rectangle { Rectangle {
width: geoCol.width + 8 width: geoCol.width + 8
height: geoCol.height + 8 height: geoCol.height + 8
@ -112,34 +117,34 @@ Item {
Column { Column {
id: geoCol id: geoCol
spacing: 4; x: 4; y: 4; spacing: 4; x: 4; y: 4;
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Position: " + root.position.x.toFixed(1) + ", " + text: "Position: " + root.position.x.toFixed(1) + ", " +
root.position.y.toFixed(1) + ", " + root.position.z.toFixed(1) root.position.y.toFixed(1) + ", " + root.position.z.toFixed(1)
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Velocity: " + root.velocity.toFixed(1) text: "Velocity: " + root.velocity.toFixed(1)
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Yaw: " + root.yaw.toFixed(1) text: "Yaw: " + root.yaw.toFixed(1)
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded; visible: root.expanded;
text: "Avatar Mixer: " + root.avatarMixerKbps + " kbps, " + text: "Avatar Mixer: " + root.avatarMixerKbps + " kbps, " +
root.avatarMixerPps + "pps"; root.avatarMixerPps + "pps";
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded; visible: root.expanded;
text: "Downloads: "; text: "Downloads: ";
} }
} }
} }
@ -154,72 +159,72 @@ Item {
Column { Column {
id: octreeCol id: octreeCol
spacing: 4; x: 4; y: 4; spacing: 4; x: 4; y: 4;
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
text: "Triangles: " + root.triangles + text: "Triangles: " + root.triangles +
" / Quads: " + root.quads + " / Material Switches: " + root.materialSwitches " / Quads: " + root.quads + " / Material Switches: " + root.materialSwitches
} }
Text { Text {
color: root.fontColor;
font.pixelSize: root.fontSize
visible: root.expanded;
text: "\tMesh Parts Rendered Opaque: " + root.meshOpaque +
" / Translucent: " + root.meshTranslucent;
}
Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded; visible: root.expanded;
text: "\tOpaque considered: " + root.opaqueConsidered + text: "\tMesh Parts Rendered Opaque: " + root.meshOpaque +
" / Out of view: " + root.opaqueOutOfView + " / Too small: " + root.opaqueTooSmall; " / Translucent: " + root.meshTranslucent;
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: !root.expanded visible: root.expanded;
text: "Octree Elements Server: " + root.serverElements + text: "\tOpaque considered: " + root.opaqueConsidered +
" Local: " + root.localElements; " / Out of view: " + root.opaqueOutOfView + " / Too small: " + root.opaqueTooSmall;
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded visible: !root.expanded
text: "Octree Sending Mode: " + root.sendingMode; text: "Octree Elements Server: " + root.serverElements +
" Local: " + root.localElements;
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded visible: root.expanded
text: "Octree Packets to Process: " + root.packetStats; text: "Octree Sending Mode: " + root.sendingMode;
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded visible: root.expanded
text: "Octree Elements - "; text: "Octree Packets to Process: " + root.packetStats;
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded visible: root.expanded
text: "\tServer: " + root.serverElements + text: "Octree Elements - ";
" Internal: " + root.serverInternal +
" Leaves: " + root.serverLeaves;
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded visible: root.expanded
text: "\tLocal: " + root.localElements + text: "\tServer: " + root.serverElements +
" Internal: " + root.localInternal + " Internal: " + root.serverInternal +
" Leaves: " + root.localLeaves; " Leaves: " + root.serverLeaves;
} }
Text { Text {
color: root.fontColor; color: root.fontColor;
font.pixelSize: root.fontSize font.pixelSize: root.fontSize
visible: root.expanded visible: root.expanded
text: "LOD: " + root.lodStatus; text: "\tLocal: " + root.localElements +
" Internal: " + root.localInternal +
" Leaves: " + root.localLeaves;
}
Text {
color: root.fontColor;
font.pixelSize: root.fontSize
visible: root.expanded
text: "LOD: " + root.lodStatus;
} }
} }
} }

View file

@ -52,6 +52,7 @@
#include <AccountManager.h> #include <AccountManager.h>
#include <AddressManager.h> #include <AddressManager.h>
#include <AssetClient.h>
#include <ApplicationVersion.h> #include <ApplicationVersion.h>
#include <CursorManager.h> #include <CursorManager.h>
#include <AudioInjector.h> #include <AudioInjector.h>
@ -297,6 +298,7 @@ bool setupEssentials(int& argc, char** argv) {
auto autoUpdater = DependencyManager::set<AutoUpdater>(); auto autoUpdater = DependencyManager::set<AutoUpdater>();
auto pathUtils = DependencyManager::set<PathUtils>(); auto pathUtils = DependencyManager::set<PathUtils>();
auto actionFactory = DependencyManager::set<InterfaceActionFactory>(); auto actionFactory = DependencyManager::set<InterfaceActionFactory>();
auto assetClient = DependencyManager::set<AssetClient>();
auto userInputMapper = DependencyManager::set<UserInputMapper>(); auto userInputMapper = DependencyManager::set<UserInputMapper>();
return true; return true;
@ -444,6 +446,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
audioThread->start(); audioThread->start();
QThread* assetThread = new QThread();
assetThread->setObjectName("Asset Thread");
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->moveToThread(assetThread);
assetThread->start();
const DomainHandler& domainHandler = nodeList->getDomainHandler(); const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
@ -513,7 +526,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// tell the NodeList instance who to tell the domain server we care about // tell the NodeList instance who to tell the domain server we care about
nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer nodeList->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer
<< NodeType::EntityServer); << NodeType::EntityServer << NodeType::AssetServer);
// connect to the packet sent signal of the _entityEditSender // connect to the packet sent signal of the _entityEditSender
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent); connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
@ -2010,6 +2023,7 @@ void Application::sendPingPackets() {
case NodeType::AvatarMixer: case NodeType::AvatarMixer:
case NodeType::AudioMixer: case NodeType::AudioMixer:
case NodeType::EntityServer: case NodeType::EntityServer:
case NodeType::AssetServer:
return true; return true;
default: default:
return false; return false;

View file

@ -22,6 +22,7 @@
#include <UserActivityLogger.h> #include <UserActivityLogger.h>
#include <VrMenu.h> #include <VrMenu.h>
#include <AssetClient.h>
#include "Application.h" #include "Application.h"
#include "AccountManager.h" #include "AccountManager.h"
@ -89,6 +90,36 @@ Menu::Menu() {
addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J, addActionToQMenuAndActionHash(fileMenu, MenuOption::RunningScripts, Qt::CTRL | Qt::Key_J,
qApp, SLOT(toggleRunningScriptsWidget())); 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>(); auto addressManager = DependencyManager::get<AddressManager>();
addDisabledActionAndSeparator(fileMenu, "History"); addDisabledActionAndSeparator(fileMenu, "History");

View file

@ -126,8 +126,10 @@ void Stats::updateStats() {
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer);
SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer);
SharedNodePointer assetServerNode = nodeList->soloNodeOfType(NodeType::AssetServer);
STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1); STAT_UPDATE(audioPing, audioMixerNode ? audioMixerNode->getPingMs() : -1);
STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->getPingMs() : -1); STAT_UPDATE(avatarPing, avatarMixerNode ? avatarMixerNode->getPingMs() : -1);
STAT_UPDATE(assetPing, assetServerNode ? assetServerNode->getPingMs() : -1);
//// Now handle entity servers, since there could be more than one, we average their ping times //// Now handle entity servers, since there could be more than one, we average their ping times
int totalPingOctree = 0; int totalPingOctree = 0;

View file

@ -39,6 +39,7 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, audioPing, 0) STATS_PROPERTY(int, audioPing, 0)
STATS_PROPERTY(int, avatarPing, 0) STATS_PROPERTY(int, avatarPing, 0)
STATS_PROPERTY(int, entitiesPing, 0) STATS_PROPERTY(int, entitiesPing, 0)
STATS_PROPERTY(int, assetPing, 0)
STATS_PROPERTY(QVector3D, position, QVector3D(0, 0, 0) ) STATS_PROPERTY(QVector3D, position, QVector3D(0, 0, 0) )
STATS_PROPERTY(float, velocity, 0) STATS_PROPERTY(float, velocity, 0)
STATS_PROPERTY(float, yaw, 0) STATS_PROPERTY(float, yaw, 0)
@ -105,6 +106,7 @@ signals:
void audioPingChanged(); void audioPingChanged();
void avatarPingChanged(); void avatarPingChanged();
void entitiesPingChanged(); void entitiesPingChanged();
void assetPingChanged();
void positionChanged(); void positionChanged();
void velocityChanged(); void velocityChanged();
void yawChanged(); void yawChanged();

View file

@ -39,14 +39,16 @@ QSharedPointer<Resource> AnimationCache::createResource(const QUrl& url, const Q
return QSharedPointer<Resource>(new Animation(url), &Resource::allReferencesCleared); return QSharedPointer<Resource>(new Animation(url), &Resource::allReferencesCleared);
} }
AnimationReader::AnimationReader(const QUrl& url, QNetworkReply* reply) : Animation::Animation(const QUrl& url) : Resource(url) {}
AnimationReader::AnimationReader(const QUrl& url, const QByteArray& data) :
_url(url), _url(url),
_reply(reply) { _data(data) {
} }
void AnimationReader::run() { void AnimationReader::run() {
try { try {
if (!_reply) { if (_data.isEmpty()) {
throw QString("Reply is NULL ?!"); throw QString("Reply is NULL ?!");
} }
QString urlname = _url.path().toLower(); QString urlname = _url.path().toLower();
@ -58,7 +60,7 @@ void AnimationReader::run() {
// Parse the FBX directly from the QNetworkReply // Parse the FBX directly from the QNetworkReply
FBXGeometry* fbxgeo = nullptr; FBXGeometry* fbxgeo = nullptr;
if (_url.path().toLower().endsWith(".fbx")) { if (_url.path().toLower().endsWith(".fbx")) {
fbxgeo = readFBX(_reply, QVariantHash(), _url.path()); fbxgeo = readFBX(_data, QVariantHash(), _url.path());
} else { } else {
QString errorStr("usupported format"); QString errorStr("usupported format");
emit onError(299, errorStr); emit onError(299, errorStr);
@ -71,11 +73,8 @@ void AnimationReader::run() {
} catch (const QString& error) { } catch (const QString& error) {
emit onError(299, error); emit onError(299, error);
} }
_reply->deleteLater();
} }
Animation::Animation(const QUrl& url) : Resource(url) {}
bool Animation::isLoaded() const { bool Animation::isLoaded() const {
return _loaded && _geometry; return _loaded && _geometry;
} }
@ -108,9 +107,9 @@ const QVector<FBXAnimationFrame>& Animation::getFramesReference() const {
return _geometry->animationFrames; return _geometry->animationFrames;
} }
void Animation::downloadFinished(QNetworkReply* reply) { void Animation::downloadFinished(const QByteArray& data) {
// parse the animation/fbx file on a background thread. // parse the animation/fbx file on a background thread.
AnimationReader* animationReader = new AnimationReader(reply->url(), reply); AnimationReader* animationReader = new AnimationReader(_url, data);
connect(animationReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(animationParseSuccess(FBXGeometry*))); connect(animationReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(animationParseSuccess(FBXGeometry*)));
connect(animationReader, SIGNAL(onError(int, QString)), SLOT(animationParseError(int, QString))); connect(animationReader, SIGNAL(onError(int, QString)), SLOT(animationParseError(int, QString)));
QThreadPool::globalInstance()->start(animationReader); QThreadPool::globalInstance()->start(animationReader);

View file

@ -65,7 +65,7 @@ public:
const QVector<FBXAnimationFrame>& getFramesReference() const; const QVector<FBXAnimationFrame>& getFramesReference() const;
protected: protected:
virtual void downloadFinished(QNetworkReply* reply); virtual void downloadFinished(const QByteArray& data) override;
protected slots: protected slots:
void animationParseSuccess(FBXGeometry* geometry); void animationParseSuccess(FBXGeometry* geometry);
@ -81,7 +81,7 @@ class AnimationReader : public QObject, public QRunnable {
Q_OBJECT Q_OBJECT
public: public:
AnimationReader(const QUrl& url, QNetworkReply* reply); AnimationReader(const QUrl& url, const QByteArray& data);
virtual void run(); virtual void run();
signals: signals:
@ -90,7 +90,7 @@ signals:
private: private:
QUrl _url; QUrl _url;
QNetworkReply* _reply; QByteArray _data;
}; };
class AnimationDetails { class AnimationDetails {

View file

@ -56,16 +56,17 @@ Sound::Sound(const QUrl& url, bool isStereo) :
} }
void Sound::downloadFinished(QNetworkReply* reply) { void Sound::downloadFinished(const QByteArray& data) {
// replace our byte array with the downloaded data // replace our byte array with the downloaded data
QByteArray rawAudioByteArray = reply->readAll(); QByteArray rawAudioByteArray = QByteArray(data);
QString fileName = reply->url().fileName(); QString fileName = getURL().fileName();
const QString WAV_EXTENSION = ".wav"; const QString WAV_EXTENSION = ".wav";
if (reply->hasRawHeader("Content-Type") || fileName.endsWith(WAV_EXTENSION)) { if (fileName.endsWith(WAV_EXTENSION)) {
QByteArray headerContentType = reply->rawHeader("Content-Type"); QString headerContentType = "audio/x-wav";
//QByteArray headerContentType = reply->rawHeader("Content-Type");
// WAV audio file encountered // WAV audio file encountered
if (headerContentType == "audio/x-wav" if (headerContentType == "audio/x-wav"
@ -80,9 +81,9 @@ void Sound::downloadFinished(QNetworkReply* reply) {
} else { } else {
// check if this was a stereo raw file // check if this was a stereo raw file
// since it's raw the only way for us to know that is if the file was called .stereo.raw // since it's raw the only way for us to know that is if the file was called .stereo.raw
if (reply->url().fileName().toLower().endsWith("stereo.raw")) { if (fileName.toLower().endsWith("stereo.raw")) {
_isStereo = true; _isStereo = true;
qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << reply->url() << "as stereo audio file."; qCDebug(audio) << "Processing sound of" << rawAudioByteArray.size() << "bytes from" << getURL() << "as stereo audio file.";
} }
// Process as RAW file // Process as RAW file
@ -94,7 +95,6 @@ void Sound::downloadFinished(QNetworkReply* reply) {
} }
_isReady = true; _isReady = true;
reply->deleteLater();
} }
void Sound::downSample(const QByteArray& rawAudioByteArray) { void Sound::downSample(const QByteArray& rawAudioByteArray) {

View file

@ -39,7 +39,7 @@ private:
void downSample(const QByteArray& rawAudioByteArray); void downSample(const QByteArray& rawAudioByteArray);
void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray);
virtual void downloadFinished(QNetworkReply* reply); virtual void downloadFinished(const QByteArray& data) override;
}; };
typedef QSharedPointer<Sound> SharedSoundPointer; typedef QSharedPointer<Sound> SharedSoundPointer;

View file

@ -0,0 +1,217 @@
//
// AssetClient.cpp
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/21
// 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 "AssetClient.h"
#include <QBuffer>
#include <QThread>
#include "AssetRequest.h"
#include "NodeList.h"
#include "PacketReceiver.h"
#include "AssetUtils.h"
MessageID AssetClient::_currentID = 0;
AssetClient::AssetClient() {
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListener(PacketType::AssetGetInfoReply, this, "handleAssetGetInfoReply");
packetReceiver.registerMessageListener(PacketType::AssetGetReply, this, "handleAssetGetReply");
packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply");
}
AssetRequest* AssetClient::create(QString hash, QString extension) {
if (QThread::currentThread() != thread()) {
AssetRequest* req;
QMetaObject::invokeMethod(this, "create",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(AssetRequest*, req),
Q_ARG(QString, hash),
Q_ARG(QString, extension));
return req;
}
if (hash.length() != HASH_HEX_LENGTH) {
qDebug() << "Invalid hash size";
return nullptr;
}
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
if (assetServer) {
auto assetClient = DependencyManager::get<AssetClient>();
auto request = new AssetRequest(assetClient.data(), hash, extension);
return request;
}
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";
return false;
}
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
if (assetServer) {
auto packet = NLPacket::create(PacketType::AssetGet);
auto messageID = ++_currentID;
packet->writePrimitive(messageID);
packet->write(hash.toLatin1());
packet->writePrimitive(uint8_t(extension.length()));
packet->write(extension.toLatin1());
packet->writePrimitive(start);
packet->writePrimitive(end);
nodeList->sendPacket(std::move(packet), *assetServer);
_pendingRequests[messageID] = callback;
return true;
}
return false;
}
bool AssetClient::getAssetInfo(QString hash, QString extension, GetInfoCallback callback) {
auto nodeList = DependencyManager::get<NodeList>();
SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer);
if (assetServer) {
auto packet = NLPacket::create(PacketType::AssetGetInfo);
auto messageID = ++_currentID;
packet->writePrimitive(messageID);
packet->write(hash.toLatin1().constData(), HASH_HEX_LENGTH);
packet->writePrimitive(uint8_t(extension.length()));
packet->write(extension.toLatin1());
nodeList->sendPacket(std::move(packet), *assetServer);
_pendingInfoRequests[messageID] = callback;
return true;
}
return false;
}
void AssetClient::handleAssetGetInfoReply(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
MessageID messageID;
packet->readPrimitive(&messageID);
auto assetHash = QString(packet->read(HASH_HEX_LENGTH));
AssetServerError error;
packet->readPrimitive(&error);
AssetInfo info { assetHash, 0 };
if (error == NO_ERROR) {
packet->readPrimitive(&info.size);
}
if (_pendingInfoRequests.contains(messageID)) {
auto callback = _pendingInfoRequests.take(messageID);
callback(error == NO_ERROR, info);
}
}
void AssetClient::handleAssetGetReply(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode) {
QByteArray data = packetList->getMessage();
QBuffer packet { &data };
packet.open(QIODevice::ReadOnly);
auto assetHash = packet.read(HASH_HEX_LENGTH);
qDebug() << "Got reply for asset: " << assetHash;
MessageID messageID;
packet.read(reinterpret_cast<char*>(&messageID), sizeof(messageID));
AssetServerError error;
packet.read(reinterpret_cast<char*>(&error), sizeof(AssetServerError));
QByteArray assetData;
if (!error) {
DataOffset length;
packet.read(reinterpret_cast<char*>(&length), sizeof(DataOffset));
data = packet.read(length);
} else {
qDebug() << "Failure getting asset: " << error;
}
if (_pendingRequests.contains(messageID)) {
auto callback = _pendingRequests.take(messageID);
callback(error == NO_ERROR, data);
}
}
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));
auto messageID = ++_currentID;
packetList->writePrimitive(messageID);
packetList->writePrimitive(static_cast<uint8_t>(extension.length()));
packetList->write(extension.toLatin1().constData(), extension.length());
qDebug() << "Extension length: " << extension.length();
qDebug() << "Extension: " << extension;
uint64_t size = data.length();
packetList->writePrimitive(size);
packetList->write(data.constData(), size);
nodeList->sendPacketList(std::move(packetList), *assetServer);
_pendingUploads[messageID] = callback;
return true;
}
return false;
}
void AssetClient::handleAssetUploadReply(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode) {
MessageID messageID;
packet->readPrimitive(&messageID);
AssetServerError error;
packet->readPrimitive(&error);
QString hashString { "" };
if (error) {
qDebug() << "Error uploading file to asset server";
} else {
auto hashData = packet->read(HASH_HEX_LENGTH);
hashString = QString(hashData);
qDebug() << "Successfully uploaded asset to asset-server - SHA256 hash is " << hashString;
}
if (_pendingUploads.contains(messageID)) {
auto callback = _pendingUploads.take(messageID);
callback(error == NO_ERROR, hashString);
}
}

View file

@ -0,0 +1,61 @@
//
// AssetClient.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/21
// 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
//
#ifndef hifi_AssetClient_h
#define hifi_AssetClient_h
#include <QString>
#include <DependencyManager.h>
#include "AssetUtils.h"
#include "LimitedNodeList.h"
#include "NLPacket.h"
class AssetRequest;
struct AssetInfo {
QString hash;
int64_t size;
};
using ReceivedAssetCallback = std::function<void(bool result, QByteArray data)>;
using GetInfoCallback = std::function<void(bool result, AssetInfo info)>;
using UploadResultCallback = std::function<void(bool result, QString hash)>;
class AssetClient : public QObject, public Dependency {
Q_OBJECT
public:
AssetClient();
Q_INVOKABLE AssetRequest* create(QString hash, QString extension);
private slots:
void handleAssetGetInfoReply(QSharedPointer<NLPacket> packet, SharedNodePointer senderNode);
void handleAssetGetReply(QSharedPointer<NLPacketList> packetList, SharedNodePointer senderNode);
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);
static MessageID _currentID;
QHash<MessageID, ReceivedAssetCallback> _pendingRequests;
QHash<MessageID, GetInfoCallback> _pendingInfoRequests;
QHash<MessageID, UploadResultCallback> _pendingUploads;
};
#endif

View file

@ -0,0 +1,75 @@
//
// AssetRequest.cpp
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/24
// 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 "AssetRequest.h"
#include <algorithm>
#include <QThread>
#include "AssetClient.h"
#include "NodeList.h"
AssetRequest::AssetRequest(QObject* parent, QString hash, QString extension) :
QObject(parent),
_hash(hash),
_extension(extension)
{
}
void AssetRequest::start() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "start", Qt::AutoConnection);
return;
}
if (_state == NOT_STARTED) {
_state = WAITING_FOR_INFO;
auto assetClient = DependencyManager::get<AssetClient>();
assetClient->getAssetInfo(_hash, _extension, [this](bool success, AssetInfo info) {
_info = info;
_data.resize(info.size);
const DataOffset CHUNK_SIZE = 1024000000;
qDebug() << "Got size of " << _hash << " : " << info.size << " bytes";
// Round up
int numChunks = (info.size + CHUNK_SIZE - 1) / CHUNK_SIZE;
auto assetClient = DependencyManager::get<AssetClient>();
for (int i = 0; i < numChunks; ++i) {
++_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));
if (success) {
_result = Success;
memcpy((_data.data() + start), data.constData(), end - start);
_totalReceived += data.size();
emit progress(_totalReceived, _info.size);
} else {
_result = Error;
qDebug() << "Got error retrieving asset";
}
--_numPendingRequests;
if (_numPendingRequests == 0) {
_state = FINISHED;
emit finished(this);
}
});
}
});
}
}

View file

@ -0,0 +1,61 @@
//
// AssetRequest.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/24
// 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
//
#ifndef hifi_AssetRequest_h
#define hifi_AssetRequest_h
#include <QByteArray>
#include <QObject>
#include <QString>
#include "AssetClient.h"
#include "AssetUtils.h"
class AssetRequest : public QObject {
Q_OBJECT
public:
enum State {
NOT_STARTED = 0,
WAITING_FOR_INFO,
WAITING_FOR_DATA,
FINISHED
};
enum Result {
Success = 0,
Timeout,
NotFound,
Error,
};
AssetRequest(QObject* parent, QString hash, QString extension);
Q_INVOKABLE void start();
const QByteArray& getData() { return _data; }
signals:
void finished(AssetRequest*);
void progress(qint64 totalReceived, qint64 total);
private:
State _state = NOT_STARTED;
Result _result;
AssetInfo _info;
uint64_t _totalReceived { 0 };
QString _hash;
QString _extension;
QByteArray _data;
int _numPendingRequests { 0 };
};
#endif

View file

@ -0,0 +1,50 @@
//
// AssetResourceRequest.cpp
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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 "AssetResourceRequest.h"
#include "AssetClient.h"
#include "AssetRequest.h"
void AssetResourceRequest::doSend() {
// Make request to atp
auto assetClient = DependencyManager::get<AssetClient>();
auto parts = _url.path().split(".", QString::SkipEmptyParts);
auto hash = parts[0];
auto extension = parts.length() > 1 ? parts[1] : "";
auto request = assetClient->create(hash, extension);
if (!request) {
return;
}
connect(request, &AssetRequest::progress, this, &AssetResourceRequest::progress);
QObject::connect(request, &AssetRequest::finished, [this](AssetRequest* req) mutable {
if (_state != IN_PROGRESS) return;
_state = FINISHED;
if (true) {
_data = req->getData();
_result = ResourceRequest::SUCCESS;
emit finished();
} else {
_result = ResourceRequest::ERROR;
emit finished();
}
});
request->start();
}
void AssetResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
qDebug() << "Got asset data: " << bytesReceived << " / " << bytesTotal;
emit progress(bytesReceived, bytesTotal);
}

View file

@ -0,0 +1,31 @@
//
// AssetResourceRequest.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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
//
#ifndef hifi_AssetResourceRequest_h
#define hifi_AssetResourceRequest_h
#include <QUrl>
#include "ResourceRequest.h"
class AssetResourceRequest : public ResourceRequest {
Q_OBJECT
public:
AssetResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { }
protected:
virtual void doSend() override;
private slots:
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
};
#endif

View file

@ -0,0 +1,30 @@
//
// AssetUtils.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/30
// 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
//
#ifndef hifi_AssetUtils_h
#define hifi_AssetUtils_h
#include "NLPacketList.h"
using MessageID = uint32_t;
using DataOffset = int64_t;
const size_t HASH_HEX_LENGTH = 64;
const uint64_t MAX_UPLOAD_SIZE = 1000 * 1000 * 1000; // 1GB
enum AssetServerError : uint8_t {
NO_ERROR = 0,
ASSET_NOT_FOUND,
INVALID_BYTE_RANGE,
ASSET_TOO_LARGE,
};
#endif

View file

@ -28,6 +28,8 @@ Assignment::Type Assignment::typeForNodeType(NodeType_t nodeType) {
return Assignment::AgentType; return Assignment::AgentType;
case NodeType::EntityServer: case NodeType::EntityServer:
return Assignment::EntityServerType; return Assignment::EntityServerType;
case NodeType::AssetServer:
return Assignment::AssetServerType;
default: default:
return Assignment::AllTypes; return Assignment::AllTypes;
} }

View file

@ -29,9 +29,9 @@ public:
AudioMixerType = 0, AudioMixerType = 0,
AvatarMixerType = 1, AvatarMixerType = 1,
AgentType = 2, AgentType = 2,
UNUSED_0 = 3, AssetServerType = 3,
UNUSED_1 = 4, UNUSED_0 = 4,
UNUSED_2 = 5, UNUSED_1 = 5,
EntityServerType = 6, EntityServerType = 6,
AllTypes = 7 AllTypes = 7
}; };

View file

@ -0,0 +1,28 @@
//
// FileResourceRequest.cpp
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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 "FileResourceRequest.h"
#include <QFile>
void FileResourceRequest::doSend() {
QString filename = _url.toLocalFile();
QFile file(filename);
_state = FINISHED;
if (file.open(QFile::ReadOnly)) {
_data = file.readAll();
_result = ResourceRequest::SUCCESS;
emit finished();
} else {
_result = ResourceRequest::ERROR;
emit finished();
}
}

View file

@ -0,0 +1,28 @@
//
// FileResourceRequest.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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
//
#ifndef hifi_FileResourceRequest_h
#define hifi_FileResourceRequest_h
#include <QUrl>
#include "ResourceRequest.h"
class FileResourceRequest : public ResourceRequest {
Q_OBJECT
public:
FileResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { }
protected:
virtual void doSend() override;
};
#endif

View file

@ -0,0 +1,96 @@
//
// HTTPResourceRequest.cpp
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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 "HTTPResourceRequest.h"
#include <QFile>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <SharedUtil.h>
#include "NetworkAccessManager.h"
#include "NetworkLogging.h"
HTTPResourceRequest::~HTTPResourceRequest() {
if (_reply) {
_reply->disconnect(this);
_reply->deleteLater();
_reply = nullptr;
}
}
void HTTPResourceRequest::doSend() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(_url);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
if (_cacheEnabled) {
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
} else {
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
}
_reply = networkAccessManager.get(networkRequest);
connect(_reply, &QNetworkReply::finished, this, &HTTPResourceRequest::onRequestFinished);
static const int TIMEOUT_MS = 10000;
connect(&_sendTimer, &QTimer::timeout, this, &HTTPResourceRequest::onTimeout);
_sendTimer.setSingleShot(true);
_sendTimer.start(TIMEOUT_MS);
}
void HTTPResourceRequest::onRequestFinished() {
Q_ASSERT(_state == IN_PROGRESS);
Q_ASSERT(_reply);
_state = FINISHED;
auto error = _reply->error();
if (error == QNetworkReply::NoError) {
_data = _reply->readAll();
_loadedFromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
_result = ResourceRequest::SUCCESS;
emit finished();
} else if (error == QNetworkReply::TimeoutError) {
_result = ResourceRequest::TIMEOUT;
emit finished();
} else {
_result = ResourceRequest::ERROR;
emit finished();
}
_reply->deleteLater();
_reply = nullptr;
}
void HTTPResourceRequest::onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
if (_state == IN_PROGRESS) {
// We've received data, so reset the timer
_sendTimer.start();
}
emit progress(bytesReceived, bytesTotal);
}
void HTTPResourceRequest::onTimeout() {
Q_ASSERT(_state != UNSENT);
if (_state == IN_PROGRESS) {
qCDebug(networking) << "Timed out loading " << _url;
_reply->abort();
_state = FINISHED;
_result = TIMEOUT;
emit finished();
}
}

View file

@ -0,0 +1,41 @@
//
// HTTPResourceRequest.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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
//
#ifndef hifi_HTTPResourceRequest_h
#define hifi_HTTPResourceRequest_h
#include <QNetworkReply>
#include <QUrl>
#include <QTimer>
#include "ResourceRequest.h"
class HTTPResourceRequest : public ResourceRequest {
Q_OBJECT
public:
~HTTPResourceRequest();
HTTPResourceRequest(QObject* parent, const QUrl& url) : ResourceRequest(parent, url) { }
protected:
virtual void doSend() override;
private slots:
void onTimeout();
void onDownloadProgress(qint64 bytesReceived, qint64 bytesTotal);
void onRequestFinished();
private:
QTimer _sendTimer;
QNetworkReply* _reply { nullptr };
};
#endif

View file

@ -32,6 +32,7 @@
#include "HifiSockAddr.h" #include "HifiSockAddr.h"
#include "UUID.h" #include "UUID.h"
#include "NetworkLogging.h" #include "NetworkLogging.h"
#include "udt/Packet.h"
const char SOLO_NODE_TYPES[2] = { const char SOLO_NODE_TYPES[2] = {
NodeType::AvatarMixer, NodeType::AvatarMixer,
@ -344,6 +345,19 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList,
return _nodeSocket.writePacketList(std::move(packetList), sockAddr); return _nodeSocket.writePacketList(std::move(packetList), sockAddr);
} }
qint64 LimitedNodeList::sendPacketList(std::unique_ptr<NLPacketList> packetList, const Node& destinationNode) {
// close the last packet in the list
packetList->closeCurrentPacket();
for (std::unique_ptr<udt::Packet>& packet : packetList->_packets) {
NLPacket* nlPacket = static_cast<NLPacket*>(packet.get());
collectPacketStats(*nlPacket);
fillPacketHeader(*nlPacket, destinationNode.getConnectionSecret());
}
return _nodeSocket.writePacketList(std::move(packetList), *destinationNode.getActiveSocket());
}
qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode, qint64 LimitedNodeList::sendPacket(std::unique_ptr<NLPacket> packet, const Node& destinationNode,
const HifiSockAddr& overridenSockAddr) { const HifiSockAddr& overridenSockAddr) {
// use the node's active socket as the destination socket if there is no overriden socket address // use the node's active socket as the destination socket if there is no overriden socket address
@ -428,6 +442,10 @@ void LimitedNodeList::handleNodeKill(const SharedNodePointer& node) {
qCDebug(networking) << "Killed" << *node; qCDebug(networking) << "Killed" << *node;
node->stopPingTimer(); node->stopPingTimer();
emit nodeKilled(node); emit nodeKilled(node);
if (auto activeSocket = node->getActiveSocket()) {
_nodeSocket.cleanupConnection(*activeSocket);
}
} }
SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType,
@ -531,7 +549,7 @@ unsigned int LimitedNodeList::broadcastToNodes(std::unique_ptr<NLPacket> packet,
return n; return n;
} }
SharedNodePointer LimitedNodeList::soloNodeOfType(char nodeType) { SharedNodePointer LimitedNodeList::soloNodeOfType(NodeType_t nodeType) {
return nodeMatchingPredicate([&](const SharedNodePointer& node){ return nodeMatchingPredicate([&](const SharedNodePointer& node){
return node->getType() == nodeType; return node->getType() == nodeType;
}); });

View file

@ -128,6 +128,7 @@ public:
qint64 sendPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr, qint64 sendPacketList(NLPacketList& packetList, const HifiSockAddr& sockAddr,
const QUuid& connectionSecret = QUuid()); const QUuid& connectionSecret = QUuid());
qint64 sendPacketList(std::unique_ptr<NLPacketList> packetList, const HifiSockAddr& sockAddr); qint64 sendPacketList(std::unique_ptr<NLPacketList> packetList, const HifiSockAddr& sockAddr);
qint64 sendPacketList(std::unique_ptr<NLPacketList> packetList, const Node& destinationNode);
void (*linkedDataCreateCallback)(Node *); void (*linkedDataCreateCallback)(Node *);
@ -150,7 +151,7 @@ public:
int updateNodeWithDataFromPacket(QSharedPointer<NLPacket> packet, SharedNodePointer matchingNode); int updateNodeWithDataFromPacket(QSharedPointer<NLPacket> packet, SharedNodePointer matchingNode);
unsigned int broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes); unsigned int broadcastToNodes(std::unique_ptr<NLPacket> packet, const NodeSet& destinationNodeTypes);
SharedNodePointer soloNodeOfType(char nodeType); SharedNodePointer soloNodeOfType(NodeType_t nodeType);
void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void getPacketStats(float &packetsPerSecond, float &bytesPerSecond);
void resetPacketStats(); void resetPacketStats();

View file

@ -22,6 +22,7 @@ namespace NodeType {
const NodeType_t Agent = 'I'; const NodeType_t Agent = 'I';
const NodeType_t AudioMixer = 'M'; const NodeType_t AudioMixer = 'M';
const NodeType_t AvatarMixer = 'W'; const NodeType_t AvatarMixer = 'W';
const NodeType_t AssetServer = 'A';
const NodeType_t Unassigned = 1; const NodeType_t Unassigned = 1;
void init(); void init();

View file

@ -19,8 +19,10 @@
#include "NodeList.h" #include "NodeList.h"
#include "SharedUtil.h" #include "SharedUtil.h"
Q_DECLARE_METATYPE(QSharedPointer<NLPacketList>);
PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) { PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) {
qRegisterMetaType<QSharedPointer<NLPacket>>(); qRegisterMetaType<QSharedPointer<NLPacket>>();
qRegisterMetaType<QSharedPointer<NLPacketList>>();
} }
bool PacketReceiver::registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot) { bool PacketReceiver::registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot) {
@ -206,12 +208,13 @@ void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object,
void PacketReceiver::unregisterListener(QObject* listener) { void PacketReceiver::unregisterListener(QObject* listener) {
Q_ASSERT_X(listener, "PacketReceiver::unregisterListener", "No listener to unregister"); Q_ASSERT_X(listener, "PacketReceiver::unregisterListener", "No listener to unregister");
QMutexLocker packetListenerLocker(&_packetListenerLock); {
std::remove_if(std::begin(_packetListenerMap), std::end(_packetListenerMap), QMutexLocker packetListenerLocker(&_packetListenerLock);
[&listener](const ObjectMethodPair& pair) { std::remove_if(std::begin(_packetListenerMap), std::end(_packetListenerMap),
return pair.first == listener; [&listener](const ObjectMethodPair& pair) {
}); return pair.first == listener;
packetListenerLocker.unlock(); });
}
QMutexLocker directConnectSetLocker(&_directConnectSetMutex); QMutexLocker directConnectSetLocker(&_directConnectSetMutex);
_directlyConnectedObjects.remove(listener); _directlyConnectedObjects.remove(listener);
@ -454,7 +457,6 @@ void PacketReceiver::handleVerifiedPacket(std::unique_ptr<udt::Packet> packet) {
// if it exists, remove the listener from _directlyConnectedObjects // if it exists, remove the listener from _directlyConnectedObjects
QMutexLocker locker(&_directConnectSetMutex); QMutexLocker locker(&_directConnectSetMutex);
_directlyConnectedObjects.remove(listener.first); _directlyConnectedObjects.remove(listener.first);
locker.unlock();
} }
} else if (it == _packetListenerMap.end()) { } else if (it == _packetListenerMap.end()) {
@ -463,7 +465,4 @@ void PacketReceiver::handleVerifiedPacket(std::unique_ptr<udt::Packet> packet) {
// insert a dummy listener so we don't print this again // insert a dummy listener so we don't print this again
_packetListenerMap.insert(nlPacket->getType(), { nullptr, QMetaMethod() }); _packetListenerMap.insert(nlPacket->getType(), { nullptr, QMetaMethod() });
} }
packetListenerLocker.unlock();
} }

View file

@ -155,11 +155,17 @@ void ResourceCache::clearUnusedResource() {
void ResourceCache::attemptRequest(Resource* resource) { void ResourceCache::attemptRequest(Resource* resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>(); auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
if (_requestLimit <= 0) { if (_requestLimit <= 0) {
qDebug() << "REQUEST LIMIT REACHED (" << _requestLimit << "), queueing: " << resource->getURL();
// wait until a slot becomes available // wait until a slot becomes available
sharedItems->_pendingRequests.append(resource); sharedItems->_pendingRequests.append(resource);
return; return;
} }
_requestLimit--; qDebug() << "-- Decreasing limit for : " << resource->getURL();
// Disable request limiting for ATP
if (resource->getURL() != URL_SCHEME_ATP) {
_requestLimit--;
}
sharedItems->_loadingRequests.append(resource); sharedItems->_loadingRequests.append(resource);
resource->makeRequest(); resource->makeRequest();
} }
@ -168,7 +174,10 @@ void ResourceCache::requestCompleted(Resource* resource) {
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>(); auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
sharedItems->_loadingRequests.removeOne(resource); sharedItems->_loadingRequests.removeOne(resource);
_requestLimit++; qDebug() << "++ Increasing limit after finished: " << resource->getURL();
if (resource->getURL() != URL_SCHEME_ATP) {
_requestLimit++;
}
// look for the highest priority pending request // look for the highest priority pending request
int highestIndex = -1; int highestIndex = -1;
@ -196,24 +205,22 @@ int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT;
Resource::Resource(const QUrl& url, bool delayLoad) : Resource::Resource(const QUrl& url, bool delayLoad) :
_url(url), _url(url),
_request(url) { _activeUrl(url),
_request(nullptr) {
init(); init();
_request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
// start loading immediately unless instructed otherwise // start loading immediately unless instructed otherwise
if (!(_startedLoading || delayLoad)) { if (!(_startedLoading || delayLoad)) {
attemptRequest(); QTimer::singleShot(0, this, &Resource::ensureLoading);
} }
} }
Resource::~Resource() { Resource::~Resource() {
if (_reply) { if (_request) {
ResourceCache::requestCompleted(this); ResourceCache::requestCompleted(this);
delete _reply; _request->deleteLater();
_reply = nullptr; _request = nullptr;
} }
} }
@ -259,21 +266,17 @@ float Resource::getLoadPriority() {
} }
void Resource::refresh() { void Resource::refresh() {
if (_reply && !(_loaded || _failedToLoad)) { if (_request && !(_loaded || _failedToLoad)) {
return; return;
} }
if (_reply) { if (_request) {
_request->disconnect(this);
_request->deleteLater();
_request = nullptr;
ResourceCache::requestCompleted(this); ResourceCache::requestCompleted(this);
_reply->disconnect(this);
_replyTimer->disconnect(this);
_reply->deleteLater();
_reply = nullptr;
_replyTimer->deleteLater();
_replyTimer = nullptr;
} }
init(); init();
_request.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
ensureLoading(); ensureLoading();
emit onRefresh(); emit onRefresh();
} }
@ -303,6 +306,7 @@ void Resource::init() {
_failedToLoad = false; _failedToLoad = false;
_loaded = false; _loaded = false;
_attempts = 0; _attempts = 0;
_activeUrl = _url;
if (_url.isEmpty()) { if (_url.isEmpty()) {
_startedLoading = _loaded = true; _startedLoading = _loaded = true;
@ -318,6 +322,7 @@ void Resource::attemptRequest() {
} }
void Resource::finishedLoading(bool success) { void Resource::finishedLoading(bool success) {
qDebug() << "Finished loading: " << _url;
if (success) { if (success) {
_loaded = true; _loaded = true;
} else { } else {
@ -330,94 +335,91 @@ void Resource::reinsert() {
_cache->_resources.insert(_url, _self); _cache->_resources.insert(_url, _self);
} }
static const int REPLY_TIMEOUT_MS = 5000;
void Resource::handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal) {
_bytesReceived = bytesReceived;
_bytesTotal = bytesTotal;
_replyTimer->start(REPLY_TIMEOUT_MS);
}
void Resource::handleReplyError() {
handleReplyErrorInternal(_reply->error());
}
void Resource::handleReplyTimeout() {
handleReplyErrorInternal(QNetworkReply::TimeoutError);
}
void Resource::makeRequest() { void Resource::makeRequest() {
_reply = NetworkAccessManager::getInstance().get(_request); Q_ASSERT(!_request);
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64))); _request = ResourceManager::createResourceRequest(this, _activeUrl);
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));
connect(_reply, SIGNAL(finished()), SLOT(handleReplyFinished())); if (!_request) {
qDebug() << "Failed to get request for " << _url;
ResourceCache::requestCompleted(this);
finishedLoading(false);
return;
}
qDebug() << "Starting request for: " << _url;
connect(_request, &ResourceRequest::progress, this, &Resource::handleDownloadProgress);
connect(_request, &ResourceRequest::finished, this, &Resource::handleReplyFinished);
_replyTimer = new QTimer(this);
connect(_replyTimer, SIGNAL(timeout()), SLOT(handleReplyTimeout()));
_replyTimer->setSingleShot(true);
_replyTimer->start(REPLY_TIMEOUT_MS);
_bytesReceived = _bytesTotal = 0; _bytesReceived = _bytesTotal = 0;
_request->send();
} }
void Resource::handleReplyErrorInternal(QNetworkReply::NetworkError error) { void Resource::handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal) {
_bytesReceived = bytesReceived;
_reply->disconnect(this); _bytesTotal = bytesTotal;
_replyTimer->disconnect(this);
_reply->deleteLater();
_reply = nullptr;
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
// retry for certain types of failures
switch (error) {
case QNetworkReply::RemoteHostClosedError:
case QNetworkReply::TimeoutError:
case QNetworkReply::TemporaryNetworkFailureError:
case QNetworkReply::ProxyConnectionClosedError:
case QNetworkReply::ProxyTimeoutError:
case QNetworkReply::UnknownNetworkError:
case QNetworkReply::UnknownProxyError:
case QNetworkReply::UnknownContentError:
case QNetworkReply::ProtocolFailure: {
// retry with increasing delays
const int MAX_ATTEMPTS = 8;
const int BASE_DELAY_MS = 1000;
if (++_attempts < MAX_ATTEMPTS) {
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest()));
qCWarning(networking) << "error downloading url =" << _url.toDisplayString() << ", error =" << error << ", retrying (" << _attempts << "/" << MAX_ATTEMPTS << ")";
return;
}
// fall through to final failure
}
default:
qCCritical(networking) << "error downloading, url =" << _url.toDisplayString() << ", error =" << error;
emit failed(error);
finishedLoading(false);
break;
}
} }
void Resource::handleReplyFinished() { void Resource::handleReplyFinished() {
Q_ASSERT(_request);
bool fromCache = _reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool(); auto result = _request->getResult();
qCDebug(networking) << "success downloading url =" << _url.toDisplayString() << (fromCache ? "from cache" : ""); if (result == ResourceRequest::SUCCESS) {
_data = _request->getData();
qDebug() << "Request finished for " << _url << ", " << _activeUrl;
_reply->disconnect(this); _request->disconnect(this);
_replyTimer->disconnect(this); _request->deleteLater();
QNetworkReply* reply = _reply; _request = nullptr;
_reply = nullptr;
_replyTimer->deleteLater();
_replyTimer = nullptr;
ResourceCache::requestCompleted(this);
finishedLoading(true); ResourceCache::requestCompleted(this);
emit loaded(*reply);
downloadFinished(reply); emit loaded(_data);
downloadFinished(_data);
} else {
_request->disconnect(this);
_request->deleteLater();
_request = nullptr;
if (result == ResourceRequest::Result::TIMEOUT) {
qDebug() << "Timed out loading" << _url << "received" << _bytesReceived << "total" << _bytesTotal;
} else {
qDebug() << "Error loading " << _url;
}
bool retry = false;
switch (result) {
case ResourceRequest::Result::TIMEOUT:
case ResourceRequest::Result::ERROR: {
// retry with increasing delays
const int MAX_ATTEMPTS = 8;
const int BASE_DELAY_MS = 1000;
if (++_attempts < MAX_ATTEMPTS) {
QTimer::singleShot(BASE_DELAY_MS * (int)pow(2.0, _attempts), this, SLOT(attemptRequest()));
retry = true;
break;
}
// fall through to final failure
}
default:
finishedLoading(false);
break;
}
auto error = result == ResourceRequest::TIMEOUT ? QNetworkReply::TimeoutError : QNetworkReply::UnknownNetworkError;
emit failed(error);
if (!retry) {
ResourceCache::requestCompleted(this);
}
}
} }
void Resource::downloadFinished(const QByteArray& data) {
void Resource::downloadFinished(QNetworkReply* reply) {
; ;
} }

View file

@ -26,6 +26,8 @@
#include <DependencyManager.h> #include <DependencyManager.h>
#include "ResourceManager.h"
class QNetworkReply; class QNetworkReply;
class QTimer; class QTimer;
@ -102,7 +104,7 @@ protected:
void reserveUnusedResource(qint64 resourceSize); void reserveUnusedResource(qint64 resourceSize);
void clearUnusedResource(); void clearUnusedResource();
static void attemptRequest(Resource* resource); Q_INVOKABLE static void attemptRequest(Resource* resource);
static void requestCompleted(Resource* resource); static void requestCompleted(Resource* resource);
private: private:
@ -174,7 +176,7 @@ public:
signals: signals:
/// Fired when the resource has been loaded. /// Fired when the resource has been loaded.
void loaded(QNetworkReply& request); void loaded(const QByteArray& request);
/// Fired when resource failed to load. /// Fired when resource failed to load.
void failed(QNetworkReply::NetworkError error); void failed(QNetworkReply::NetworkError error);
@ -189,7 +191,7 @@ protected:
virtual void init(); virtual void init();
/// Called when the download has finished. The recipient should delete the reply when done with it. /// Called when the download has finished. The recipient should delete the reply when done with it.
virtual void downloadFinished(QNetworkReply* reply); virtual void downloadFinished(const QByteArray& data);
/// Should be called by subclasses when all the loading that will be done has been done. /// Should be called by subclasses when all the loading that will be done has been done.
Q_INVOKABLE void finishedLoading(bool success); Q_INVOKABLE void finishedLoading(bool success);
@ -198,31 +200,29 @@ protected:
virtual void reinsert(); virtual void reinsert();
QUrl _url; QUrl _url;
QNetworkRequest _request; QUrl _activeUrl;
bool _startedLoading = false; bool _startedLoading = false;
bool _failedToLoad = false; bool _failedToLoad = false;
bool _loaded = false; bool _loaded = false;
QHash<QPointer<QObject>, float> _loadPriorities; QHash<QPointer<QObject>, float> _loadPriorities;
QWeakPointer<Resource> _self; QWeakPointer<Resource> _self;
QPointer<ResourceCache> _cache; QPointer<ResourceCache> _cache;
QByteArray _data;
private slots: private slots:
void handleDownloadProgress(qint64 bytesReceived, qint64 bytesTotal); void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
void handleReplyError();
void handleReplyFinished(); void handleReplyFinished();
void handleReplyTimeout();
private: private:
void setLRUKey(int lruKey) { _lruKey = lruKey; } void setLRUKey(int lruKey) { _lruKey = lruKey; }
void makeRequest(); void makeRequest();
void retry();
void handleReplyErrorInternal(QNetworkReply::NetworkError error);
friend class ResourceCache; friend class ResourceCache;
ResourceRequest* _request = nullptr;
int _lruKey = 0; int _lruKey = 0;
QNetworkReply* _reply = nullptr;
QTimer* _replyTimer = nullptr; QTimer* _replyTimer = nullptr;
qint64 _bytesReceived = 0; qint64 _bytesReceived = 0;
qint64 _bytesTotal = 0; qint64 _bytesTotal = 0;

View file

@ -0,0 +1,33 @@
//
// ResourceManager.cpp
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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 "ResourceManager.h"
#include "AssetResourceRequest.h"
#include "FileResourceRequest.h"
#include "HTTPResourceRequest.h"
#include <SharedUtil.h>
ResourceRequest* ResourceManager::createResourceRequest(QObject* parent, const QUrl& url) {
auto scheme = url.scheme();
if (scheme == URL_SCHEME_FILE) {
return new FileResourceRequest(parent, url);
} else if (scheme == URL_SCHEME_HTTP || scheme == URL_SCHEME_HTTPS || scheme == URL_SCHEME_FTP) {
return new HTTPResourceRequest(parent, url);
} else if (scheme == URL_SCHEME_ATP) {
return new AssetResourceRequest(parent, url);
}
qDebug() << "Failed to load: " << url.url();
return nullptr;
}

View file

@ -0,0 +1,30 @@
//
// ResourceManager.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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
//
#ifndef hifi_ResourceManager_h
#define hifi_ResourceManager_h
#include <functional>
#include "ResourceRequest.h"
const QString URL_SCHEME_FILE = "file";
const QString URL_SCHEME_HTTP = "http";
const QString URL_SCHEME_HTTPS = "https";
const QString URL_SCHEME_FTP = "ftp";
const QString URL_SCHEME_ATP = "atp";
class ResourceManager {
public:
static ResourceRequest* createResourceRequest(QObject* parent, const QUrl& url);
};
#endif

View file

@ -0,0 +1,24 @@
//
// ResourceRequest.cpp
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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 "ResourceRequest.h"
ResourceRequest::ResourceRequest(QObject* parent, const QUrl& url) :
QObject(parent),
_url(url) {
}
void ResourceRequest::send() {
Q_ASSERT(_state == UNSENT);
_state = IN_PROGRESS;
doSend();
}

View file

@ -0,0 +1,60 @@
//
// ResourceRequest.h
// libraries/networking/src
//
// Created by Ryan Huffman on 2015/07/23
// 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
//
#ifndef hifi_ResourceRequest_h
#define hifi_ResourceRequest_h
#include <QObject>
#include <QUrl>
class ResourceRequest : public QObject {
Q_OBJECT
public:
ResourceRequest(QObject* parent, const QUrl& url);
enum State {
UNSENT = 0,
IN_PROGRESS,
FINISHED
};
enum Result {
SUCCESS,
ERROR,
TIMEOUT,
NOT_FOUND
};
void send();
QByteArray getData() { return _data; }
State getState() const { return _state; }
Result getResult() const { return _result; }
QUrl getUrl() const { return _url; }
bool loadedFromCache() const { return _loadedFromCache; }
void setCacheEnabled(bool value) { _cacheEnabled = value; }
signals:
void progress(uint64_t bytesReceived, uint64_t bytesTotal);
void finished();
protected:
virtual void doSend() = 0;
QUrl _url;
State _state { UNSENT };
Result _result;
QByteArray _data;
bool _cacheEnabled { true };
bool _loadedFromCache { false };
};
#endif

View file

@ -76,6 +76,7 @@ SendQueue& Connection::getSendQueue() {
QObject::connect(_sendQueue.get(), &SendQueue::packetSent, this, &Connection::packetSent); QObject::connect(_sendQueue.get(), &SendQueue::packetSent, this, &Connection::packetSent);
QObject::connect(_sendQueue.get(), &SendQueue::packetSent, this, &Connection::recordSentPackets); QObject::connect(_sendQueue.get(), &SendQueue::packetSent, this, &Connection::recordSentPackets);
QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission); QObject::connect(_sendQueue.get(), &SendQueue::packetRetransmitted, this, &Connection::recordRetransmission);
QObject::connect(_sendQueue.get(), &SendQueue::queueInactive, this, &Connection::queueInactive);
// set defaults on the send queue from our congestion control object // set defaults on the send queue from our congestion control object
_sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod); _sendQueue->setPacketSendPeriod(_congestionControl->_packetSendPeriod);
@ -85,6 +86,10 @@ SendQueue& Connection::getSendQueue() {
return *_sendQueue; return *_sendQueue;
} }
void Connection::queueInactive() {
emit connectionInactive(_destination);
}
void Connection::sendReliablePacket(std::unique_ptr<Packet> packet) { void Connection::sendReliablePacket(std::unique_ptr<Packet> packet) {
Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably."); Q_ASSERT_X(packet->isReliable(), "Connection::send", "Trying to send an unreliable packet reliably.");
getSendQueue().queuePacket(std::move(packet)); getSendQueue().queuePacket(std::move(packet));

View file

@ -73,10 +73,12 @@ public:
signals: signals:
void packetSent(); void packetSent();
void connectionInactive(HifiSockAddr sockAdrr);
private slots: private slots:
void recordSentPackets(int payload, int total); void recordSentPackets(int payload, int total);
void recordRetransmission(); void recordRetransmission();
void queueInactive();
private: private:
void sendACK(bool wasCausedBySyncTimeout = true); void sendACK(bool wasCausedBySyncTimeout = true);

View file

@ -93,6 +93,8 @@ Packet::Packet(Packet&& other) :
_isReliable = other._isReliable; _isReliable = other._isReliable;
_isPartOfMessage = other._isPartOfMessage; _isPartOfMessage = other._isPartOfMessage;
_sequenceNumber = other._sequenceNumber; _sequenceNumber = other._sequenceNumber;
_packetPosition = other._packetPosition;
_messageNumber = other._messageNumber;
} }
Packet& Packet::operator=(Packet&& other) { Packet& Packet::operator=(Packet&& other) {
@ -101,6 +103,8 @@ Packet& Packet::operator=(Packet&& other) {
_isReliable = other._isReliable; _isReliable = other._isReliable;
_isPartOfMessage = other._isPartOfMessage; _isPartOfMessage = other._isPartOfMessage;
_sequenceNumber = other._sequenceNumber; _sequenceNumber = other._sequenceNumber;
_packetPosition = other._packetPosition;
_messageNumber = other._messageNumber;
return *this; return *this;
} }

View file

@ -73,7 +73,13 @@ enum class PacketType : uint8_t {
EntityEdit, EntityEdit,
DomainServerConnectionToken, DomainServerConnectionToken,
DomainSettingsRequest, DomainSettingsRequest,
DomainSettings DomainSettings,
AssetGet,
AssetGetReply,
AssetUpload,
AssetUploadReply,
AssetGetInfo,
AssetGetInfoReply
}; };
const int NUM_BYTES_MD5_HASH = 16; const int NUM_BYTES_MD5_HASH = 16;

View file

@ -82,7 +82,7 @@ std::unique_ptr<Packet> PacketList::createPacket() {
std::unique_ptr<Packet> PacketList::createPacketWithExtendedHeader() { std::unique_ptr<Packet> PacketList::createPacketWithExtendedHeader() {
// use the static create method to create a new packet // use the static create method to create a new packet
auto packet = createPacket(); auto packet = createPacket();
if (!_extendedHeader.isEmpty()) { if (!_extendedHeader.isEmpty()) {
// add the extended header to the front of the packet // add the extended header to the front of the packet
if (packet->write(_extendedHeader) == -1) { if (packet->write(_extendedHeader) == -1) {
@ -90,7 +90,7 @@ std::unique_ptr<Packet> PacketList::createPacketWithExtendedHeader() {
<< "- make sure that _extendedHeader is not larger than the payload capacity."; << "- make sure that _extendedHeader is not larger than the payload capacity.";
} }
} }
return packet; return packet;
} }
@ -112,83 +112,91 @@ QByteArray PacketList::getMessage() {
} }
qint64 PacketList::writeData(const char* data, qint64 maxSize) { qint64 PacketList::writeData(const char* data, qint64 maxSize) {
if (!_currentPacket) { auto sizeRemaining = maxSize;
// we don't have a current packet, time to set one up
_currentPacket = createPacketWithExtendedHeader(); while (sizeRemaining > 0) {
} if (!_currentPacket) {
// we don't have a current packet, time to set one up
// check if this block of data can fit into the currentPacket _currentPacket = createPacketWithExtendedHeader();
if (maxSize <= _currentPacket->bytesAvailableForWrite()) { }
// it fits, just write it to the current packet
_currentPacket->write(data, maxSize); // check if this block of data can fit into the currentPacket
if (sizeRemaining <= _currentPacket->bytesAvailableForWrite()) {
// return the number of bytes written // it fits, just write it to the current packet
return maxSize; _currentPacket->write(data, sizeRemaining);
} else {
// it does not fit - this may need to be in the next packet sizeRemaining = 0;
if (!_isOrdered) {
auto newPacket = createPacketWithExtendedHeader();
if (_segmentStartIndex >= 0) {
// We in the process of writing a segment for an unordered PacketList.
// We need to try and pull the first part of the segment out to our new packet
// check now to see if this is an unsupported write
int numBytesToEnd = _currentPacket->bytesAvailableForWrite();
if ((newPacket->size() - numBytesToEnd) < maxSize) {
// this is an unsupported case - the segment is bigger than the size of an individual packet
// but the PacketList is not going to be sent ordered
qDebug() << "Error in PacketList::writeData - attempted to write a segment to an unordered packet that is"
<< "larger than the payload size.";
Q_ASSERT(false);
}
int segmentSize = _currentPacket->pos() - _segmentStartIndex;
// copy from currentPacket where the segment started to the beginning of the newPacket
newPacket->write(_currentPacket->getPayload() + _segmentStartIndex, segmentSize);
// the current segment now starts at the beginning of the new packet
_segmentStartIndex = _extendedHeader.size();
// shrink the current payload to the actual size of the packet
_currentPacket->setPayloadSize(_segmentStartIndex);
}
// move the current packet to our list of packets
_packets.push_back(std::move(_currentPacket));
// write the data to the newPacket
newPacket->write(data, maxSize);
// swap our current packet with the new packet
_currentPacket.swap(newPacket);
// return the number of bytes written to the new packet
return maxSize;
} else { } else {
// we're an ordered PacketList - let's fit what we can into the current packet and then put the leftover // it does not fit - this may need to be in the next packet
// into a new packet
if (!_isOrdered) {
int numBytesToEnd = _currentPacket->bytesAvailableForWrite(); auto newPacket = createPacketWithExtendedHeader();
_currentPacket->write(data, numBytesToEnd);
if (_segmentStartIndex >= 0) {
// move the current packet to our list of packets // We in the process of writing a segment for an unordered PacketList.
_packets.push_back(std::move(_currentPacket)); // We need to try and pull the first part of the segment out to our new packet
// recursively call our writeData method for the remaining data to write to a new packet // check now to see if this is an unsupported write
return numBytesToEnd + writeData(data + numBytesToEnd, maxSize - numBytesToEnd); int numBytesToEnd = _currentPacket->bytesAvailableForWrite();
if ((newPacket->size() - numBytesToEnd) < sizeRemaining) {
// this is an unsupported case - the segment is bigger than the size of an individual packet
// but the PacketList is not going to be sent ordered
qDebug() << "Error in PacketList::writeData - attempted to write a segment to an unordered packet that is"
<< "larger than the payload size.";
Q_ASSERT(false);
}
int segmentSize = _currentPacket->pos() - _segmentStartIndex;
// copy from currentPacket where the segment started to the beginning of the newPacket
newPacket->write(_currentPacket->getPayload() + _segmentStartIndex, segmentSize);
// the current segment now starts at the beginning of the new packet
_segmentStartIndex = _extendedHeader.size();
// shrink the current payload to the actual size of the packet
_currentPacket->setPayloadSize(_segmentStartIndex);
}
// move the current packet to our list of packets
_packets.push_back(std::move(_currentPacket));
// write the data to the newPacket
newPacket->write(data, sizeRemaining);
// swap our current packet with the new packet
_currentPacket.swap(newPacket);
// We've written all of the data, so set sizeRemaining to 0
sizeRemaining = 0;
} else {
// we're an ordered PacketList - let's fit what we can into the current packet and then put the leftover
// into a new packet
int numBytesToEnd = _currentPacket->bytesAvailableForWrite();
_currentPacket->write(data, numBytesToEnd);
// Remove number of bytes written from sizeRemaining
sizeRemaining -= numBytesToEnd;
// Move the data pointer forward
data += numBytesToEnd;
// move the current packet to our list of packets
_packets.push_back(std::move(_currentPacket));
}
} }
} }
return maxSize;
} }
void PacketList::closeCurrentPacket(bool shouldSendEmpty) { void PacketList::closeCurrentPacket(bool shouldSendEmpty) {
if (shouldSendEmpty && !_currentPacket) { if (shouldSendEmpty && !_currentPacket) {
_currentPacket = createPacketWithExtendedHeader(); _currentPacket = createPacketWithExtendedHeader();
} }
if (_currentPacket) { if (_currentPacket) {
// move the current packet to our list of packets // move the current packet to our list of packets
_packets.push_back(std::move(_currentPacket)); _packets.push_back(std::move(_currentPacket));

View file

@ -12,6 +12,7 @@
#include "SendQueue.h" #include "SendQueue.h"
#include <algorithm> #include <algorithm>
#include <thread>
#include <QtCore/QCoreApplication> #include <QtCore/QCoreApplication>
#include <QtCore/QThread> #include <QtCore/QThread>
@ -24,7 +25,27 @@
#include "Socket.h" #include "Socket.h"
using namespace udt; using namespace udt;
using namespace std::chrono;
class DoubleLock {
public:
DoubleLock(std::mutex& mutex1, std::mutex& mutex2) : _mutex1(mutex1), _mutex2(mutex2) { }
DoubleLock(const DoubleLock&) = delete;
DoubleLock& operator=(const DoubleLock&) = delete;
// Either locks all the mutexes or none of them
bool try_lock() { return (std::try_lock(_mutex1, _mutex2) == -1); }
// Locks all the mutexes
void lock() { std::lock(_mutex1, _mutex2); }
// Undefined behavior if not locked
void unlock() { _mutex1.unlock(); _mutex2.unlock(); }
private:
std::mutex& _mutex1;
std::mutex& _mutex2;
};
std::unique_ptr<SendQueue> SendQueue::create(Socket* socket, HifiSockAddr destination) { std::unique_ptr<SendQueue> SendQueue::create(Socket* socket, HifiSockAddr destination) {
auto queue = std::unique_ptr<SendQueue>(new SendQueue(socket, destination)); auto queue = std::unique_ptr<SendQueue>(new SendQueue(socket, destination));
@ -200,8 +221,8 @@ void SendQueue::sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket,
// Insert the packet we have just sent in the sent list // Insert the packet we have just sent in the sent list
QWriteLocker locker(&_sentLock); QWriteLocker locker(&_sentLock);
_sentPackets[newPacket->getSequenceNumber()].swap(newPacket); _sentPackets[newPacket->getSequenceNumber()].swap(newPacket);
Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list");
} }
Q_ASSERT_X(!newPacket, "SendQueue::sendNewPacketAndAddToSentList()", "Overriden packet in sent list");
emit packetSent(packetSize, payloadSize); emit packetSent(packetSize, payloadSize);
} }
@ -210,8 +231,8 @@ void SendQueue::run() {
_isRunning = true; _isRunning = true;
while (_isRunning) { while (_isRunning) {
// Record timing // Record how long the loop takes to execute
_lastSendTimestamp = high_resolution_clock::now(); auto loopStartTimestamp = high_resolution_clock::now();
std::unique_lock<std::mutex> handshakeLock { _handshakeMutex }; std::unique_lock<std::mutex> handshakeLock { _handshakeMutex };
@ -222,12 +243,13 @@ void SendQueue::run() {
// hold the time of last send in a static // hold the time of last send in a static
static auto lastSendHandshake = high_resolution_clock::time_point(); static auto lastSendHandshake = high_resolution_clock::time_point();
static const int HANDSHAKE_RESEND_INTERVAL_MS = 100; static const auto HANDSHAKE_RESEND_INTERVAL_MS = std::chrono::milliseconds(100);
// calculation the duration since the last handshake send // calculation the duration since the last handshake send
auto sinceLastHandshake = duration_cast<milliseconds>(high_resolution_clock::now() - lastSendHandshake); auto sinceLastHandshake = std::chrono::duration_cast<std::chrono::milliseconds>(high_resolution_clock::now()
- lastSendHandshake);
if (sinceLastHandshake.count() >= HANDSHAKE_RESEND_INTERVAL_MS) { if (sinceLastHandshake >= HANDSHAKE_RESEND_INTERVAL_MS) {
// it has been long enough since last handshake, send another // it has been long enough since last handshake, send another
static auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0); static auto handshakePacket = ControlPacket::create(ControlPacket::Handshake, 0);
@ -238,7 +260,8 @@ void SendQueue::run() {
// we wait for the ACK or the re-send interval to expire // we wait for the ACK or the re-send interval to expire
_handshakeACKCondition.wait_until(handshakeLock, _handshakeACKCondition.wait_until(handshakeLock,
high_resolution_clock::now() + milliseconds(HANDSHAKE_RESEND_INTERVAL_MS)); high_resolution_clock::now()
+ HANDSHAKE_RESEND_INTERVAL_MS);
// Once we're here we've either received the handshake ACK or it's going to be time to re-send a handshake. // Once we're here we've either received the handshake ACK or it's going to be time to re-send a handshake.
// Either way let's continue processing - no packets will be sent if no handshake ACK has been received. // Either way let's continue processing - no packets will be sent if no handshake ACK has been received.
@ -246,109 +269,23 @@ void SendQueue::run() {
handshakeLock.unlock(); handshakeLock.unlock();
bool naksEmpty = true; // used at the end of processing to see if we should wait for NAKs bool sentAPacket = maybeResendPacket();
bool resentPacket = false; bool flowWindowFull = false;
// the following while makes sure that we find a packet to re-send, if there is one
while (!resentPacket) {
std::unique_lock<std::mutex> nakLocker(_naksLock);
if (_naks.getLength() > 0) {
naksEmpty = _naks.getLength() > 1;
// pull the sequence number we need to re-send
SequenceNumber resendNumber = _naks.popFirstSequenceNumber();
nakLocker.unlock();
// pull the packet to re-send from the sent packets list
QReadLocker sentLocker(&_sentLock);
// see if we can find the packet to re-send
auto it = _sentPackets.find(resendNumber);
if (it != _sentPackets.end()) {
// we found the packet - grab it
auto& resendPacket = *(it->second);
// unlock the sent packets
sentLocker.unlock();
// send it off
sendPacket(resendPacket);
emit packetRetransmitted();
// mark that we did resend a packet
resentPacket = true;
// break out of our while now that we have re-sent a packet
break;
} else {
// we didn't find this packet in the sentPackets queue - assume this means it was ACKed
// we'll fire the loop again to see if there is another to re-send
continue;
}
} else {
naksEmpty = true;
}
// break from the while, we didn't resend a packet
break;
}
bool packetsEmpty = false; // used after processing to check if we should wait for packets
bool sentPacket = false;
// if we didn't find a packet to re-send AND we think we can fit a new packet on the wire // 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 // (this is according to the current flow window size) then we send out a new packet
if (_hasReceivedHandshakeACK if (!sentAPacket) {
&& !resentPacket flowWindowFull = (seqlen(SequenceNumber { (uint32_t) _lastACKSequenceNumber }, _currentSequenceNumber) >
&& seqlen(SequenceNumber { (uint32_t) _lastACKSequenceNumber }, _currentSequenceNumber) <= _flowWindowSize) { _flowWindowSize);
sentAPacket = maybeSendNewPacket();
// we didn't re-send a packet, so time to send a new one
std::unique_lock<std::mutex> locker(_packetsLock);
if (_packets.size() > 0) {
SequenceNumber nextNumber = getNextSequenceNumber();
// grab the first packet we will send
std::unique_ptr<Packet> firstPacket;
firstPacket.swap(_packets.front());
_packets.pop_front();
std::unique_ptr<Packet> secondPacket;
if (((uint32_t) nextNumber & 0xF) == 0) {
// the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
// pull off a second packet if we can before we unlock
if (_packets.size() > 0) {
secondPacket.swap(_packets.front());
_packets.pop_front();
}
}
packetsEmpty = _packets.size() == 0;
// unlock the packets, we're done pulling
locker.unlock();
sentPacket = true;
// definitely send the first packet
sendNewPacketAndAddToSentList(move(firstPacket), nextNumber);
// do we have a second in a pair to send as well?
if (secondPacket) {
nextNumber = getNextSequenceNumber();
sendNewPacketAndAddToSentList(move(secondPacket), nextNumber);
}
} else {
packetsEmpty = true;
locker.unlock();
}
} }
// Keep track of how long the flow window has been full for
if (flowWindowFull && !_flowWindowWasFull) {
_flowWindowFullSince = loopStartTimestamp;
}
_flowWindowWasFull = flowWindowFull;
// since we're a while loop, give the thread a chance to process events // since we're a while loop, give the thread a chance to process events
QCoreApplication::processEvents(); QCoreApplication::processEvents();
@ -357,30 +294,133 @@ void SendQueue::run() {
break; break;
} }
if (packetsEmpty && naksEmpty) { if (_hasReceivedHandshakeACK && !sentAPacket) {
// During our processing above the loss list and packet list were both empty. static const std::chrono::seconds CONSIDER_INACTIVE_AFTER { 5 };
// If that is still the case we should use a condition_variable_any to sleep until we have data to handle. if (flowWindowFull && (high_resolution_clock::now() - _flowWindowFullSince) > CONSIDER_INACTIVE_AFTER) {
// To confirm that the queue of packets and the NAKs list are still both empty we'll need to use the DoubleLock // If the flow window has been full for over CONSIDER_INACTIVE_AFTER,
DoubleLock doubleLock(_packetsLock, _naksLock); // then signal the queue is inactive
emit queueInactive();
// The packets queue and loss list mutexes are now both locked - check if they're still both empty } else {
if (_packets.empty() && _naks.getLength() == 0) { // During our processing above we didn't send any packets and the flow window is not full.
// both are empty - let's use a condition_variable_any to wait
_emptyCondition.wait(doubleLock);
// we have the double lock again - it'll be unlocked once it goes out of scope // If that is still the case we should use a condition_variable_any to sleep until we have data to handle.
// skip to the next iteration // To confirm that the queue of packets and the NAKs list are still both empty we'll need to use the DoubleLock
DoubleLock doubleLock(_packetsLock, _naksLock);
// 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) {
emit queueInactive();
}
// skip to the next iteration
continue;
}
}
}
auto loopEndTimestamp = high_resolution_clock::now();
// sleep as long as we need until next packet send, if we can
auto timeToSleep = (loopStartTimestamp + std::chrono::microseconds(_packetSendPeriod)) - loopEndTimestamp;
if (timeToSleep > timeToSleep.zero()) {
std::this_thread::sleep_for(timeToSleep);
}
}
}
bool SendQueue::maybeSendNewPacket() {
// we didn't re-send a packet, so time to send a new one
std::unique_lock<std::mutex> locker(_packetsLock);
if (_packets.size() > 0) {
SequenceNumber nextNumber = getNextSequenceNumber();
// grab the first packet we will send
std::unique_ptr<Packet> firstPacket;
firstPacket.swap(_packets.front());
_packets.pop_front();
std::unique_ptr<Packet> secondPacket;
if (((uint32_t) nextNumber & 0xF) == 0) {
// the first packet is the first in a probe pair - every 16 (rightmost 16 bits = 0) packets
// pull off a second packet if we can before we unlock
if (_packets.size() > 0) {
secondPacket.swap(_packets.front());
_packets.pop_front();
}
}
// unlock the packets, we're done pulling
locker.unlock();
// definitely send the first packet
sendNewPacketAndAddToSentList(move(firstPacket), nextNumber);
// do we have a second in a pair to send as well?
if (secondPacket) {
sendNewPacketAndAddToSentList(move(secondPacket), getNextSequenceNumber());
}
// We sent our packet(s), return here
return true;
}
// No packets were sent
return false;
}
bool SendQueue::maybeResendPacket() {
// the following while makes sure that we find a packet to re-send, if there is one
while (true) {
std::unique_lock<std::mutex> naksLocker(_naksLock);
if (_naks.getLength() > 0) {
// pull the sequence number we need to re-send
SequenceNumber resendNumber = _naks.popFirstSequenceNumber();
naksLocker.unlock();
// pull the packet to re-send from the sent packets list
QReadLocker sentLocker(&_sentLock);
// see if we can find the packet to re-send
auto it = _sentPackets.find(resendNumber);
if (it != _sentPackets.end()) {
// we found the packet - grab it
auto& resendPacket = *(it->second);
// unlock the sent packets
sentLocker.unlock();
// send it off
sendPacket(resendPacket);
emit packetRetransmitted();
// Signal that we did resend a packet
return true;
} else {
// we didn't find this packet in the sentPackets queue - assume this means it was ACKed
// we'll fire the loop again to see if there is another to re-send
continue; continue;
} }
} }
// sleep as long as we need until next packet send, if we can // break from the while, we didn't resend a packet
auto now = high_resolution_clock::now(); break;
auto microsecondDuration = duration_cast<microseconds>((_lastSendTimestamp + microseconds(_packetSendPeriod)) - now);
if (microsecondDuration.count() > 0) {
usleep(microsecondDuration.count());
}
} }
// No packet was resent
return false;
} }

View file

@ -42,21 +42,8 @@ class SendQueue : public QObject {
Q_OBJECT Q_OBJECT
public: public:
using high_resolution_clock = std::chrono::high_resolution_clock;
class DoubleLock { using time_point = high_resolution_clock::time_point;
public:
DoubleLock(std::mutex& mutex1, std::mutex& mutex2) : _mutex1(mutex1), _mutex2(mutex2) { lock(); }
~DoubleLock() { unlock(); }
DoubleLock(const DoubleLock&) = delete;
DoubleLock& operator=(const DoubleLock&) = delete;
void lock() { std::lock(_mutex1, _mutex2); }
void unlock() { _mutex1.unlock(); _mutex2.unlock(); }
private:
std::mutex& _mutex1;
std::mutex& _mutex2;
};
static std::unique_ptr<SendQueue> create(Socket* socket, HifiSockAddr destination); static std::unique_ptr<SendQueue> create(Socket* socket, HifiSockAddr destination);
@ -82,6 +69,8 @@ signals:
void packetSent(int dataSize, int payloadSize); void packetSent(int dataSize, int payloadSize);
void packetRetransmitted(); void packetRetransmitted();
void queueInactive();
private slots: private slots:
void run(); void run();
@ -93,6 +82,9 @@ private:
void sendPacket(const Packet& packet); void sendPacket(const Packet& packet);
void sendNewPacketAndAddToSentList(std::unique_ptr<Packet> newPacket, SequenceNumber sequenceNumber); 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
// Increments current sequence number and return it // Increments current sequence number and return it
SequenceNumber getNextSequenceNumber(); SequenceNumber getNextSequenceNumber();
MessageNumber getNextMessageNumber(); MessageNumber getNextMessageNumber();
@ -110,11 +102,14 @@ private:
std::atomic<uint32_t> _atomicCurrentSequenceNumber { 0 };// Atomic for last sequence number sent out std::atomic<uint32_t> _atomicCurrentSequenceNumber { 0 };// Atomic for last sequence number sent out
std::atomic<int> _packetSendPeriod { 0 }; // Interval between two packet send event in microseconds, set from CC std::atomic<int> _packetSendPeriod { 0 }; // Interval between two packet send event in microseconds, set from CC
std::chrono::high_resolution_clock::time_point _lastSendTimestamp; // Record last time of packet departure
std::atomic<bool> _isRunning { false }; std::atomic<bool> _isRunning { false };
std::atomic<int> _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC std::atomic<int> _flowWindowSize { 0 }; // Flow control window size (number of packets that can be on wire) - set from CC
// Used to detect when the connection becomes inactive for too long
bool _flowWindowWasFull = false;
time_point _flowWindowFullSince;
mutable std::mutex _naksLock; // Protects the naks list. mutable std::mutex _naksLock; // Protects the naks list.
LossList _naks; // Sequence numbers of packets to resend LossList _naks; // Sequence numbers of packets to resend

View file

@ -147,12 +147,18 @@ Connection& Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) {
if (it == _connectionsHash.end()) { if (it == _connectionsHash.end()) {
auto connection = std::unique_ptr<Connection>(new Connection(this, sockAddr, _ccFactory->create())); auto connection = std::unique_ptr<Connection>(new Connection(this, sockAddr, _ccFactory->create()));
QObject::connect(connection.get(), &Connection::connectionInactive, this, &Socket::cleanupConnection);
it = _connectionsHash.insert(it, std::make_pair(sockAddr, std::move(connection))); it = _connectionsHash.insert(it, std::make_pair(sockAddr, std::move(connection)));
} }
return *it->second; return *it->second;
} }
void Socket::cleanupConnection(HifiSockAddr sockAddr) {
qCDebug(networking) << "Socket::cleanupConnection called for connection to" << sockAddr;
_connectionsHash.erase(sockAddr);
}
void Socket::messageReceived(std::unique_ptr<PacketList> packetList) { void Socket::messageReceived(std::unique_ptr<PacketList> packetList) {
if (_packetListHandler) { if (_packetListHandler) {
_packetListHandler(std::move(packetList)); _packetListHandler(std::move(packetList));
@ -205,8 +211,8 @@ void Socket::readPendingDatagrams() {
// if this was a reliable packet then signal the matching connection with the sequence number // if this was a reliable packet then signal the matching connection with the sequence number
auto& connection = findOrCreateConnection(senderSockAddr); auto& connection = findOrCreateConnection(senderSockAddr);
connection.processReceivedSequenceNumber(packet->getSequenceNumber(), connection.processReceivedSequenceNumber(packet->getSequenceNumber(),
packet->getDataSize(), packet->getDataSize(),
packet->getPayloadSize()); packet->getPayloadSize());
} }
if (packet->isPartOfMessage()) { if (packet->isPartOfMessage()) {

View file

@ -73,6 +73,9 @@ public:
ConnectionStats::Stats sampleStatsForConnection(const HifiSockAddr& destination); ConnectionStats::Stats sampleStatsForConnection(const HifiSockAddr& destination);
std::vector<HifiSockAddr> getConnectionSockAddrs(); std::vector<HifiSockAddr> getConnectionSockAddrs();
public slots:
void cleanupConnection(HifiSockAddr sockAddr);
private slots: private slots:
void readPendingDatagrams(); void readPendingDatagrams();
void rateControlSync(); void rateControlSync();

View file

@ -1678,15 +1678,15 @@ void GeometryCache::useSimpleDrawPipeline(gpu::Batch& batch, bool noBlend) {
} }
} }
GeometryReader::GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping) : GeometryReader::GeometryReader(const QUrl& url, const QByteArray& data, const QVariantHash& mapping) :
_url(url), _url(url),
_reply(reply), _data(data),
_mapping(mapping) { _mapping(mapping) {
} }
void GeometryReader::run() { void GeometryReader::run() {
try { try {
if (!_reply) { if (_data.isEmpty()) {
throw QString("Reply is NULL ?!"); throw QString("Reply is NULL ?!");
} }
QString urlname = _url.path().toLower(); QString urlname = _url.path().toLower();
@ -1701,9 +1701,9 @@ void GeometryReader::run() {
if (_url.path().toLower().endsWith(".fbx")) { if (_url.path().toLower().endsWith(".fbx")) {
const bool grabLightmaps = true; const bool grabLightmaps = true;
const float lightmapLevel = 1.0f; const float lightmapLevel = 1.0f;
fbxgeo = readFBX(_reply, _mapping, _url.path(), grabLightmaps, lightmapLevel); fbxgeo = readFBX(_data, _mapping, _url.path(), grabLightmaps, lightmapLevel);
} else if (_url.path().toLower().endsWith(".obj")) { } else if (_url.path().toLower().endsWith(".obj")) {
fbxgeo = OBJReader().readOBJ(_reply, _mapping, &_url); fbxgeo = OBJReader().readOBJ(_data, _mapping);
} else { } else {
QString errorStr("usupported format"); QString errorStr("usupported format");
emit onError(NetworkGeometry::ModelParseError, errorStr); emit onError(NetworkGeometry::ModelParseError, errorStr);
@ -1717,7 +1717,6 @@ void GeometryReader::run() {
qCDebug(renderutils) << "Error reading " << _url << ": " << error; qCDebug(renderutils) << "Error reading " << _url << ": " << error;
emit onError(NetworkGeometry::ModelParseError, error); emit onError(NetworkGeometry::ModelParseError, error);
} }
_reply->deleteLater();
} }
NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) : NetworkGeometry::NetworkGeometry(const QUrl& url, bool delayLoad, const QVariantHash& mapping, const QUrl& textureBaseUrl) :
@ -1746,8 +1745,10 @@ void NetworkGeometry::attemptRequest() {
void NetworkGeometry::attemptRequestInternal() { void NetworkGeometry::attemptRequestInternal() {
if (_url.path().toLower().endsWith(".fst")) { if (_url.path().toLower().endsWith(".fst")) {
_mappingUrl = _url;
requestMapping(_url); requestMapping(_url);
} else { } else {
_modelUrl = _url;
requestModel(_url); requestModel(_url);
} }
} }
@ -1838,8 +1839,8 @@ void NetworkGeometry::requestMapping(const QUrl& url) {
_resource->deleteLater(); _resource->deleteLater();
} }
_resource = new Resource(url, false); _resource = new Resource(url, false);
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(mappingRequestDone(QNetworkReply&))); connect(_resource, &Resource::loaded, this, &NetworkGeometry::mappingRequestDone);
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(mappingRequestError(QNetworkReply::NetworkError))); connect(_resource, &Resource::failed, this, &NetworkGeometry::mappingRequestError);
} }
void NetworkGeometry::requestModel(const QUrl& url) { void NetworkGeometry::requestModel(const QUrl& url) {
@ -1847,18 +1848,19 @@ void NetworkGeometry::requestModel(const QUrl& url) {
if (_resource) { if (_resource) {
_resource->deleteLater(); _resource->deleteLater();
} }
_modelUrl = url;
_resource = new Resource(url, false); _resource = new Resource(url, false);
connect(_resource, SIGNAL(loaded(QNetworkReply&)), SLOT(modelRequestDone(QNetworkReply&))); connect(_resource, &Resource::loaded, this, &NetworkGeometry::modelRequestDone);
connect(_resource, SIGNAL(failed(QNetworkReply::NetworkError)), SLOT(modelRequestError(QNetworkReply::NetworkError))); connect(_resource, &Resource::failed, this, &NetworkGeometry::modelRequestError);
} }
void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) { void NetworkGeometry::mappingRequestDone(const QByteArray& data) {
assert(_state == RequestMappingState); assert(_state == RequestMappingState);
// parse the mapping file // parse the mapping file
_mapping = FSTReader::readMapping(reply.readAll()); _mapping = FSTReader::readMapping(data);
QUrl replyUrl = reply.url(); QUrl replyUrl = _mappingUrl;
QString modelUrlStr = _mapping.value("filename").toString(); QString modelUrlStr = _mapping.value("filename").toString();
if (modelUrlStr.isNull()) { if (modelUrlStr.isNull()) {
qCDebug(renderutils) << "Mapping file " << _url << "has no \"filename\" entry"; qCDebug(renderutils) << "Mapping file " << _url << "has no \"filename\" entry";
@ -1873,8 +1875,8 @@ void NetworkGeometry::mappingRequestDone(QNetworkReply& reply) {
_textureBaseUrl = replyUrl.resolved(texdir); _textureBaseUrl = replyUrl.resolved(texdir);
} }
QUrl modelUrl = replyUrl.resolved(modelUrlStr); _modelUrl = replyUrl.resolved(modelUrlStr);
requestModel(modelUrl); requestModel(_modelUrl);
} }
} }
@ -1884,13 +1886,13 @@ void NetworkGeometry::mappingRequestError(QNetworkReply::NetworkError error) {
emit onFailure(*this, MappingRequestError); emit onFailure(*this, MappingRequestError);
} }
void NetworkGeometry::modelRequestDone(QNetworkReply& reply) { void NetworkGeometry::modelRequestDone(const QByteArray& data) {
assert(_state == RequestModelState); assert(_state == RequestModelState);
_state = ParsingModelState; _state = ParsingModelState;
// asynchronously parse the model file. // asynchronously parse the model file.
GeometryReader* geometryReader = new GeometryReader(reply.url(), &reply, _mapping); GeometryReader* geometryReader = new GeometryReader(_modelUrl, data, _mapping);
connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*))); connect(geometryReader, SIGNAL(onSuccess(FBXGeometry*)), SLOT(modelParseSuccess(FBXGeometry*)));
connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString))); connect(geometryReader, SIGNAL(onError(int, QString)), SLOT(modelParseError(int, QString)));

View file

@ -348,10 +348,10 @@ signals:
void onFailure(NetworkGeometry& networkGeometry, Error error); void onFailure(NetworkGeometry& networkGeometry, Error error);
protected slots: protected slots:
void mappingRequestDone(QNetworkReply& reply); void mappingRequestDone(const QByteArray& data);
void mappingRequestError(QNetworkReply::NetworkError error); void mappingRequestError(QNetworkReply::NetworkError error);
void modelRequestDone(QNetworkReply& reply); void modelRequestDone(const QByteArray& data);
void modelRequestError(QNetworkReply::NetworkError error); void modelRequestError(QNetworkReply::NetworkError error);
void modelParseSuccess(FBXGeometry* geometry); void modelParseSuccess(FBXGeometry* geometry);
@ -371,6 +371,8 @@ protected:
State _state; State _state;
QUrl _url; QUrl _url;
QUrl _mappingUrl;
QUrl _modelUrl;
QVariantHash _mapping; QVariantHash _mapping;
QUrl _textureBaseUrl; QUrl _textureBaseUrl;
@ -386,14 +388,14 @@ protected:
class GeometryReader : public QObject, public QRunnable { class GeometryReader : public QObject, public QRunnable {
Q_OBJECT Q_OBJECT
public: public:
GeometryReader(const QUrl& url, QNetworkReply* reply, const QVariantHash& mapping); GeometryReader(const QUrl& url, const QByteArray& data, const QVariantHash& mapping);
virtual void run(); virtual void run();
signals: signals:
void onSuccess(FBXGeometry* geometry); void onSuccess(FBXGeometry* geometry);
void onError(int error, QString str); void onError(int error, QString str);
private: private:
QUrl _url; QUrl _url;
QNetworkReply* _reply; QByteArray _data;
QVariantHash _mapping; QVariantHash _mapping;
}; };

View file

@ -215,8 +215,7 @@ NetworkTexture::NetworkTexture(const QUrl& url, TextureType type, const QByteArr
class ImageReader : public QRunnable { class ImageReader : public QRunnable {
public: public:
ImageReader(const QWeakPointer<Resource>& texture, TextureType type, QNetworkReply* reply, const QUrl& url = QUrl(), ImageReader(const QWeakPointer<Resource>& texture, TextureType type, const QByteArray& data, const QUrl& url = QUrl());
const QByteArray& content = QByteArray());
virtual void run(); virtual void run();
@ -224,27 +223,25 @@ private:
QWeakPointer<Resource> _texture; QWeakPointer<Resource> _texture;
TextureType _type; TextureType _type;
QNetworkReply* _reply;
QUrl _url; QUrl _url;
QByteArray _content; QByteArray _content;
}; };
void NetworkTexture::downloadFinished(QNetworkReply* reply) { void NetworkTexture::downloadFinished(const QByteArray& data) {
// send the reader off to the thread pool // send the reader off to the thread pool
QThreadPool::globalInstance()->start(new ImageReader(_self, _type, reply)); QThreadPool::globalInstance()->start(new ImageReader(_self, _type, data, _url));
} }
void NetworkTexture::loadContent(const QByteArray& content) { void NetworkTexture::loadContent(const QByteArray& content) {
QThreadPool::globalInstance()->start(new ImageReader(_self, _type, NULL, _url, content)); QThreadPool::globalInstance()->start(new ImageReader(_self, _type, content, _url));
} }
ImageReader::ImageReader(const QWeakPointer<Resource>& texture, TextureType type, QNetworkReply* reply, ImageReader::ImageReader(const QWeakPointer<Resource>& texture, TextureType type, const QByteArray& data,
const QUrl& url, const QByteArray& content) : const QUrl& url) :
_texture(texture), _texture(texture),
_type(type), _type(type),
_reply(reply),
_url(url), _url(url),
_content(content) { _content(data) {
} }
std::once_flag onceListSupportedFormatsflag; std::once_flag onceListSupportedFormatsflag;
@ -297,16 +294,8 @@ public:
void ImageReader::run() { void ImageReader::run() {
QSharedPointer<Resource> texture = _texture.toStrongRef(); QSharedPointer<Resource> texture = _texture.toStrongRef();
if (texture.isNull()) { if (texture.isNull()) {
if (_reply) {
_reply->deleteLater();
}
return; return;
} }
if (_reply) {
_url = _reply->url();
_content = _reply->readAll();
_reply->deleteLater();
}
listSupportedImageFormats(); listSupportedImageFormats();

View file

@ -123,7 +123,7 @@ public:
TextureType getType() const { return _type; } TextureType getType() const { return _type; }
protected: protected:
virtual void downloadFinished(QNetworkReply* reply); virtual void downloadFinished(const QByteArray& data) override;
Q_INVOKABLE void loadContent(const QByteArray& content); Q_INVOKABLE void loadContent(const QByteArray& content);
// FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on... // FIXME: This void* should be a gpu::Texture* but i cannot get it to work for now, moving on...

View file

@ -17,9 +17,7 @@
#include "BatchLoader.h" #include "BatchLoader.h"
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <SharedUtil.h> #include <SharedUtil.h>
#include "ResourceManager.h"
BatchLoader::BatchLoader(const QList<QUrl>& urls) BatchLoader::BatchLoader(const QList<QUrl>& urls)
: QObject(), : QObject(),
@ -27,6 +25,7 @@ BatchLoader::BatchLoader(const QList<QUrl>& urls)
_finished(false), _finished(false),
_urls(urls.toSet()), _urls(urls.toSet()),
_data() { _data() {
qRegisterMetaType<QMap<QUrl, QString>>("QMap<QUrl, QString>");
} }
void BatchLoader::start() { void BatchLoader::start() {
@ -35,45 +34,27 @@ void BatchLoader::start() {
} }
_started = true; _started = true;
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
for (QUrl url : _urls) { for (QUrl url : _urls) {
if (url.scheme() == "http" || url.scheme() == "https" || url.scheme() == "ftp") { auto request = ResourceManager::createResourceRequest(this, url);
QNetworkRequest request = QNetworkRequest(url); if (!request) {
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); continue;
QNetworkReply* reply = networkAccessManager.get(request); }
connect(request, &ResourceRequest::finished, this, [=]() {
qCDebug(scriptengine) << "Downloading file at" << url; if (request->getResult() == ResourceRequest::SUCCESS) {
_data.insert(url, request->getData());
connect(reply, &QNetworkReply::finished, [=]() {
if (reply->error()) {
_data.insert(url, QString());
} else {
_data.insert(url, reply->readAll());
}
reply->deleteLater();
checkFinished();
});
// If we end up being destroyed before the reply finishes, clean it up
connect(this, &QObject::destroyed, reply, &QObject::deleteLater);
} else {
#ifdef _WIN32
QString fileName = url.toString();
#else
QString fileName = url.toLocalFile();
#endif
qCDebug(scriptengine) << "Reading file at " << fileName;
QFile scriptFile(fileName);
if (scriptFile.open(QFile::ReadOnly | QFile::Text)) {
QTextStream in(&scriptFile);
_data.insert(url, in.readAll());
} else { } else {
_data.insert(url, QString()); _data.insert(url, QString());
} }
} request->deleteLater();
checkFinished();
});
// If we end up being destroyed before the reply finishes, clean it up
connect(this, &QObject::destroyed, request, &QObject::deleteLater);
qCDebug(scriptengine) << "Loading script at " << url;
request->send();
} }
checkFinished(); checkFinished();
} }

View file

@ -17,7 +17,6 @@
#include <QObject> #include <QObject>
#include <assert.h> #include <assert.h>
#include <NetworkAccessManager.h>
#include <SharedUtil.h> #include <SharedUtil.h>
#include "ScriptCache.h" #include "ScriptCache.h"
@ -28,8 +27,6 @@ ScriptCache::ScriptCache(QObject* parent) {
} }
QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool reload) { QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& isPending, bool reload) {
assert(!_scriptCache.contains(url) || !reload);
QString scriptContents; QString scriptContents;
if (_scriptCache.contains(url) && !reload) { if (_scriptCache.contains(url) && !reload) {
qCDebug(scriptengine) << "Found script in cache:" << url.toString(); qCDebug(scriptengine) << "Found script in cache:" << url.toString();
@ -44,18 +41,10 @@ QString ScriptCache::getScript(const QUrl& url, ScriptUser* scriptUser, bool& is
if (alreadyWaiting) { if (alreadyWaiting) {
qCDebug(scriptengine) << "Already downloading script at:" << url.toString(); qCDebug(scriptengine) << "Already downloading script at:" << url.toString();
} else { } else {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); auto request = ResourceManager::createResourceRequest(this, url);
QNetworkRequest networkRequest = QNetworkRequest(url); request->setCacheEnabled(!reload);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); connect(request, &ResourceRequest::finished, this, &ScriptCache::scriptDownloaded);
if (reload) { request->send();
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
qCDebug(scriptengine) << "Redownloading script at:" << url.toString();
} else {
qCDebug(scriptengine) << "Downloading script at:" << url.toString();
}
QNetworkReply* reply = networkAccessManager.get(networkRequest);
connect(reply, &QNetworkReply::finished, this, &ScriptCache::scriptDownloaded);
} }
} }
return scriptContents; return scriptContents;
@ -69,27 +58,25 @@ void ScriptCache::deleteScript(const QUrl& url) {
} }
void ScriptCache::scriptDownloaded() { void ScriptCache::scriptDownloaded() {
QNetworkReply* reply = qobject_cast<QNetworkReply*>(sender()); ResourceRequest* req = qobject_cast<ResourceRequest*>(sender());
QUrl url = reply->url(); QUrl url = req->getUrl();
QList<ScriptUser*> scriptUsers = _scriptUsers.values(url); QList<ScriptUser*> scriptUsers = _scriptUsers.values(url);
_scriptUsers.remove(url); _scriptUsers.remove(url);
if (reply->error() == QNetworkReply::NoError && reply->attribute(QNetworkRequest::HttpStatusCodeAttribute) == 200) { if (req->getResult() == ResourceRequest::SUCCESS) {
_scriptCache[url] = reply->readAll(); _scriptCache[url] = req->getData();
qCDebug(scriptengine) << "Done downloading script at:" << url.toString(); qCDebug(scriptengine) << "Done downloading script at:" << url.toString();
foreach(ScriptUser* user, scriptUsers) { foreach(ScriptUser* user, scriptUsers) {
user->scriptContentsAvailable(url, _scriptCache[url]); user->scriptContentsAvailable(url, _scriptCache[url]);
} }
} else { } else {
qCWarning(scriptengine) << "Error loading script from URL " << reply->url().toString() qCWarning(scriptengine) << "Error loading script from URL " << url;
<< "- HTTP status code is" << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt()
<< "and error from QNetworkReply is" << reply->errorString();
foreach(ScriptUser* user, scriptUsers) { foreach(ScriptUser* user, scriptUsers) {
user->errorInLoadingScript(url); user->errorInLoadingScript(url);
} }
} }
reply->deleteLater(); req->deleteLater();
} }